summaryrefslogtreecommitdiffstats
path: root/dom/base
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/base
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/base')
-rw-r--r--dom/base/AnonymousContent.cpp212
-rw-r--r--dom/base/AnonymousContent.h79
-rw-r--r--dom/base/Attr.cpp374
-rw-r--r--dom/base/Attr.h115
-rw-r--r--dom/base/AutocompleteFieldList.h142
-rw-r--r--dom/base/BarProps.cpp309
-rw-r--r--dom/base/BarProps.h129
-rw-r--r--dom/base/BlobSet.cpp64
-rw-r--r--dom/base/BlobSet.h36
-rw-r--r--dom/base/BodyUtil.cpp575
-rw-r--r--dom/base/BodyUtil.h68
-rw-r--r--dom/base/BorrowedAttrInfo.cpp28
-rw-r--r--dom/base/BorrowedAttrInfo.h46
-rw-r--r--dom/base/CORSMode.h33
-rw-r--r--dom/base/ChildIterator.cpp616
-rw-r--r--dom/base/ChildIterator.h286
-rw-r--r--dom/base/ChromeNodeList.cpp50
-rw-r--r--dom/base/ChromeNodeList.h35
-rw-r--r--dom/base/ChromeUtils.cpp203
-rw-r--r--dom/base/ChromeUtils.h103
-rw-r--r--dom/base/Comment.cpp84
-rw-r--r--dom/base/Comment.h82
-rw-r--r--dom/base/Crypto.cpp127
-rw-r--r--dom/base/Crypto.h65
-rw-r--r--dom/base/CustomElementRegistry.cpp857
-rw-r--r--dom/base/CustomElementRegistry.h259
-rw-r--r--dom/base/DOMCursor.cpp92
-rw-r--r--dom/base/DOMCursor.h60
-rw-r--r--dom/base/DOMError.cpp77
-rw-r--r--dom/base/DOMError.h85
-rw-r--r--dom/base/DOMException.cpp646
-rw-r--r--dom/base/DOMException.h214
-rw-r--r--dom/base/DOMImplementation.cpp266
-rw-r--r--dom/base/DOMImplementation.h101
-rw-r--r--dom/base/DOMIntersectionObserver.cpp471
-rw-r--r--dom/base/DOMIntersectionObserver.h183
-rw-r--r--dom/base/DOMMatrix.cpp655
-rw-r--r--dom/base/DOMMatrix.h258
-rw-r--r--dom/base/DOMParser.cpp495
-rw-r--r--dom/base/DOMParser.h126
-rw-r--r--dom/base/DOMPoint.cpp43
-rw-r--r--dom/base/DOMPoint.h79
-rw-r--r--dom/base/DOMQuad.cpp160
-rw-r--r--dom/base/DOMQuad.h70
-rw-r--r--dom/base/DOMRect.cpp126
-rw-r--r--dom/base/DOMRect.h215
-rw-r--r--dom/base/DOMRequest.cpp382
-rw-r--r--dom/base/DOMRequest.h120
-rw-r--r--dom/base/DOMRequestHelper.jsm336
-rw-r--r--dom/base/DOMStringList.cpp34
-rw-r--r--dom/base/DOMStringList.h104
-rw-r--r--dom/base/DOMTokenListSupportedTokens.h31
-rw-r--r--dom/base/DirectionalityUtils.cpp1070
-rw-r--r--dom/base/DirectionalityUtils.h146
-rw-r--r--dom/base/DocGroup.cpp55
-rw-r--r--dom/base/DocGroup.h79
-rw-r--r--dom/base/DocumentFragment.cpp151
-rw-r--r--dom/base/DocumentFragment.h155
-rw-r--r--dom/base/DocumentType.cpp151
-rw-r--r--dom/base/DocumentType.h107
-rw-r--r--dom/base/Element.cpp3932
-rw-r--r--dom/base/Element.h1907
-rw-r--r--dom/base/ElementInlines.h31
-rw-r--r--dom/base/EventSource.cpp1356
-rw-r--r--dom/base/EventSource.h259
-rw-r--r--dom/base/FeedWriterEnabled.h19
-rw-r--r--dom/base/File.cpp1367
-rw-r--r--dom/base/File.h878
-rw-r--r--dom/base/FileList.cpp86
-rw-r--r--dom/base/FileList.h103
-rw-r--r--dom/base/FileReader.cpp790
-rw-r--r--dom/base/FileReader.h202
-rw-r--r--dom/base/FormData.cpp430
-rw-r--r--dom/base/FormData.h166
-rw-r--r--dom/base/FragmentOrElement.cpp2394
-rw-r--r--dom/base/FragmentOrElement.h400
-rw-r--r--dom/base/FromParser.h27
-rw-r--r--dom/base/GroupedSHistory.cpp172
-rw-r--r--dom/base/GroupedSHistory.h105
-rw-r--r--dom/base/HTMLSplitOnSpacesTokenizer.h16
-rw-r--r--dom/base/IdleDeadline.cpp70
-rw-r--r--dom/base/IdleDeadline.h55
-rw-r--r--dom/base/IdleRequest.cpp157
-rw-r--r--dom/base/IdleRequest.h74
-rw-r--r--dom/base/IframeSandboxKeywordList.h27
-rw-r--r--dom/base/ImageEncoder.cpp586
-rw-r--r--dom/base/ImageEncoder.h138
-rw-r--r--dom/base/ImageTracker.cpp161
-rw-r--r--dom/base/ImageTracker.h69
-rw-r--r--dom/base/ImportManager.cpp748
-rw-r--r--dom/base/ImportManager.h283
-rw-r--r--dom/base/IndexedDBHelper.jsm208
-rw-r--r--dom/base/Link.cpp659
-rw-r--r--dom/base/Link.h171
-rw-r--r--dom/base/Location.cpp941
-rw-r--r--dom/base/Location.h258
-rw-r--r--dom/base/MultipartBlobImpl.cpp455
-rw-r--r--dom/base/MultipartBlobImpl.h138
-rw-r--r--dom/base/MutableBlobStorage.cpp643
-rw-r--r--dom/base/MutableBlobStorage.h107
-rw-r--r--dom/base/MutableBlobStreamListener.cpp102
-rw-r--r--dom/base/MutableBlobStreamListener.h48
-rw-r--r--dom/base/NameSpaceConstants.h29
-rw-r--r--dom/base/Navigator.cpp2038
-rw-r--r--dom/base/Navigator.h306
-rw-r--r--dom/base/NodeInfo.cpp219
-rw-r--r--dom/base/NodeInfo.h303
-rw-r--r--dom/base/NodeInfoInlines.h116
-rw-r--r--dom/base/NodeIterator.cpp291
-rw-r--r--dom/base/NodeIterator.h122
-rw-r--r--dom/base/PartialSHistory.cpp294
-rw-r--r--dom/base/PartialSHistory.h61
-rw-r--r--dom/base/Pose.cpp87
-rw-r--r--dom/base/Pose.h64
-rw-r--r--dom/base/PostMessageEvent.cpp186
-rw-r--r--dom/base/PostMessageEvent.h54
-rw-r--r--dom/base/ProcessGlobal.cpp110
-rw-r--r--dom/base/ProcessGlobal.h84
-rw-r--r--dom/base/ResponsiveImageSelector.cpp779
-rw-r--r--dom/base/ResponsiveImageSelector.h185
-rw-r--r--dom/base/SameProcessMessageQueue.cpp74
-rw-r--r--dom/base/SameProcessMessageQueue.h53
-rw-r--r--dom/base/ScreenOrientation.cpp688
-rw-r--r--dom/base/ScreenOrientation.h122
-rw-r--r--dom/base/ScriptSettings.cpp833
-rw-r--r--dom/base/ScriptSettings.h465
-rw-r--r--dom/base/ShadowRoot.cpp765
-rw-r--r--dom/base/ShadowRoot.h218
-rw-r--r--dom/base/SiteSpecificUserAgent.js88
-rw-r--r--dom/base/SiteSpecificUserAgent.manifest2
-rw-r--r--dom/base/SlowScriptDebug.js24
-rw-r--r--dom/base/SlowScriptDebug.manifest2
-rw-r--r--dom/base/StructuredCloneHolder.cpp1368
-rw-r--r--dom/base/StructuredCloneHolder.h332
-rw-r--r--dom/base/StructuredCloneTags.h75
-rw-r--r--dom/base/StyleSheetList.cpp47
-rw-r--r--dom/base/StyleSheetList.h47
-rw-r--r--dom/base/SubtleCrypto.cpp178
-rw-r--r--dom/base/SubtleCrypto.h128
-rw-r--r--dom/base/TabGroup.cpp170
-rw-r--r--dom/base/TabGroup.h118
-rw-r--r--dom/base/Text.cpp38
-rw-r--r--dom/base/Text.h56
-rw-r--r--dom/base/TextInputProcessor.cpp1035
-rw-r--r--dom/base/TextInputProcessor.h191
-rw-r--r--dom/base/ThirdPartyUtil.cpp320
-rw-r--r--dom/base/ThirdPartyUtil.h36
-rw-r--r--dom/base/Timeout.cpp111
-rw-r--r--dom/base/Timeout.h103
-rw-r--r--dom/base/TreeWalker.cpp449
-rw-r--r--dom/base/TreeWalker.h111
-rw-r--r--dom/base/UseCounter.h36
-rw-r--r--dom/base/UseCounters.conf63
-rw-r--r--dom/base/WebKitCSSMatrix.cpp262
-rw-r--r--dom/base/WebKitCSSMatrix.h72
-rw-r--r--dom/base/WebSocket.cpp2908
-rw-r--r--dom/base/WebSocket.h209
-rw-r--r--dom/base/WindowNamedPropertiesHandler.cpp325
-rw-r--r--dom/base/WindowNamedPropertiesHandler.h79
-rw-r--r--dom/base/WindowOrientationObserver.cpp57
-rw-r--r--dom/base/WindowOrientationObserver.h35
-rw-r--r--dom/base/contentAreaDropListener.js228
-rw-r--r--dom/base/contentAreaDropListener.manifest2
-rw-r--r--dom/base/crashtests/1024428-1.html12
-rw-r--r--dom/base/crashtests/1026714.html16
-rw-r--r--dom/base/crashtests/1027461-1.html9
-rw-r--r--dom/base/crashtests/1027461-inner.xul2
-rw-r--r--dom/base/crashtests/1029710.html11
-rw-r--r--dom/base/crashtests/1118764.html33
-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/1230422.html28
-rw-r--r--dom/base/crashtests/1251361.html33
-rw-r--r--dom/base/crashtests/1304437.html13
-rw-r--r--dom/base/crashtests/1385272-1.html29
-rw-r--r--dom/base/crashtests/149320-1.html16
-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/308120-1.xul3
-rw-r--r--dom/base/crashtests/324871-1.html14
-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/326778-1.xul11
-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/330925-1.xhtml35
-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/348049-1.xhtml40
-rw-r--r--dom/base/crashtests/349355-1.html41
-rw-r--r--dom/base/crashtests/354645-1.xul25
-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.xul18
-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/395469-1.xhtml29
-rw-r--r--dom/base/crashtests/395469-2.xhtml45
-rw-r--r--dom/base/crashtests/398088-1.xul23
-rw-r--r--dom/base/crashtests/399712-1.html29
-rw-r--r--dom/base/crashtests/400763-1.html7
-rw-r--r--dom/base/crashtests/401993-1.html38
-rw-r--r--dom/base/crashtests/401993-1.xml1
-rw-r--r--dom/base/crashtests/404869-1.xul23
-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/473284.xul93
-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.html22
-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/561981-1-iframe.xhtml56
-rw-r--r--dom/base/crashtests/561981-1.html12
-rw-r--r--dom/base/crashtests/561981-2-iframe.xhtml38
-rw-r--r--dom/base/crashtests/561981-2.html12
-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/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/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/822691.html21
-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.list211
-rw-r--r--dom/base/crashtests/structured_clone_container_throws.html9
-rw-r--r--dom/base/crashtests/xhr_abortinprogress.html23
-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.msg168
-rwxr-xr-xdom/base/gen-usecounters.py80
-rw-r--r--dom/base/messageWakeupService.js96
-rw-r--r--dom/base/messageWakeupService.manifest4
-rw-r--r--dom/base/moz.build495
-rw-r--r--dom/base/mozAutoDocUpdate.h91
-rw-r--r--dom/base/mozFlushType.h46
-rw-r--r--dom/base/mozIDOMWindow.idl16
-rw-r--r--dom/base/nsAtomListUtils.cpp26
-rw-r--r--dom/base/nsAtomListUtils.h26
-rw-r--r--dom/base/nsAttrAndChildArray.cpp907
-rw-r--r--dom/base/nsAttrAndChildArray.h222
-rw-r--r--dom/base/nsAttrName.h214
-rw-r--r--dom/base/nsAttrValue.cpp1974
-rw-r--r--dom/base/nsAttrValue.h544
-rw-r--r--dom/base/nsAttrValueInlines.h239
-rw-r--r--dom/base/nsAttrValueOrString.cpp30
-rw-r--r--dom/base/nsAttrValueOrString.h95
-rw-r--r--dom/base/nsCCUncollectableMarker.cpp550
-rw-r--r--dom/base/nsCCUncollectableMarker.h51
-rw-r--r--dom/base/nsCaseTreatment.h18
-rw-r--r--dom/base/nsChildContentList.h63
-rw-r--r--dom/base/nsContentAreaDragDrop.cpp895
-rw-r--r--dom/base/nsContentAreaDragDrop.h81
-rw-r--r--dom/base/nsContentCID.h183
-rw-r--r--dom/base/nsContentCreatorFunctions.h77
-rw-r--r--dom/base/nsContentIterator.cpp1553
-rw-r--r--dom/base/nsContentList.cpp1077
-rw-r--r--dom/base/nsContentList.h587
-rw-r--r--dom/base/nsContentListDeclarations.h67
-rw-r--r--dom/base/nsContentPermissionHelper.cpp858
-rw-r--r--dom/base/nsContentPermissionHelper.h215
-rw-r--r--dom/base/nsContentPolicy.cpp269
-rw-r--r--dom/base/nsContentPolicy.h63
-rw-r--r--dom/base/nsContentPolicyUtils.h334
-rw-r--r--dom/base/nsContentSink.cpp1621
-rw-r--r--dom/base/nsContentSink.h362
-rw-r--r--dom/base/nsContentTypeParser.h28
-rw-r--r--dom/base/nsContentUtils.cpp9783
-rw-r--r--dom/base/nsContentUtils.h2963
-rw-r--r--dom/base/nsCopySupport.cpp927
-rw-r--r--dom/base/nsCopySupport.h101
-rw-r--r--dom/base/nsDOMAttributeMap.cpp527
-rw-r--r--dom/base/nsDOMAttributeMap.h188
-rw-r--r--dom/base/nsDOMCID.h33
-rw-r--r--dom/base/nsDOMCaretPosition.cpp76
-rw-r--r--dom/base/nsDOMCaretPosition.h101
-rw-r--r--dom/base/nsDOMClassInfo.cpp2179
-rw-r--r--dom/base/nsDOMClassInfo.h296
-rw-r--r--dom/base/nsDOMClassInfoClasses.h57
-rw-r--r--dom/base/nsDOMClassInfoID.h65
-rw-r--r--dom/base/nsDOMDataChannel.cpp635
-rw-r--r--dom/base/nsDOMDataChannel.h144
-rw-r--r--dom/base/nsDOMDataChannelDeclarations.h30
-rw-r--r--dom/base/nsDOMJSUtils.h25
-rw-r--r--dom/base/nsDOMMutationObserver.cpp1175
-rw-r--r--dom/base/nsDOMMutationObserver.h927
-rw-r--r--dom/base/nsDOMNavigationTiming.cpp247
-rw-r--r--dom/base/nsDOMNavigationTiming.h157
-rw-r--r--dom/base/nsDOMSerializer.cpp147
-rw-r--r--dom/base/nsDOMSerializer.h69
-rw-r--r--dom/base/nsDOMString.h22
-rw-r--r--dom/base/nsDOMTokenList.cpp374
-rw-r--r--dom/base/nsDOMTokenList.h104
-rw-r--r--dom/base/nsDOMWindowList.cpp127
-rw-r--r--dom/base/nsDOMWindowList.h49
-rw-r--r--dom/base/nsDOMWindowUtils.cpp4156
-rw-r--r--dom/base/nsDOMWindowUtils.h135
-rw-r--r--dom/base/nsDataDocumentContentPolicy.cpp161
-rw-r--r--dom/base/nsDataDocumentContentPolicy.h40
-rw-r--r--dom/base/nsDeprecatedOperationList.h52
-rw-r--r--dom/base/nsDocElementCreatedNotificationRunner.h34
-rw-r--r--dom/base/nsDocument.cpp12834
-rw-r--r--dom/base/nsDocument.h1641
-rw-r--r--dom/base/nsDocumentEncoder.cpp2037
-rw-r--r--dom/base/nsDocumentWarningList.h14
-rw-r--r--dom/base/nsFocusManager.cpp3659
-rw-r--r--dom/base/nsFocusManager.h552
-rw-r--r--dom/base/nsFrameLoader.cpp3490
-rw-r--r--dom/base/nsFrameLoader.h400
-rw-r--r--dom/base/nsFrameMessageManager.cpp2281
-rw-r--r--dom/base/nsFrameMessageManager.h450
-rw-r--r--dom/base/nsGenConImageContent.cpp127
-rw-r--r--dom/base/nsGenericDOMDataNode.cpp1148
-rw-r--r--dom/base/nsGenericDOMDataNode.h333
-rw-r--r--dom/base/nsGkAtomList.h2484
-rw-r--r--dom/base/nsGkAtoms.cpp37
-rw-r--r--dom/base/nsGkAtoms.h35
-rw-r--r--dom/base/nsGlobalWindow.cpp15189
-rw-r--r--dom/base/nsGlobalWindow.h2141
-rw-r--r--dom/base/nsGlobalWindowCommands.cpp1264
-rw-r--r--dom/base/nsGlobalWindowCommands.h29
-rw-r--r--dom/base/nsHTMLContentSerializer.cpp622
-rw-r--r--dom/base/nsHTMLContentSerializer.h58
-rw-r--r--dom/base/nsHistory.cpp346
-rw-r--r--dom/base/nsHistory.h69
-rw-r--r--dom/base/nsHostObjectProtocolHandler.cpp1087
-rw-r--r--dom/base/nsHostObjectProtocolHandler.h153
-rw-r--r--dom/base/nsHostObjectURI.cpp292
-rw-r--r--dom/base/nsHostObjectURI.h83
-rw-r--r--dom/base/nsIAnimationObserver.h67
-rw-r--r--dom/base/nsIAttribute.h53
-rw-r--r--dom/base/nsIContent.h1067
-rw-r--r--dom/base/nsIContentInlines.h71
-rw-r--r--dom/base/nsIContentIterator.h72
-rw-r--r--dom/base/nsIContentPolicy.idl131
-rw-r--r--dom/base/nsIContentPolicyBase.idl381
-rw-r--r--dom/base/nsIContentSerializer.h76
-rw-r--r--dom/base/nsIDOMBlob.idl12
-rw-r--r--dom/base/nsIDOMClassInfo.h30
-rw-r--r--dom/base/nsIDOMDOMCursor.idl20
-rw-r--r--dom/base/nsIDOMDOMRequest.idl45
-rw-r--r--dom/base/nsIDOMDataChannel.idl37
-rw-r--r--dom/base/nsIDOMFileList.idl14
-rw-r--r--dom/base/nsIDOMFormData.idl22
-rw-r--r--dom/base/nsIDOMParser.idl102
-rw-r--r--dom/base/nsIDOMSerializer.idl53
-rw-r--r--dom/base/nsIDocument.h3461
-rw-r--r--dom/base/nsIDocumentEncoder.idl372
-rw-r--r--dom/base/nsIDocumentInlines.h60
-rw-r--r--dom/base/nsIDocumentObserver.h291
-rw-r--r--dom/base/nsIDroppedLinkHandler.idl80
-rw-r--r--dom/base/nsIFrameLoader.idl288
-rw-r--r--dom/base/nsIGlobalObject.cpp113
-rw-r--r--dom/base/nsIGlobalObject.h89
-rw-r--r--dom/base/nsIImageLoadingContent.idl193
-rw-r--r--dom/base/nsIMessageManager.idl550
-rw-r--r--dom/base/nsIMutationObserver.h482
-rw-r--r--dom/base/nsINode.cpp3111
-rw-r--r--dom/base/nsINode.h2339
-rw-r--r--dom/base/nsINodeList.h64
-rw-r--r--dom/base/nsIObjectLoadingContent.idl191
-rw-r--r--dom/base/nsIRemoteWindowContext.idl15
-rw-r--r--dom/base/nsIScriptChannel.idl68
-rw-r--r--dom/base/nsIScriptContext.h111
-rw-r--r--dom/base/nsIScriptElement.h329
-rw-r--r--dom/base/nsIScriptGlobalObject.h89
-rw-r--r--dom/base/nsIScriptLoaderObserver.idl47
-rw-r--r--dom/base/nsIScriptNameSpaceManager.h20
-rw-r--r--dom/base/nsIScriptObjectPrincipal.h33
-rw-r--r--dom/base/nsIScriptTimeoutHandler.h53
-rw-r--r--dom/base/nsISelection.idl170
-rw-r--r--dom/base/nsISelectionController.idl322
-rw-r--r--dom/base/nsISelectionDisplay.idl37
-rw-r--r--dom/base/nsISelectionListener.idl27
-rw-r--r--dom/base/nsISelectionPrivate.idl165
-rw-r--r--dom/base/nsISimpleContentPolicy.idl147
-rw-r--r--dom/base/nsISiteSpecificUserAgent.idl28
-rw-r--r--dom/base/nsISizeOfEventTarget.h40
-rw-r--r--dom/base/nsISlowScriptDebug.idl34
-rw-r--r--dom/base/nsIStyleSheetLinkingElement.h110
-rw-r--r--dom/base/nsITimeoutHandler.h31
-rw-r--r--dom/base/nsImageLoadingContent.cpp1608
-rw-r--r--dom/base/nsImageLoadingContent.h462
-rw-r--r--dom/base/nsInProcessTabChildGlobal.cpp354
-rw-r--r--dom/base/nsInProcessTabChildGlobal.h184
-rw-r--r--dom/base/nsJSEnvironment.cpp2812
-rw-r--r--dom/base/nsJSEnvironment.h217
-rw-r--r--dom/base/nsJSTimeoutHandler.cpp391
-rw-r--r--dom/base/nsJSUtils.cpp388
-rw-r--r--dom/base/nsJSUtils.h212
-rw-r--r--dom/base/nsLineBreaker.cpp498
-rw-r--r--dom/base/nsLineBreaker.h225
-rw-r--r--dom/base/nsMappedAttributeElement.cpp42
-rw-r--r--dom/base/nsMappedAttributeElement.h48
-rw-r--r--dom/base/nsMappedAttributes.cpp283
-rw-r--r--dom/base/nsMappedAttributes.h121
-rw-r--r--dom/base/nsMimeTypeArray.cpp255
-rw-r--r--dom/base/nsMimeTypeArray.h93
-rw-r--r--dom/base/nsNameSpaceManager.cpp266
-rw-r--r--dom/base/nsNameSpaceManager.h83
-rw-r--r--dom/base/nsNoDataProtocolContentPolicy.cpp86
-rw-r--r--dom/base/nsNoDataProtocolContentPolicy.h40
-rw-r--r--dom/base/nsNodeInfoManager.cpp433
-rw-r--r--dom/base/nsNodeInfoManager.h142
-rw-r--r--dom/base/nsNodeUtils.cpp696
-rw-r--r--dom/base/nsNodeUtils.h332
-rw-r--r--dom/base/nsObjectLoadingContent.cpp4079
-rw-r--r--dom/base/nsObjectLoadingContent.h721
-rw-r--r--dom/base/nsOpenURIInFrameParams.cpp58
-rw-r--r--dom/base/nsOpenURIInFrameParams.h29
-rw-r--r--dom/base/nsPIDOMWindow.h1007
-rw-r--r--dom/base/nsPIDOMWindowInlines.h132
-rw-r--r--dom/base/nsPIWindowRoot.h66
-rw-r--r--dom/base/nsPlainTextSerializer.cpp2034
-rw-r--r--dom/base/nsPlainTextSerializer.h245
-rw-r--r--dom/base/nsPluginArray.cpp553
-rw-r--r--dom/base/nsPluginArray.h115
-rw-r--r--dom/base/nsPropertyTable.cpp342
-rw-r--r--dom/base/nsPropertyTable.h199
-rw-r--r--dom/base/nsQueryContentEventResult.cpp256
-rw-r--r--dom/base/nsQueryContentEventResult.h44
-rw-r--r--dom/base/nsRange.cpp3579
-rw-r--r--dom/base/nsRange.h380
-rw-r--r--dom/base/nsReferencedElement.cpp247
-rw-r--r--dom/base/nsReferencedElement.h203
-rw-r--r--dom/base/nsSandboxFlags.h117
-rw-r--r--dom/base/nsScreen.cpp360
-rw-r--r--dom/base/nsScreen.h147
-rw-r--r--dom/base/nsScriptElement.cpp150
-rw-r--r--dom/base/nsScriptElement.h52
-rw-r--r--dom/base/nsScriptLoader.cpp3016
-rw-r--r--dom/base/nsScriptLoader.h715
-rw-r--r--dom/base/nsScriptNameSpaceManager.cpp436
-rw-r--r--dom/base/nsScriptNameSpaceManager.h177
-rw-r--r--dom/base/nsStructuredCloneContainer.cpp179
-rw-r--r--dom/base/nsStructuredCloneContainer.h40
-rw-r--r--dom/base/nsStubAnimationObserver.cpp9
-rw-r--r--dom/base/nsStubAnimationObserver.h17
-rw-r--r--dom/base/nsStubDocumentObserver.cpp20
-rw-r--r--dom/base/nsStubDocumentObserver.h35
-rw-r--r--dom/base/nsStubMutationObserver.cpp17
-rw-r--r--dom/base/nsStubMutationObserver.h35
-rw-r--r--dom/base/nsStyleLinkElement.cpp526
-rw-r--r--dom/base/nsStyleLinkElement.h146
-rw-r--r--dom/base/nsStyledElement.cpp197
-rw-r--r--dom/base/nsStyledElement.h86
-rw-r--r--dom/base/nsSyncLoadService.cpp394
-rw-r--r--dom/base/nsSyncLoadService.h65
-rw-r--r--dom/base/nsTextFragment.cpp470
-rw-r--r--dom/base/nsTextFragment.h242
-rw-r--r--dom/base/nsTextFragmentImpl.h33
-rw-r--r--dom/base/nsTextFragmentSSE2.cpp74
-rw-r--r--dom/base/nsTextNode.cpp296
-rw-r--r--dom/base/nsTextNode.h89
-rw-r--r--dom/base/nsTraversal.cpp80
-rw-r--r--dom/base/nsTraversal.h48
-rw-r--r--dom/base/nsTreeSanitizer.cpp1550
-rw-r--r--dom/base/nsTreeSanitizer.h227
-rw-r--r--dom/base/nsViewportInfo.cpp28
-rw-r--r--dom/base/nsViewportInfo.h106
-rw-r--r--dom/base/nsWindowMemoryReporter.cpp861
-rw-r--r--dom/base/nsWindowMemoryReporter.h263
-rw-r--r--dom/base/nsWindowRoot.cpp435
-rw-r--r--dom/base/nsWindowRoot.h124
-rw-r--r--dom/base/nsWrapperCache.cpp149
-rw-r--r--dom/base/nsWrapperCache.h402
-rw-r--r--dom/base/nsWrapperCacheInlines.h56
-rw-r--r--dom/base/nsXHTMLContentSerializer.cpp947
-rw-r--r--dom/base/nsXHTMLContentSerializer.h163
-rw-r--r--dom/base/nsXMLContentSerializer.cpp1828
-rw-r--r--dom/base/nsXMLContentSerializer.h406
-rw-r--r--dom/base/nsXMLNameSpaceMap.cpp110
-rw-r--r--dom/base/nsXMLNameSpaceMap.h75
-rw-r--r--dom/base/test/345339_iframe.html27
-rw-r--r--dom/base/test/Ahem.ttfbin0 -> 12480 bytes
-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/audioEndedDuringPlaying.webmbin0 -> 53840 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.ini28
-rw-r--r--dom/base/test/browser_bug1011748.js31
-rw-r--r--dom/base/test/browser_bug1058164.js109
-rw-r--r--dom/base/test/browser_bug1307747.js32
-rw-r--r--dom/base/test/browser_bug593387.js70
-rw-r--r--dom/base/test/browser_bug902350.js66
-rw-r--r--dom/base/test/browser_messagemanager_loadprocessscript.js114
-rw-r--r--dom/base/test/browser_messagemanager_targetframeloader.js31
-rw-r--r--dom/base/test/browser_messagemanager_unload.js102
-rw-r--r--dom/base/test/browser_pagehide_on_tab_close.js17
-rw-r--r--dom/base/test/browser_state_notifications.js189
-rw-r--r--dom/base/test/browser_use_counters.js305
-rw-r--r--dom/base/test/bug282547.sjs9
-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.js17
-rw-r--r--dom/base/test/bug419132.html22
-rw-r--r--dom/base/test/bug426308-redirect.sjs4
-rw-r--r--dom/base/test/bug435425.sjs24
-rw-r--r--dom/base/test/bug435425_redirect.sjs6
-rw-r--r--dom/base/test/bug444322.js0
-rw-r--r--dom/base/test/bug444322.txt0
-rw-r--r--dom/base/test/bug444546.sjs20
-rw-r--r--dom/base/test/bug455629-helper.svg6
-rw-r--r--dom/base/test/bug457746.sjs11
-rw-r--r--dom/base/test/bug461735-post-redirect.js3
-rw-r--r--dom/base/test/bug461735-redirect1.sjs4
-rw-r--r--dom/base/test/bug461735-redirect2.sjs4
-rw-r--r--dom/base/test/bug466080.sjs17
-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.sjs27
-rw-r--r--dom/base/test/bug482935.sjs12
-rw-r--r--dom/base/test/bug540854.sjs19
-rw-r--r--dom/base/test/bug578096LoadChromeScript.js16
-rw-r--r--dom/base/test/bug638112-response.txtbin0 -> 247 bytes
-rw-r--r--dom/base/test/bug638112.sjs26
-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.sjs299
-rw-r--r--dom/base/test/bug704320_counter.sjs94
-rw-r--r--dom/base/test/bug819051.sjs7
-rw-r--r--dom/base/test/chrome.ini27
-rw-r--r--dom/base/test/chrome/blockNoPlugins.xml7
-rw-r--r--dom/base/test/chrome/blockPluginHard.xml11
-rw-r--r--dom/base/test/chrome/bug418986-1.js73
-rw-r--r--dom/base/test/chrome/bug421622-referer.sjs8
-rw-r--r--dom/base/test/chrome/bug884693.sjs8
-rw-r--r--dom/base/test/chrome/chrome.ini76
-rw-r--r--dom/base/test/chrome/cpows_child.js382
-rw-r--r--dom/base/test/chrome/cpows_parent.xul493
-rw-r--r--dom/base/test/chrome/file_bug1139964.xul62
-rw-r--r--dom/base/test/chrome/file_bug1209621.xul79
-rw-r--r--dom/base/test/chrome/file_bug549682.xul226
-rw-r--r--dom/base/test/chrome/file_bug616841.xul63
-rw-r--r--dom/base/test/chrome/file_bug816340.xul70
-rw-r--r--dom/base/test/chrome/file_bug990812-1.xul64
-rw-r--r--dom/base/test/chrome/file_bug990812-2.xul59
-rw-r--r--dom/base/test/chrome/file_bug990812-3.xul71
-rw-r--r--dom/base/test/chrome/file_bug990812-4.xul68
-rw-r--r--dom/base/test/chrome/file_bug990812-5.xul77
-rw-r--r--dom/base/test/chrome/file_bug990812.xul58
-rw-r--r--dom/base/test/chrome/fileconstructor_file.pngbin0 -> 95 bytes
-rw-r--r--dom/base/test/chrome/frame_bug814638.xul15
-rw-r--r--dom/base/test/chrome/frame_registerElement_content.html5
-rw-r--r--dom/base/test/chrome/host_bug814638.xul9
-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/registerElement_ep.js8
-rw-r--r--dom/base/test/chrome/test_bug1063837.xul37
-rw-r--r--dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xul50
-rw-r--r--dom/base/test/chrome/test_bug1139964.xul33
-rw-r--r--dom/base/test/chrome/test_bug120684.xul80
-rw-r--r--dom/base/test/chrome/test_bug1209621.xul34
-rw-r--r--dom/base/test/chrome/test_bug1339722.html67
-rw-r--r--dom/base/test/chrome/test_bug206691.xul33
-rw-r--r--dom/base/test/chrome/test_bug289714.xul33
-rw-r--r--dom/base/test/chrome/test_bug339494.xul64
-rw-r--r--dom/base/test/chrome/test_bug357450.xul56
-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.xul26
-rw-r--r--dom/base/test/chrome/test_bug421622.xul35
-rw-r--r--dom/base/test/chrome/test_bug429785.xul61
-rw-r--r--dom/base/test/chrome/test_bug430050.xul50
-rw-r--r--dom/base/test/chrome/test_bug467123.xul35
-rw-r--r--dom/base/test/chrome/test_bug549682.xul33
-rw-r--r--dom/base/test/chrome/test_bug571390.xul42
-rw-r--r--dom/base/test/chrome/test_bug616841.xul31
-rw-r--r--dom/base/test/chrome/test_bug635835.xul37
-rw-r--r--dom/base/test/chrome/test_bug682305.html175
-rw-r--r--dom/base/test/chrome/test_bug683852.xul69
-rw-r--r--dom/base/test/chrome/test_bug752226-3.xul28
-rw-r--r--dom/base/test/chrome/test_bug752226-4.xul28
-rw-r--r--dom/base/test/chrome/test_bug765993.html61
-rw-r--r--dom/base/test/chrome/test_bug780199.xul51
-rw-r--r--dom/base/test/chrome/test_bug780529.xul40
-rw-r--r--dom/base/test/chrome/test_bug800386.xul68
-rw-r--r--dom/base/test/chrome/test_bug814638.xul64
-rw-r--r--dom/base/test/chrome/test_bug816340.xul31
-rw-r--r--dom/base/test/chrome/test_bug884693.xul67
-rw-r--r--dom/base/test/chrome/test_bug914381.html48
-rw-r--r--dom/base/test/chrome/test_bug990812.xul43
-rw-r--r--dom/base/test/chrome/test_cpows.xul33
-rw-r--r--dom/base/test/chrome/test_domparsing.xul144
-rw-r--r--dom/base/test/chrome/test_fileconstructor.xul72
-rw-r--r--dom/base/test/chrome/test_fileconstructor_tempfile.xul93
-rw-r--r--dom/base/test/chrome/test_groupedSHistory.xul32
-rw-r--r--dom/base/test/chrome/test_nsITextInputProcessor.xul30
-rw-r--r--dom/base/test/chrome/test_range_getClientRectsAndTexts.html60
-rw-r--r--dom/base/test/chrome/test_registerElement_content.xul55
-rw-r--r--dom/base/test/chrome/test_registerElement_ep.xul44
-rw-r--r--dom/base/test/chrome/test_swapFrameLoaders.xul25
-rw-r--r--dom/base/test/chrome/test_title.xul30
-rw-r--r--dom/base/test/chrome/test_windowroot.xul19
-rw-r--r--dom/base/test/chrome/title_window.xul198
-rw-r--r--dom/base/test/chrome/window_groupedSHistory.xul343
-rw-r--r--dom/base/test/chrome/window_nsITextInputProcessor.xul4027
-rw-r--r--dom/base/test/chrome/window_swapFrameLoaders.xul258
-rw-r--r--dom/base/test/copypaste.js412
-rw-r--r--dom/base/test/create_file_objects.js10
-rw-r--r--dom/base/test/delayedServerEvents.sjs42
-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_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/fake_plugin.tst1
-rw-r--r--dom/base/test/file_audioLoop.html2
-rw-r--r--dom/base/test/file_base_xbl.xml9
-rw-r--r--dom/base/test/file_blobURL_expiring.html4
-rw-r--r--dom/base/test/file_bug1008126_worker.js176
-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_bug1198095.js26
-rw-r--r--dom/base/test/file_bug1250148.sjs60
-rw-r--r--dom/base/test/file_bug1263696_frame_fail.html12
-rw-r--r--dom/base/test/file_bug1263696_frame_pass.html13
-rw-r--r--dom/base/test/file_bug1268962.sjs90
-rw-r--r--dom/base/test/file_bug1274806.html33
-rw-r--r--dom/base/test/file_bug28293.sjs5
-rw-r--r--dom/base/test/file_bug326337.xml1
-rw-r--r--dom/base/test/file_bug326337_inner.html40
-rw-r--r--dom/base/test/file_bug326337_outer.html15
-rw-r--r--dom/base/test/file_bug357450.js64
-rw-r--r--dom/base/test/file_bug416317.xhtml1476
-rw-r--r--dom/base/test/file_bug426646-1.html36
-rw-r--r--dom/base/test/file_bug426646-2.html64
-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.sjs23
-rw-r--r--dom/base/test/file_bug503481.sjs43
-rw-r--r--dom/base/test/file_bug503481b_inner.html62
-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.sjs6
-rw-r--r--dom/base/test/file_bug622088_inner.html38
-rw-r--r--dom/base/test/file_bug675121.sjs15
-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_common.js34
-rw-r--r--dom/base/test/file_bug704320_preload_noreuse.html33
-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_bug769117.html16
-rw-r--r--dom/base/test/file_bug782342.txt1
-rw-r--r--dom/base/test/file_bug787778.sjs8
-rw-r--r--dom/base/test/file_bug804395.jarbin0 -> 164 bytes
-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.js103
-rw-r--r--dom/base/test/file_change_policy_redirect.html10
-rw-r--r--dom/base/test/file_empty.html1
-rw-r--r--dom/base/test/file_explicit_user_agent.sjs8
-rw-r--r--dom/base/test/file_general_document.html10
-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_2_enthtml.html47
-rw-r--r--dom/base/test/file_htmlserializer_2_entw3c.html47
-rw-r--r--dom/base/test/file_htmlserializer_2_latin1.html34
-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_lock_orientation.html14
-rw-r--r--dom/base/test/file_messagemanager_unload.html6
-rw-r--r--dom/base/test/file_mozfiledataurl_audio.oggbin0 -> 135861 bytes
-rw-r--r--dom/base/test/file_mozfiledataurl_doc.html6
-rw-r--r--dom/base/test/file_mozfiledataurl_img.jpgbin0 -> 2711 bytes
-rw-r--r--dom/base/test/file_mozfiledataurl_inner.html76
-rw-r--r--dom/base/test/file_mozfiledataurl_text.txt1
-rw-r--r--dom/base/test/file_navigator_resolve_identity_xrays.xul30
-rw-r--r--dom/base/test/file_nonascii_blob_url.html24
-rw-r--r--dom/base/test/file_pluginAudio.html21
-rw-r--r--dom/base/test/file_pluginAudioNonAutoStart.html25
-rw-r--r--dom/base/test/file_receiveMessage.html13
-rw-r--r--dom/base/test/file_record_orientation.html16
-rw-r--r--dom/base/test/file_restrictedEventSource.sjs48
-rw-r--r--dom/base/test/file_setname.html8
-rw-r--r--dom/base/test/file_simplecontentpolicy.js73
-rw-r--r--dom/base/test/file_timer_flood.html19
-rw-r--r--dom/base/test/file_use_counter_outer.html17
-rw-r--r--dom/base/test/file_use_counter_svg_currentScale.svg17
-rw-r--r--dom/base/test/file_use_counter_svg_fill_pattern.svg15
-rw-r--r--dom/base/test/file_use_counter_svg_fill_pattern_data.svg15
-rw-r--r--dom/base/test/file_use_counter_svg_fill_pattern_definition.svg14
-rw-r--r--dom/base/test/file_use_counter_svg_fill_pattern_internal.svg23
-rw-r--r--dom/base/test/file_use_counter_svg_getElementById.svg22
-rw-r--r--dom/base/test/file_webaudioLoop.html44
-rw-r--r--dom/base/test/file_webaudioLoop2.html15
-rw-r--r--dom/base/test/file_websocket_basic_wsh.py29
-rw-r--r--dom/base/test/file_websocket_hello_wsh.py10
-rw-r--r--dom/base/test/file_websocket_http_resource.txt1
-rw-r--r--dom/base/test/file_websocket_permessage_deflate_disabled_wsh.py17
-rw-r--r--dom/base/test/file_websocket_permessage_deflate_params_wsh.py23
-rw-r--r--dom/base/test/file_websocket_permessage_deflate_rejected_wsh.py23
-rw-r--r--dom/base/test/file_websocket_permessage_deflate_wsh.py22
-rw-r--r--dom/base/test/file_websocket_wsh.py159
-rw-r--r--dom/base/test/file_x-frame-options_main.html44
-rw-r--r--dom/base/test/file_x-frame-options_page.sjs60
-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/fileapi_chromeScript.js29
-rw-r--r--dom/base/test/fileutils.js254
-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/gtest/TestNativeXMLHttpRequest.cpp58
-rw-r--r--dom/base/test/gtest/TestParserDialogOptions.cpp138
-rw-r--r--dom/base/test/gtest/TestPlainTextSerializer.cpp231
-rw-r--r--dom/base/test/gtest/moz.build19
-rw-r--r--dom/base/test/iframe_bug962251.html26
-rw-r--r--dom/base/test/iframe_bug976673.html25
-rw-r--r--dom/base/test/iframe_main_bug1022229.html40
-rw-r--r--dom/base/test/iframe_postMessage_solidus.html15
-rw-r--r--dom/base/test/iframe_postMessages.html10
-rw-r--r--dom/base/test/iframe_sandbox_bug1022229.html13
-rw-r--r--dom/base/test/iframe_webSocket_sandbox.html65
-rw-r--r--dom/base/test/img_referrer_testserver.sjs298
-rw-r--r--dom/base/test/intersectionobserver_iframe.html23
-rw-r--r--dom/base/test/intersectionobserver_window.html34
-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/chrome.ini51
-rw-r--r--dom/base/test/jsmodules/iframe_extractIntroType.html14
-rw-r--r--dom/base/test/jsmodules/mochitest.ini5
-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.js7
-rw-r--r--dom/base/test/jsmodules/module_cyclic2.js7
-rw-r--r--dom/base/test/jsmodules/module_cyclic3.js7
-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.build13
-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_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_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_moduleScriptsNotRun.html19
-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.html23
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorAsync.html23
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorInline.html27
-rw-r--r--dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html27
-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/mochitest.ini820
-rw-r--r--dom/base/test/moz.build37
-rw-r--r--dom/base/test/mozbrowser_api_utils.js72
-rw-r--r--dom/base/test/mutationobserver_dialog.html62
-rw-r--r--dom/base/test/noaudio.webmbin0 -> 105755 bytes
-rw-r--r--dom/base/test/orientationcommon.js21
-rw-r--r--dom/base/test/referrerHelper.js286
-rw-r--r--dom/base/test/referrer_change_server.sjs139
-rw-r--r--dom/base/test/referrer_helper.js102
-rw-r--r--dom/base/test/referrer_testserver.sjs391
-rw-r--r--dom/base/test/reftest/mixed-bmp-png.icobin0 -> 17542 bytes
-rw-r--r--dom/base/test/reftest/reftest-stylo.list2
-rw-r--r--dom/base/test/reftest/reftest.list1
-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/script-1_bug597345.sjs16
-rw-r--r--dom/base/test/script-2_bug597345.js1
-rw-r--r--dom/base/test/script_bug1238440.js31
-rw-r--r--dom/base/test/script_bug602838.sjs37
-rw-r--r--dom/base/test/script_postmessages_fileList.js25
-rw-r--r--dom/base/test/send_gzip_content.sjs48
-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.html125
-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_anonymousContent_api.html56
-rw-r--r--dom/base/test/test_anonymousContent_append_after_reflow.html40
-rw-r--r--dom/base/test/test_anonymousContent_canvas.html57
-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_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.xul30
-rw-r--r--dom/base/test/test_applet_alternate_content.html42
-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_audioNotification.html71
-rw-r--r--dom/base/test/test_audioNotificationSilent_audioFile.html73
-rw-r--r--dom/base/test/test_audioNotificationSilent_webAudio.html103
-rw-r--r--dom/base/test/test_audioNotificationStopOnNavigation.html71
-rw-r--r--dom/base/test/test_audioNotificationStream.html71
-rw-r--r--dom/base/test/test_audioNotificationWithEarlyPlay.html73
-rw-r--r--dom/base/test/test_audioWindowUtils.html107
-rw-r--r--dom/base/test/test_base.xhtml55
-rw-r--r--dom/base/test/test_blobURL_expiring.html47
-rw-r--r--dom/base/test/test_blob_fragment_and_query.html59
-rw-r--r--dom/base/test/test_blobconstructor.html246
-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.html47
-rw-r--r--dom/base/test/test_bug1025933.html37
-rw-r--r--dom/base/test/test_bug1037687.html63
-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_bug1081686.html71
-rw-r--r--dom/base/test/test_bug1091883.html89
-rw-r--r--dom/base/test/test_bug1101364.html73
-rw-r--r--dom/base/test/test_bug1118689.html57
-rw-r--r--dom/base/test/test_bug1126851.html44
-rw-r--r--dom/base/test/test_bug116083.html103
-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.html23
-rw-r--r--dom/base/test/test_bug1198095.html71
-rw-r--r--dom/base/test/test_bug1238440.html88
-rw-r--r--dom/base/test/test_bug1250148.html52
-rw-r--r--dom/base/test/test_bug1259588.html13
-rw-r--r--dom/base/test/test_bug1263696.html53
-rw-r--r--dom/base/test/test_bug1268962.html105
-rw-r--r--dom/base/test/test_bug1274806.html31
-rw-r--r--dom/base/test/test_bug1281963.html68
-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_bug166235.html160
-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.xhtml53
-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.html69
-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_bug330925.xhtml74
-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.html666
-rw-r--r--dom/base/test/test_bug338679.html78
-rw-r--r--dom/base/test/test_bug339494.html61
-rw-r--r--dom/base/test/test_bug339494.xhtml60
-rw-r--r--dom/base/test/test_bug340571.html28
-rw-r--r--dom/base/test/test_bug343596.html51
-rw-r--r--dom/base/test/test_bug345339.html85
-rw-r--r--dom/base/test/test_bug346485.html77
-rw-r--r--dom/base/test/test_bug352728.html139
-rw-r--r--dom/base/test/test_bug352728.xhtml187
-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.xhtml40
-rw-r--r--dom/base/test/test_bug357450_svg.xhtml47
-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.html187
-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.html36
-rw-r--r--dom/base/test/test_bug395915.html43
-rw-r--r--dom/base/test/test_bug397234.html37
-rw-r--r--dom/base/test/test_bug398243.html56
-rw-r--r--dom/base/test/test_bug401662.html48
-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.html65
-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.html85
-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.html52
-rw-r--r--dom/base/test/test_bug418214.html101
-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.xhtml74
-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.html320
-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_bug429157.html54
-rw-r--r--dom/base/test/test_bug431082.html51
-rw-r--r--dom/base/test/test_bug431701.html120
-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.html432
-rw-r--r--dom/base/test/test_bug444030.xhtml50
-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.html142
-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.html125
-rw-r--r--dom/base/test/test_bug466409.html39
-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.html299
-rw-r--r--dom/base/test/test_bug482935.html68
-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.js72
-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.html95
-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_bug505783.html33
-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.html37
-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.html119
-rw-r--r--dom/base/test/test_bug544642.html44
-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.html83
-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.html54
-rw-r--r--dom/base/test/test_bug564047.html31
-rw-r--r--dom/base/test/test_bug564863.xhtml359
-rw-r--r--dom/base/test/test_bug567350.html24
-rw-r--r--dom/base/test/test_bug574596.html83
-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.html336
-rw-r--r--dom/base/test/test_bug590812.html36
-rw-r--r--dom/base/test/test_bug590870.html37
-rw-r--r--dom/base/test/test_bug592366.html59
-rw-r--r--dom/base/test/test_bug592829.html39
-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_bug614583.html261
-rw-r--r--dom/base/test/test_bug622088.html96
-rw-r--r--dom/base/test/test_bug622117.html43
-rw-r--r--dom/base/test/test_bug622246.html43
-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.html31
-rw-r--r--dom/base/test/test_bug650776.html105
-rw-r--r--dom/base/test/test_bug650784.html37
-rw-r--r--dom/base/test/test_bug656283.html59
-rw-r--r--dom/base/test/test_bug664916.html39
-rw-r--r--dom/base/test/test_bug666604.html142
-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.html56
-rw-r--r--dom/base/test/test_bug698384.html62
-rw-r--r--dom/base/test/test_bug704063.html56
-rw-r--r--dom/base/test/test_bug704320.html74
-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.html145
-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_bug715041.xul815
-rw-r--r--dom/base/test/test_bug715041_removal.xul841
-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_bug793311.html35
-rw-r--r--dom/base/test/test_bug804395.html74
-rw-r--r--dom/base/test/test_bug809003.html47
-rw-r--r--dom/base/test/test_bug810494.html48
-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_bug840098.html36
-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_bug890580.html58
-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_bug895239.html123
-rw-r--r--dom/base/test/test_bug895974.html69
-rw-r--r--dom/base/test/test_bug902847.html58
-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.html258
-rw-r--r--dom/base/test/test_bug976673.html107
-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.html123
-rw-r--r--dom/base/test/test_change_policy.html129
-rw-r--r--dom/base/test/test_classList.html426
-rw-r--r--dom/base/test/test_clearTimeoutIntervalNoArg.html14
-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_copyimage.html94
-rw-r--r--dom/base/test/test_copypaste.html119
-rw-r--r--dom/base/test/test_copypaste.xhtml108
-rw-r--r--dom/base/test/test_copypaste.xul64
-rw-r--r--dom/base/test/test_createHTMLDocument.html52
-rw-r--r--dom/base/test/test_declare_stylesheet_obsolete.html94
-rw-r--r--dom/base/test/test_dialogArguments.html31
-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_register.html27
-rw-r--r--dom/base/test/test_domcursor.html140
-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.html229
-rw-r--r--dom/base/test/test_domrequesthelper.xul552
-rw-r--r--dom/base/test/test_domwindowutils.html85
-rw-r--r--dom/base/test/test_e4x_for_each.html55
-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_encodeToStringWithMaxLength.html64
-rw-r--r--dom/base/test/test_error.html44
-rw-r--r--dom/base/test/test_explicit_user_agent.html65
-rw-r--r--dom/base/test/test_file_from_blob.html111
-rw-r--r--dom/base/test/test_file_negative_date.html39
-rw-r--r--dom/base/test/test_fileapi.html479
-rw-r--r--dom/base/test/test_fileapi_slice.html167
-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_getTranslationNodes.html227
-rw-r--r--dom/base/test/test_getTranslationNodes_limit.html33
-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.html196
-rw-r--r--dom/base/test/test_htmlcopyencoder.xhtml180
-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_img_referrer.html189
-rw-r--r--dom/base/test/test_innersize_scrollport.html48
-rw-r--r--dom/base/test/test_integer_attr_with_leading_zero.html64
-rw-r--r--dom/base/test/test_intersectionobservers.html1214
-rw-r--r--dom/base/test/test_ipc_messagemanager_blob.html143
-rw-r--r--dom/base/test/test_link_prefetch.html220
-rw-r--r--dom/base/test/test_link_stylesheet.html221
-rw-r--r--dom/base/test/test_messagePort.html115
-rw-r--r--dom/base/test/test_messagemanager_principal.html95
-rw-r--r--dom/base/test/test_messagemanager_send_principal.html131
-rw-r--r--dom/base/test/test_messagemanager_targetchain.html126
-rw-r--r--dom/base/test/test_meta_viewport0.html81
-rw-r--r--dom/base/test/test_meta_viewport1.html77
-rw-r--r--dom/base/test/test_meta_viewport2.html77
-rw-r--r--dom/base/test/test_meta_viewport3.html79
-rw-r--r--dom/base/test/test_meta_viewport4.html78
-rw-r--r--dom/base/test/test_meta_viewport5.html54
-rw-r--r--dom/base/test/test_meta_viewport6.html83
-rw-r--r--dom/base/test/test_meta_viewport7.html114
-rw-r--r--dom/base/test/test_mozMatchesSelector.html14
-rw-r--r--dom/base/test/test_mozbrowser_apis_allowed.html46
-rw-r--r--dom/base/test/test_mozbrowser_apis_blocked.html38
-rw-r--r--dom/base/test/test_mozfiledataurl.html225
-rw-r--r--dom/base/test/test_mutationobserver_anonymous.html265
-rw-r--r--dom/base/test/test_mutationobservers.html935
-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_hardwareConcurrency.html24
-rw-r--r--dom/base/test/test_navigator_language.html212
-rw-r--r--dom/base/test/test_navigator_resolve_identity_xrays.xul42
-rw-r--r--dom/base/test/test_noAudioNotification.html77
-rw-r--r--dom/base/test/test_noAudioNotificationOnMutedElement.html129
-rw-r--r--dom/base/test/test_noAudioNotificationOnMutedOrVolume0Element.html162
-rw-r--r--dom/base/test/test_noAudioNotificationOnVolume0Element.html129
-rw-r--r--dom/base/test/test_noWebAudioNotification.html62
-rw-r--r--dom/base/test/test_nodelist_holes.html42
-rw-r--r--dom/base/test/test_nonascii_blob_url.html30
-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_orientation_alternate.html27
-rw-r--r--dom/base/test/test_orientation_frame.html37
-rw-r--r--dom/base/test/test_orientation_frame_lock.html46
-rw-r--r--dom/base/test/test_orientation_sandbox_no_lock.html36
-rw-r--r--dom/base/test/test_pluginAudioNotification.html121
-rw-r--r--dom/base/test/test_pluginMutedBeforePlay.html76
-rw-r--r--dom/base/test/test_plugin_freezing.html68
-rw-r--r--dom/base/test/test_postMessage_originAttributes.html60
-rw-r--r--dom/base/test/test_postMessage_solidus.html93
-rw-r--r--dom/base/test/test_postMessages.html654
-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_range_bounds.html288
-rw-r--r--dom/base/test/test_reentrant_flush.html61
-rw-r--r--dom/base/test/test_referrer_redirect.html72
-rw-r--r--dom/base/test/test_root_iframe.html27
-rw-r--r--dom/base/test/test_sandboxed_blob_uri.html24
-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_sendQueryContentAndSelectionSetEvent.html250
-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.html53
-rw-r--r--dom/base/test/test_setting_opener.html125
-rw-r--r--dom/base/test/test_simplecontentpolicy.html149
-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_timer_flood.html116
-rw-r--r--dom/base/test/test_title.html54
-rw-r--r--dom/base/test/test_treewalker_nextsibling.xml97
-rw-r--r--dom/base/test/test_user_select.html341
-rw-r--r--dom/base/test/test_viewport_scroll.html89
-rw-r--r--dom/base/test/test_viewsource_forbidden_in_object.html74
-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.html92
-rw-r--r--dom/base/test/test_webSocket_sandbox.html34
-rw-r--r--dom/base/test/test_webaudioNotification.html81
-rw-r--r--dom/base/test/test_webaudioNotificationStopOnNavigation.html71
-rw-r--r--dom/base/test/test_websocket1.html42
-rw-r--r--dom/base/test/test_websocket2.html44
-rw-r--r--dom/base/test/test_websocket3.html44
-rw-r--r--dom/base/test/test_websocket4.html42
-rw-r--r--dom/base/test/test_websocket5.html40
-rw-r--r--dom/base/test/test_websocket_basic.html255
-rw-r--r--dom/base/test/test_websocket_frame.html166
-rw-r--r--dom/base/test/test_websocket_hello.html49
-rw-r--r--dom/base/test/test_websocket_permessage_deflate.html110
-rw-r--r--dom/base/test/test_window_constructor.html36
-rw-r--r--dom/base/test/test_window_cross_origin_props.html101
-rw-r--r--dom/base/test/test_window_define_nonconfigurable.html49
-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_indexing.html139
-rw-r--r--dom/base/test/test_window_named_frame_enumeration.html96
-rw-r--r--dom/base/test/test_window_orientation.html33
-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.html156
-rw-r--r--dom/base/test/test_xbl_userdata.xhtml56
-rw-r--r--dom/base/test/test_youtube_flash_embed.html36
-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.xml15
-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.js40
-rw-r--r--dom/base/test/unit/head_xml.js156
-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.xul45
-rw-r--r--dom/base/test/unit/test_bloburi.js33
-rw-r--r--dom/base/test/unit/test_bug553888.js60
-rw-r--r--dom/base/test/unit/test_bug737966.js20
-rw-r--r--dom/base/test/unit/test_cancelPrefetch.js134
-rw-r--r--dom/base/test/unit/test_chromeutils_base64.js105
-rw-r--r--dom/base/test/unit/test_delete_range.xml125
-rw-r--r--dom/base/test/unit/test_error_codes.js68
-rw-r--r--dom/base/test/unit/test_isequalnode.js435
-rw-r--r--dom/base/test/unit/test_nodelist.js394
-rw-r--r--dom/base/test/unit/test_normalize.js109
-rw-r--r--dom/base/test/unit/test_range.js465
-rw-r--r--dom/base/test/unit/test_thirdpartyutil.js96
-rw-r--r--dom/base/test/unit/test_treewalker.js26
-rw-r--r--dom/base/test/unit/test_xhr_document.js42
-rw-r--r--dom/base/test/unit/test_xhr_origin_attributes.js50
-rw-r--r--dom/base/test/unit/test_xhr_standalone.js21
-rw-r--r--dom/base/test/unit/test_xml_parser.js48
-rw-r--r--dom/base/test/unit/test_xml_serializer.js374
-rw-r--r--dom/base/test/unit/test_xmlserializer.js112
-rw-r--r--dom/base/test/unit/xpcshell.ini55
-rw-r--r--dom/base/test/unit_ipc/test_bug553888_wrap.js4
-rw-r--r--dom/base/test/unit_ipc/test_xhr_document_ipc.js3
-rw-r--r--dom/base/test/unit_ipc/xpcshell.ini10
-rw-r--r--dom/base/test/variable_style_sheet.sjs19
-rw-r--r--dom/base/test/viewport_helpers.js3
-rw-r--r--dom/base/test/w3element_traversal.svg70
-rw-r--r--dom/base/test/websocket_helpers.js66
-rw-r--r--dom/base/test/websocket_hybi/file_binary-frames_wsh.py18
-rw-r--r--dom/base/test/websocket_hybi/file_check-binary-messages_wsh.py21
-rw-r--r--dom/base/test/websocket_hybi/mochitest.ini13
-rw-r--r--dom/base/test/websocket_hybi/test_receive-arraybuffer.html97
-rw-r--r--dom/base/test/websocket_hybi/test_receive-blob.html110
-rw-r--r--dom/base/test/websocket_hybi/test_send-arraybuffer.html82
-rw-r--r--dom/base/test/websocket_hybi/test_send-blob.html72
-rw-r--r--dom/base/test/websocket_tests.js1244
-rw-r--r--dom/base/test/wholeTexty-helper.xml6
-rw-r--r--dom/base/test/worker_postMessages.js33
-rw-r--r--dom/base/usecounters.py71
1607 files changed, 280277 insertions, 0 deletions
diff --git a/dom/base/AnonymousContent.cpp b/dom/base/AnonymousContent.cpp
new file mode 100644
index 000000000..1df36b048
--- /dev/null
+++ b/dom/base/AnonymousContent.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/Element.h"
+#include "mozilla/dom/AnonymousContentBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsIFrame.h"
+#include "nsStyledElement.h"
+#include "HTMLCanvasElement.h"
+
+namespace mozilla {
+namespace dom {
+
+// Ref counting and cycle collection
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AnonymousContent, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AnonymousContent, Release)
+NS_IMPL_CYCLE_COLLECTION(AnonymousContent, mContentNode)
+
+AnonymousContent::AnonymousContent(Element* aContentNode) :
+ mContentNode(aContentNode)
+{}
+
+AnonymousContent::~AnonymousContent()
+{
+}
+
+Element*
+AnonymousContent::GetContentNode()
+{
+ return mContentNode;
+}
+
+void
+AnonymousContent::SetContentNode(Element* aContentNode)
+{
+ mContentNode = aContentNode;
+}
+
+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,
+ ErrorResult& aRv)
+{
+ Element* element = GetElementById(aElementId);
+ if (!element) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ element->SetAttribute(aName, aValue, 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.
+ nsCOMPtr<nsIAtom> elementId = NS_Atomize(aElementId);
+ for (nsIContent* node = mContentNode; node;
+ node = node->GetNextNode(mContentNode)) {
+ if (!node->IsElement()) {
+ continue;
+ }
+ nsIAtom* 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 AnonymousContentBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/AnonymousContent.h b/dom/base/AnonymousContent.h
new file mode 100644
index 000000000..fd3b59c44
--- /dev/null
+++ b/dom/base/AnonymousContent.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/dom/Element.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsICSSDeclaration.h"
+#include "nsIDocument.h"
+
+namespace mozilla {
+namespace dom {
+
+class Element;
+class UnrestrictedDoubleOrAnonymousKeyframeAnimationOptions;
+
+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(Element* aContentNode);
+ Element* GetContentNode();
+ Element* GetElementById(const nsAString& aElementId);
+ void SetContentNode(Element* aContentNode);
+ 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,
+ 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);
+
+private:
+ ~AnonymousContent();
+ nsCOMPtr<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 000000000..6eb3b49fd
--- /dev/null
+++ b/dom/base/Attr.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMAttr 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 "nsContentCreatorFunctions.h"
+#include "nsError.h"
+#include "nsUnicharUtils.h"
+#include "nsDOMString.h"
+#include "nsIContentInlines.h"
+#include "nsIDocument.h"
+#include "nsGkAtoms.h"
+#include "nsCOMArray.h"
+#include "nsNameSpaceManager.h"
+#include "nsNodeUtils.h"
+#include "nsTextNode.h"
+#include "mozAutoDocUpdate.h"
+#include "nsWrapperCacheInlines.h"
+
+nsIAttribute::nsIAttribute(nsDOMAttributeMap* aAttrMap,
+ already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+: nsINode(aNodeInfo), mAttrMap(aAttrMap)
+{
+}
+
+nsIAttribute::~nsIAttribute()
+{
+}
+
+namespace mozilla {
+namespace dom {
+
+//----------------------------------------------------------------------
+bool Attr::sInitialized;
+
+Attr::Attr(nsDOMAttributeMap *aAttrMap,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ const nsAString &aValue)
+ : nsIAttribute(aAttrMap, aNodeInfo), mValue(aValue)
+{
+ MOZ_ASSERT(mNodeInfo, "We must get a nodeinfo here!");
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::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_CLASS(Attr)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Attr)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+
+ 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_TRACE_WRAPPERCACHE(Attr)
+
+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->IsBlack()) {
+ 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->IsBlackAndDoesNotNeedTracing(static_cast<nsIAttribute*>(tmp));
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Attr)
+ return tmp->IsBlack();
+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, nsIDOMAttr, nsIAttribute, nsIDOMNode,
+ nsIDOMEventTarget, 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(Attr,
+ nsNodeUtils::LastRelease(this))
+
+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(nsIDocument* aDocument)
+{
+ NS_ASSERTION(aDocument, "Missing document");
+
+ nsIDocument *doc = OwnerDoc();
+ NS_ASSERTION(doc != aDocument, "bad call to Attr::SetOwnerDocument");
+ doc->DeleteAllPropertiesFor(this);
+
+ RefPtr<mozilla::dom::NodeInfo> newNodeInfo;
+ newNodeInfo = aDocument->NodeInfoManager()->
+ GetNodeInfo(mNodeInfo->NameAtom(), mNodeInfo->GetPrefixAtom(),
+ mNodeInfo->NamespaceID(),
+ nsIDOMNode::ATTRIBUTE_NODE);
+ NS_ASSERTION(newNodeInfo, "GetNodeInfo lies");
+ mNodeInfo.swap(newNodeInfo);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Attr::GetName(nsAString& aName)
+{
+ aName = NodeName();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Attr::GetValue(nsAString& aValue)
+{
+ Element* element = GetElement();
+ if (element) {
+ nsCOMPtr<nsIAtom> nameAtom = mNodeInfo->NameAtom();
+ element->GetAttr(mNodeInfo->NamespaceID(), nameAtom, aValue);
+ }
+ else {
+ aValue = mValue;
+ }
+
+ return NS_OK;
+}
+
+void
+Attr::SetValue(const nsAString& aValue, ErrorResult& aRv)
+{
+ Element* element = GetElement();
+ if (!element) {
+ mValue = aValue;
+ return;
+ }
+
+ nsCOMPtr<nsIAtom> nameAtom = mNodeInfo->NameAtom();
+ aRv = element->SetAttr(mNodeInfo->NamespaceID(),
+ nameAtom,
+ mNodeInfo->GetPrefixAtom(),
+ aValue,
+ true);
+}
+
+NS_IMETHODIMP
+Attr::SetValue(const nsAString& aValue)
+{
+ ErrorResult rv;
+ SetValue(aValue, rv);
+ return rv.StealNSResult();
+}
+
+bool
+Attr::Specified() const
+{
+ return true;
+}
+
+NS_IMETHODIMP
+Attr::GetSpecified(bool* aSpecified)
+{
+ NS_ENSURE_ARG_POINTER(aSpecified);
+ *aSpecified = Specified();
+ return NS_OK;
+}
+
+Element*
+Attr::GetOwnerElement(ErrorResult& aRv)
+{
+ return GetElement();
+}
+
+NS_IMETHODIMP
+Attr::GetOwnerElement(nsIDOMElement** aOwnerElement)
+{
+ NS_ENSURE_ARG_POINTER(aOwnerElement);
+
+ Element* element = GetElement();
+ if (element) {
+ return CallQueryInterface(element, aOwnerElement);
+ }
+
+ *aOwnerElement = nullptr;
+
+ return NS_OK;
+}
+
+void
+Attr::GetNodeValueInternal(nsAString& aNodeValue)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eNodeValue);
+
+ GetValue(aNodeValue);
+}
+
+void
+Attr::SetNodeValueInternal(const nsAString& aNodeValue, ErrorResult& aError)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eNodeValue);
+
+ aError = SetValue(aNodeValue);
+}
+
+nsresult
+Attr::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ nsAutoString value;
+ const_cast<Attr*>(this)->GetValue(value);
+
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ *aResult = new Attr(nullptr, ni.forget(), value);
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIURI>
+Attr::GetBaseURI(bool aTryUseXHRDocBaseURI) const
+{
+ Element* parent = GetElement();
+
+ return parent ? parent->GetBaseURI(aTryUseXHRDocBaseURI) : nullptr;
+}
+
+void
+Attr::GetTextContentInternal(nsAString& aTextContent,
+ ErrorResult& aError)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eTextContent);
+
+ GetValue(aTextContent);
+}
+
+void
+Attr::SetTextContentInternal(const nsAString& aTextContent,
+ ErrorResult& aError)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eTextContent);
+
+ SetNodeValueInternal(aTextContent, aError);
+}
+
+NS_IMETHODIMP
+Attr::GetIsId(bool* aReturn)
+{
+ *aReturn = mNodeInfo->Equals(nsGkAtoms::id, kNameSpaceID_None);
+ return NS_OK;
+}
+
+bool
+Attr::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~eATTRIBUTE);
+}
+
+uint32_t
+Attr::GetChildCount() const
+{
+ return 0;
+}
+
+nsIContent *
+Attr::GetChildAt(uint32_t aIndex) const
+{
+ return nullptr;
+}
+
+nsIContent * const *
+Attr::GetChildArray(uint32_t* aChildCount) const
+{
+ *aChildCount = 0;
+ return nullptr;
+}
+
+int32_t
+Attr::IndexOf(const nsINode* aPossibleChild) const
+{
+ return -1;
+}
+
+nsresult
+Attr::InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+Attr::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+}
+
+nsresult
+Attr::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mCanHandle = true;
+ return NS_OK;
+}
+
+void
+Attr::Initialize()
+{
+ sInitialized = true;
+}
+
+void
+Attr::Shutdown()
+{
+ sInitialized = false;
+}
+
+JSObject*
+Attr::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return AttrBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/Attr.h b/dom/base/Attr.h
new file mode 100644
index 000000000..3f80030d2
--- /dev/null
+++ b/dom/base/Attr.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/. */
+
+/*
+ * Implementation of DOM Core's nsIDOMAttr node.
+ */
+
+#ifndef mozilla_dom_Attr_h
+#define mozilla_dom_Attr_h
+
+#include "mozilla/Attributes.h"
+#include "nsIAttribute.h"
+#include "nsIDOMAttr.h"
+#include "nsIDOMText.h"
+#include "nsIDOMNodeList.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsStubMutationObserver.h"
+
+class nsIDocument;
+
+namespace mozilla {
+class EventChainPreVisitor;
+namespace dom {
+
+// Attribute helper class used to wrap up an attribute with a dom
+// object that implements nsIDOMAttr and nsIDOMNode
+class Attr final : public nsIAttribute,
+ public nsIDOMAttr
+{
+ virtual ~Attr() {}
+
+public:
+ Attr(nsDOMAttributeMap* aAttrMap,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ const nsAString& aValue);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsIDOMNode interface
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+ virtual void GetTextContentInternal(nsAString& aTextContent,
+ ErrorResult& aError) override;
+ virtual void SetTextContentInternal(const nsAString& aTextContent,
+ ErrorResult& aError) override;
+ virtual void GetNodeValueInternal(nsAString& aNodeValue) override;
+ virtual void SetNodeValueInternal(const nsAString& aNodeValue,
+ ErrorResult& aError) override;
+
+ // nsIDOMAttr interface
+ NS_DECL_NSIDOMATTR
+
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
+
+ // nsIAttribute interface
+ void SetMap(nsDOMAttributeMap *aMap) override;
+ Element* GetElement() const;
+ nsresult SetOwnerDocument(nsIDocument* aDocument) override;
+
+ // nsINode interface
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+ virtual uint32_t GetChildCount() const override;
+ virtual nsIContent *GetChildAt(uint32_t aIndex) const override;
+ virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const override;
+ virtual int32_t IndexOf(const nsINode* aPossibleChild) const override;
+ virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify) override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+ virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
+
+ static void Initialize();
+ static void Shutdown();
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Attr,
+ nsIAttribute)
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+ // WebIDL
+ virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // XPCOM GetName() is OK
+ // XPCOM GetValue() is OK
+
+ void SetValue(const nsAString& aValue, ErrorResult& aRv);
+
+ bool Specified() const;
+
+ // XPCOM GetNamespaceURI() is OK
+ // XPCOM GetPrefix() is OK
+ // XPCOM GetLocalName() is OK
+
+ Element* GetOwnerElement(ErrorResult& aRv);
+
+protected:
+ virtual Element* GetNameSpaceElement() override
+ {
+ return GetElement();
+ }
+
+ static bool sInitialized;
+
+private:
+ nsString mValue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_Attr_h */
diff --git a/dom/base/AutocompleteFieldList.h b/dom/base/AutocompleteFieldList.h
new file mode 100644
index 000000000..a258d2213
--- /dev/null
+++ b/dom/base/AutocompleteFieldList.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/. */
+
+/*
+ * 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_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
+
+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")
+
+// 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")
+
+// 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")
+
+// 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_FIELD_NAME
+#undef AUTOCOMPLETE_FIELD_NAME
+#undef DEFINED_AUTOCOMPLETE_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 000000000..891257d5d
--- /dev/null
+++ b/dom/base/BarProps.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 "mozilla/dom/BarProps.h"
+#include "mozilla/dom/BarPropBinding.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsIDocShell.h"
+#include "nsIScrollable.h"
+#include "nsIWebBrowserChrome.h"
+
+namespace mozilla {
+namespace dom {
+
+//
+// Basic (virtual) BarProp class implementation
+//
+BarProp::BarProp(nsGlobalWindow* aWindow)
+ : mDOMWindow(aWindow)
+{
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+}
+
+BarProp::~BarProp()
+{
+}
+
+nsPIDOMWindowInner*
+BarProp::GetParentObject() const
+{
+ return mDOMWindow->AsInner();
+}
+
+JSObject*
+BarProp::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return BarPropBinding::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::GetVisibleByFlag(uint32_t aChromeFlag, ErrorResult& aRv)
+{
+ 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,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetBrowserChrome();
+ NS_ENSURE_TRUE_VOID(browserChrome);
+
+ if (!nsContentUtils::IsCallerChrome()) {
+ 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();
+}
+
+//
+// MenubarProp class implementation
+//
+
+MenubarProp::MenubarProp(nsGlobalWindow *aWindow)
+ : BarProp(aWindow)
+{
+}
+
+MenubarProp::~MenubarProp()
+{
+}
+
+bool
+MenubarProp::GetVisible(ErrorResult& aRv)
+{
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_MENUBAR, aRv);
+}
+
+void
+MenubarProp::SetVisible(bool aVisible, ErrorResult& aRv)
+{
+ BarProp::SetVisibleByFlag(aVisible, nsIWebBrowserChrome::CHROME_MENUBAR, aRv);
+}
+
+//
+// ToolbarProp class implementation
+//
+
+ToolbarProp::ToolbarProp(nsGlobalWindow *aWindow)
+ : BarProp(aWindow)
+{
+}
+
+ToolbarProp::~ToolbarProp()
+{
+}
+
+bool
+ToolbarProp::GetVisible(ErrorResult& aRv)
+{
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_TOOLBAR, aRv);
+}
+
+void
+ToolbarProp::SetVisible(bool aVisible, ErrorResult& aRv)
+{
+ BarProp::SetVisibleByFlag(aVisible, nsIWebBrowserChrome::CHROME_TOOLBAR,
+ aRv);
+}
+
+//
+// LocationbarProp class implementation
+//
+
+LocationbarProp::LocationbarProp(nsGlobalWindow *aWindow)
+ : BarProp(aWindow)
+{
+}
+
+LocationbarProp::~LocationbarProp()
+{
+}
+
+bool
+LocationbarProp::GetVisible(ErrorResult& aRv)
+{
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_LOCATIONBAR,
+ aRv);
+}
+
+void
+LocationbarProp::SetVisible(bool aVisible, ErrorResult& aRv)
+{
+ BarProp::SetVisibleByFlag(aVisible, nsIWebBrowserChrome::CHROME_LOCATIONBAR,
+ aRv);
+}
+
+//
+// PersonalbarProp class implementation
+//
+
+PersonalbarProp::PersonalbarProp(nsGlobalWindow *aWindow)
+ : BarProp(aWindow)
+{
+}
+
+PersonalbarProp::~PersonalbarProp()
+{
+}
+
+bool
+PersonalbarProp::GetVisible(ErrorResult& aRv)
+{
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR,
+ aRv);
+}
+
+void
+PersonalbarProp::SetVisible(bool aVisible, ErrorResult& aRv)
+{
+ BarProp::SetVisibleByFlag(aVisible,
+ nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR,
+ aRv);
+}
+
+//
+// StatusbarProp class implementation
+//
+
+StatusbarProp::StatusbarProp(nsGlobalWindow *aWindow)
+ : BarProp(aWindow)
+{
+}
+
+StatusbarProp::~StatusbarProp()
+{
+}
+
+bool
+StatusbarProp::GetVisible(ErrorResult& aRv)
+{
+ return BarProp::GetVisibleByFlag(nsIWebBrowserChrome::CHROME_STATUSBAR, aRv);
+}
+
+void
+StatusbarProp::SetVisible(bool aVisible, ErrorResult& aRv)
+{
+ return BarProp::SetVisibleByFlag(aVisible,
+ nsIWebBrowserChrome::CHROME_STATUSBAR, aRv);
+}
+
+//
+// ScrollbarsProp class implementation
+//
+
+ScrollbarsProp::ScrollbarsProp(nsGlobalWindow *aWindow)
+: BarProp(aWindow)
+{
+}
+
+ScrollbarsProp::~ScrollbarsProp()
+{
+}
+
+bool
+ScrollbarsProp::GetVisible(ErrorResult& aRv)
+{
+ if (!mDOMWindow) {
+ return true;
+ }
+
+ nsCOMPtr<nsIScrollable> scroller =
+ do_QueryInterface(mDOMWindow->GetDocShell());
+
+ if (!scroller) {
+ return true;
+ }
+
+ int32_t prefValue;
+ scroller->GetDefaultScrollbarPreferences(
+ nsIScrollable::ScrollOrientation_Y, &prefValue);
+ if (prefValue != nsIScrollable::Scrollbar_Never) {
+ return true;
+ }
+
+ scroller->GetDefaultScrollbarPreferences(
+ nsIScrollable::ScrollOrientation_X, &prefValue);
+ return prefValue != nsIScrollable::Scrollbar_Never;
+}
+
+void
+ScrollbarsProp::SetVisible(bool aVisible, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return;
+ }
+
+ /* Scrollbars, unlike the other barprops, implement visibility directly
+ rather than handing off to the superclass (and from there to the
+ chrome window) because scrollbar visibility uniquely applies only
+ to the window making the change (arguably. it does now, anyway.)
+ and because embedding apps have no interface for implementing this
+ themselves, and therefore the implementation must be internal. */
+
+ nsContentUtils::SetScrollbarsVisibility(mDOMWindow->GetDocShell(), aVisible);
+
+ /* Notably absent is the part where we notify the chrome window using
+ GetBrowserChrome()->SetChromeFlags(). Given the possibility of multiple
+ DOM windows (multiple top-level windows, even) within a single
+ chrome window, the historical concept of a single "has scrollbars"
+ flag in the chrome is inapplicable, and we can't tell at this level
+ whether we represent the particular DOM window that makes this decision
+ for the chrome.
+
+ So only this object (and its corresponding DOM window) knows whether
+ scrollbars are visible. The corresponding chrome window will need to
+ ask (one of) its DOM window(s) when it needs to know about scrollbar
+ visibility, rather than caching its own copy of that information.
+ */
+}
+
+} // namespace dom
+} // namespace mozilla
+
diff --git a/dom/base/BarProps.h b/dom/base/BarProps.h
new file mode 100644
index 000000000..bc75775a4
--- /dev/null
+++ b/dom/base/BarProps.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/. */
+
+/* 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"
+
+class nsGlobalWindow;
+class nsIWebBrowserChrome;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+// Script "BarProp" object
+class BarProp : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ explicit BarProp(nsGlobalWindow *aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BarProp)
+
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual bool GetVisible(ErrorResult& aRv) = 0;
+ virtual void SetVisible(bool aVisible, ErrorResult& aRv) = 0;
+
+protected:
+ virtual ~BarProp();
+
+ bool GetVisibleByFlag(uint32_t aChromeFlag, ErrorResult& aRv);
+ void SetVisibleByFlag(bool aVisible, uint32_t aChromeFlag, ErrorResult &aRv);
+
+ already_AddRefed<nsIWebBrowserChrome> GetBrowserChrome();
+
+ RefPtr<nsGlobalWindow> mDOMWindow;
+};
+
+// Script "menubar" object
+class MenubarProp final : public BarProp
+{
+public:
+ explicit MenubarProp(nsGlobalWindow *aWindow);
+ virtual ~MenubarProp();
+
+ virtual bool GetVisible(ErrorResult& aRv) override;
+ virtual void SetVisible(bool aVisible, ErrorResult& aRv) override;
+};
+
+// Script "toolbar" object
+class ToolbarProp final : public BarProp
+{
+public:
+ explicit ToolbarProp(nsGlobalWindow *aWindow);
+ virtual ~ToolbarProp();
+
+ virtual bool GetVisible(ErrorResult& aRv) override;
+ virtual void SetVisible(bool aVisible, ErrorResult& aRv) override;
+};
+
+// Script "locationbar" object
+class LocationbarProp final : public BarProp
+{
+public:
+ explicit LocationbarProp(nsGlobalWindow *aWindow);
+ virtual ~LocationbarProp();
+
+ virtual bool GetVisible(ErrorResult& aRv) override;
+ virtual void SetVisible(bool aVisible, ErrorResult& aRv) override;
+};
+
+// Script "personalbar" object
+class PersonalbarProp final : public BarProp
+{
+public:
+ explicit PersonalbarProp(nsGlobalWindow *aWindow);
+ virtual ~PersonalbarProp();
+
+ virtual bool GetVisible(ErrorResult& aRv) override;
+ virtual void SetVisible(bool aVisible, ErrorResult& aRv) override;
+};
+
+// Script "statusbar" object
+class StatusbarProp final : public BarProp
+{
+public:
+ explicit StatusbarProp(nsGlobalWindow *aWindow);
+ virtual ~StatusbarProp();
+
+ virtual bool GetVisible(ErrorResult& aRv) override;
+ virtual void SetVisible(bool aVisible, ErrorResult& aRv) override;
+};
+
+// Script "scrollbars" object
+class ScrollbarsProp final : public BarProp
+{
+public:
+ explicit ScrollbarsProp(nsGlobalWindow *aWindow);
+ virtual ~ScrollbarsProp();
+
+ virtual bool GetVisible(ErrorResult& aRv) override;
+ virtual void SetVisible(bool aVisible, ErrorResult& aRv) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_BarProps_h */
+
diff --git a/dom/base/BlobSet.cpp b/dom/base/BlobSet.cpp
new file mode 100644
index 000000000..f13d412fc
--- /dev/null
+++ b/dom/base/BlobSet.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/BlobSet.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/File.h"
+#include "MultipartBlobImpl.h"
+
+namespace mozilla {
+namespace dom {
+
+nsresult
+BlobSet::AppendVoidPtr(const void* aData, uint32_t aLength)
+{
+ NS_ENSURE_ARG_POINTER(aData);
+ if (!aLength) {
+ return NS_OK;
+ }
+
+ void* data = malloc(aLength);
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy((char*)data, aData, aLength);
+
+ RefPtr<BlobImpl> blobImpl = new BlobImplMemory(data, aLength, EmptyString());
+ mBlobImpls.AppendElement(blobImpl);
+
+ return NS_OK;
+}
+
+nsresult
+BlobSet::AppendString(const nsAString& aString, bool nativeEOL, JSContext* aCx)
+{
+ nsCString utf8Str = NS_ConvertUTF16toUTF8(aString);
+
+ if (nativeEOL) {
+ if (utf8Str.Contains('\r')) {
+ utf8Str.ReplaceSubstring("\r\n", "\n");
+ utf8Str.ReplaceSubstring("\r", "\n");
+ }
+#ifdef XP_WIN
+ utf8Str.ReplaceSubstring("\n", "\r\n");
+#endif
+ }
+
+ return AppendVoidPtr((void*)utf8Str.Data(),
+ utf8Str.Length());
+}
+
+nsresult
+BlobSet::AppendBlobImpl(BlobImpl* aBlobImpl)
+{
+ NS_ENSURE_ARG_POINTER(aBlobImpl);
+ mBlobImpls.AppendElement(aBlobImpl);
+ return NS_OK;
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/base/BlobSet.h b/dom/base/BlobSet.h
new file mode 100644
index 000000000..3e22955dd
--- /dev/null
+++ b/dom/base/BlobSet.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 mozilla_dom_BlobSet_h
+#define mozilla_dom_BlobSet_h
+
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpl;
+
+class BlobSet final
+{
+public:
+ nsresult AppendVoidPtr(const void* aData, uint32_t aLength);
+
+ nsresult AppendString(const nsAString& aString, bool nativeEOL,
+ JSContext* aCx);
+
+ nsresult AppendBlobImpl(BlobImpl* aBlobImpl);
+
+ nsTArray<RefPtr<BlobImpl>>& GetBlobImpls() { return mBlobImpls; }
+
+private:
+ nsTArray<RefPtr<BlobImpl>> mBlobImpls;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BlobSet_h
diff --git a/dom/base/BodyUtil.cpp b/dom/base/BodyUtil.cpp
new file mode 100644
index 000000000..2bce8a0bf
--- /dev/null
+++ b/dom/base/BodyUtil.cpp
@@ -0,0 +1,575 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIUnicodeDecoder.h"
+
+#include "nsCharSeparatedTokenizer.h"
+#include "nsDOMString.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/EncodingUtils.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"
+#include "mozilla/dom/URLSearchParams.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+class StreamDecoder final
+{
+ nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+ nsString mDecoded;
+
+public:
+ StreamDecoder()
+ : mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
+ {
+ MOZ_ASSERT(mDecoder);
+ }
+
+ nsresult
+ AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
+ {
+ int32_t destBufferLen;
+ nsresult rv =
+ mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
+ int32_t totalChars = mDecoded.Length();
+
+ int32_t srcLen = (int32_t) aSrcBufferLen;
+ int32_t outLen = destBufferLen;
+ rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ totalChars += outLen;
+ mDecoded.SetLength(totalChars);
+
+ return NS_OK;
+ }
+
+ nsString&
+ GetText()
+ {
+ return mDecoded;
+ }
+};
+
+// 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;
+}
+
+class MOZ_STACK_CLASS FillFormIterator final
+ : public URLSearchParams::ForEachIterator
+{
+public:
+ explicit FillFormIterator(FormData* aFormData)
+ : mFormData(aFormData)
+ {
+ MOZ_ASSERT(aFormData);
+ }
+
+ bool URLParamsIterator(const nsString& aName,
+ const nsString& aValue) override
+ {
+ ErrorResult rv;
+ mFormData->Append(aName, aValue, rv);
+ MOZ_ASSERT(!rv.Failed());
+ return true;
+ }
+
+private:
+ FormData* mFormData;
+};
+
+/**
+ * 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 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")) {
+ nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
+ bool seenFormData = false;
+ while (tokenizer.hasMoreTokens()) {
+ const nsDependentCSubstring& token = tokenizer.nextToken();
+ if (token.IsEmpty()) {
+ continue;
+ }
+
+ if (token.EqualsLiteral("form-data")) {
+ seenFormData = true;
+ continue;
+ }
+
+ if (seenFormData &&
+ StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
+ mName = StringTail(token, token.Length() - 5);
+ mName.Trim(" \"");
+ continue;
+ }
+
+ if (seenFormData &&
+ StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
+ 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()));
+ if (!copy) {
+ NS_WARNING("Failed to copy File entry body.");
+ return false;
+ }
+ 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::CreateMemoryFile(mParentObject,
+ reinterpret_cast<void *>(copy), body.Length(),
+ NS_ConvertUTF8toUTF16(mFilename),
+ NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
+ 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& aData, nsIGlobalObject* aParent)
+ : mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent)
+ {
+ }
+
+ bool
+ Parse()
+ {
+ // Determine boundary from mimetype.
+ const char* boundaryId = nullptr;
+ boundaryId = strstr(mMimeType.BeginWriting(), "boundary");
+ if (!boundaryId) {
+ return false;
+ }
+
+ boundaryId = strchr(boundaryId, '=');
+ if (!boundaryId) {
+ return false;
+ }
+
+ // Skip over '='.
+ boundaryId++;
+
+ char *attrib = (char *) strchr(boundaryId, ';');
+ if (attrib) *attrib = '\0';
+
+ nsAutoCString boundaryString(boundaryId);
+ if (attrib) *attrib = ';';
+
+ boundaryString.Trim(" \"");
+
+ if (boundaryString.Length() == 0) {
+ 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 = NS_LITERAL_CSTRING("text/plain");
+
+ // 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");
+ }
+ }
+
+ NS_NOTREACHED("Should never reach here.");
+ return false;
+ }
+
+ already_AddRefed<FormData> GetFormData()
+ {
+ return mFormData.forget();
+ }
+};
+}
+
+// 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(nsISupports* 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 nsCString& aStr, ErrorResult& aRv)
+{
+ NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
+
+ // 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, aStr, aParent);
+ if (!parser.Parse()) {
+ aRv.ThrowTypeError<MSG_BAD_FORMDATA>();
+ return nullptr;
+ }
+
+ RefPtr<FormData> fd = parser.GetFormData();
+ MOZ_ASSERT(fd);
+ return fd.forget();
+ }
+
+ NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
+ bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
+
+ if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
+ isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
+ }
+
+ if (isValidUrlEncodedMimeType) {
+ URLParams params;
+ params.ParseInput(aStr);
+
+ RefPtr<FormData> fd = new FormData(aParent);
+ FillFormIterator iterator(fd);
+ DebugOnly<bool> status = params.ForEach(iterator);
+ 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)
+{
+ StreamDecoder decoder;
+ nsresult rv = decoder.AppendText(reinterpret_cast<char*>(aInput),
+ aInputLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ aText = decoder.GetText();
+ 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 dom
+} // namespace mozilla
diff --git a/dom/base/BodyUtil.h b/dom/base/BodyUtil.h
new file mode 100644
index 000000000..964720b04
--- /dev/null
+++ b/dom/base/BodyUtil.h
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ErrorResult.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+
+namespace mozilla {
+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(nsISupports* 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 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 000000000..ab6a3543b
--- /dev/null
+++ b/dom/base/BorrowedAttrInfo.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/BorrowedAttrInfo.h"
+
+namespace mozilla {
+namespace 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 dom
+} // namespace mozilla
diff --git a/dom/base/BorrowedAttrInfo.h b/dom/base/BorrowedAttrInfo.h
new file mode 100644
index 000000000..8efb02d25
--- /dev/null
+++ b/dom/base/BorrowedAttrInfo.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 BorrowedAttrInfo_h__
+#define BorrowedAttrInfo_h__
+
+#include "mozilla/Assertions.h"
+
+class nsAttrName;
+class nsAttrValue;
+
+namespace mozilla {
+namespace 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 dom
+} // namespace mozilla
+#endif
diff --git a/dom/base/CORSMode.h b/dom/base/CORSMode.h
new file mode 100644
index 000000000..82f2f570d
--- /dev/null
+++ b/dom/base/CORSMode.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_
+
+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
+};
+
+} // namespace mozilla
+
+#endif /* CORSMode_h_ */
diff --git a/dom/base/ChildIterator.cpp b/dom/base/ChildIterator.cpp
new file mode 100644
index 000000000..d8c454ae8
--- /dev/null
+++ b/dom/base/ChildIterator.cpp
@@ -0,0 +1,616 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/XBLChildrenElement.h"
+#include "mozilla/dom/HTMLContentElement.h"
+#include "mozilla/dom/HTMLShadowElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIFrame.h"
+#include "nsCSSAnonBoxes.h"
+
+namespace mozilla {
+namespace dom {
+
+class MatchedNodes {
+public:
+ explicit MatchedNodes(HTMLContentElement* aInsertionPoint)
+ : mIsContentElement(true), mContentElement(aInsertionPoint) {}
+
+ explicit MatchedNodes(XBLChildrenElement* aInsertionPoint)
+ : mIsContentElement(false), mChildrenElement(aInsertionPoint) {}
+
+ uint32_t Length() const
+ {
+ return mIsContentElement ? mContentElement->MatchedNodes().Length()
+ : mChildrenElement->InsertedChildrenLength();
+ }
+
+ nsIContent* operator[](int32_t aIndex) const
+ {
+ return mIsContentElement ? mContentElement->MatchedNodes()[aIndex]
+ : mChildrenElement->InsertedChild(aIndex);
+ }
+
+ bool IsEmpty() const
+ {
+ return mIsContentElement ? mContentElement->MatchedNodes().IsEmpty()
+ : !mChildrenElement->HasInsertedChildren();
+ }
+protected:
+ bool mIsContentElement;
+ union {
+ HTMLContentElement* mContentElement;
+ XBLChildrenElement* mChildrenElement;
+ };
+};
+
+static inline MatchedNodes
+GetMatchedNodesForPoint(nsIContent* aContent)
+{
+ if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ // XBL case
+ return MatchedNodes(static_cast<XBLChildrenElement*>(aContent));
+ }
+
+ // Web components case
+ MOZ_ASSERT(aContent->IsHTMLElement(nsGkAtoms::content));
+ return MatchedNodes(HTMLContentElement::FromContent(aContent));
+}
+
+nsIContent*
+ExplicitChildIterator::GetNextChild()
+{
+ // If we're already in the inserted-children array, look there first
+ if (mIndexInInserted) {
+ MOZ_ASSERT(mChild);
+ MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild));
+ MOZ_ASSERT(!mDefaultChild);
+
+ MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
+ if (mIndexInInserted < assignedChildren.Length()) {
+ return assignedChildren[mIndexInInserted++];
+ }
+ mIndexInInserted = 0;
+ mChild = mChild->GetNextSibling();
+ } else if (mShadowIterator) {
+ // If we're inside of a <shadow> element, look through the
+ // explicit children of the projected ShadowRoot via
+ // the mShadowIterator.
+ nsIContent* nextChild = mShadowIterator->GetNextChild();
+ if (nextChild) {
+ return nextChild;
+ }
+
+ mShadowIterator = nullptr;
+ mChild = mChild->GetNextSibling();
+ } else if (mDefaultChild) {
+ // If we're already in default content, check if there are more nodes there
+ MOZ_ASSERT(mChild);
+ MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild));
+
+ mDefaultChild = mDefaultChild->GetNextSibling();
+ if (mDefaultChild) {
+ return mDefaultChild;
+ }
+
+ mChild = mChild->GetNextSibling();
+ } else 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();
+ }
+
+ // Iterate until we find a non-insertion point, or an insertion point with
+ // content.
+ while (mChild) {
+ // If the current child being iterated is a shadow insertion point then
+ // the iterator needs to go into the projected ShadowRoot.
+ if (ShadowRoot::IsShadowInsertionPoint(mChild)) {
+ // Look for the next child in the projected ShadowRoot for the <shadow>
+ // element.
+ HTMLShadowElement* shadowElem = HTMLShadowElement::FromContent(mChild);
+ ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot();
+ if (projectedShadow) {
+ mShadowIterator = new ExplicitChildIterator(projectedShadow);
+ nsIContent* nextChild = mShadowIterator->GetNextChild();
+ if (nextChild) {
+ return nextChild;
+ }
+ mShadowIterator = nullptr;
+ }
+ mChild = mChild->GetNextSibling();
+ } else if (nsContentUtils::IsContentInsertionPoint(mChild)) {
+ // If the current child being iterated is a content insertion point
+ // then the iterator needs to return the nodes distributed into
+ // the content insertion point.
+ MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
+ if (!assignedChildren.IsEmpty()) {
+ // Iterate through elements projected on insertion point.
+ mIndexInInserted = 1;
+ return assignedChildren[0];
+ }
+
+ // Insertion points inside fallback/default content
+ // are considered inactive and do not get assigned nodes.
+ mDefaultChild = mChild->GetFirstChild();
+ if (mDefaultChild) {
+ return mDefaultChild;
+ }
+
+ // If we have an insertion point with no assigned nodes and
+ // no default content, move on to the next node.
+ mChild = mChild->GetNextSibling();
+ } else {
+ // mChild is not an insertion point, thus it is the next node to
+ // return from this iterator.
+ break;
+ }
+ }
+
+ return mChild;
+}
+
+void
+FlattenedChildIterator::Init(bool aIgnoreXBL)
+{
+ if (aIgnoreXBL) {
+ return;
+ }
+
+ nsXBLBinding* binding =
+ mParent->OwnerDoc()->BindingManager()->GetBindingWithContent(mParent);
+
+ if (binding) {
+ nsIContent* anon = binding->GetAnonymousContent();
+ if (anon) {
+ mParent = anon;
+ mXBLInvolved = true;
+ }
+ }
+
+ // We set mXBLInvolved to true if either:
+ // - The node we're iterating has a binding with content attached to it.
+ // - The node is generated XBL content and has an <xbl:children> child.
+ if (!mXBLInvolved && mParent->GetBindingParent()) {
+ for (nsIContent* child = mParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ MOZ_ASSERT(child->GetBindingParent());
+ mXBLInvolved = true;
+ break;
+ }
+ }
+ }
+}
+
+bool
+ExplicitChildIterator::Seek(nsIContent* aChildToFind)
+{
+ if (aChildToFind->GetParent() == mParent &&
+ !aChildToFind->IsRootOfAnonymousSubtree()) {
+ // Fast path: just point ourselves to aChildToFind, which is a
+ // normal DOM child of ours.
+ MOZ_ASSERT(!ShadowRoot::IsShadowInsertionPoint(aChildToFind));
+ MOZ_ASSERT(!nsContentUtils::IsContentInsertionPoint(aChildToFind));
+ mChild = aChildToFind;
+ mIndexInInserted = 0;
+ mShadowIterator = nullptr;
+ mDefaultChild = nullptr;
+ mIsFirst = false;
+ return true;
+ }
+
+ // Can we add more fast paths here based on whether the parent of aChildToFind
+ // is a shadow insertion point or content insertion point?
+
+ // Slow path: just walk all our kids.
+ return Seek(aChildToFind, nullptr);
+}
+
+nsIContent*
+ExplicitChildIterator::Get() const
+{
+ MOZ_ASSERT(!mIsFirst);
+
+ if (mIndexInInserted) {
+ MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
+ return assignedChildren[mIndexInInserted - 1];
+ } else if (mShadowIterator) {
+ return mShadowIterator->Get();
+ }
+ return mDefaultChild ? mDefaultChild : mChild;
+}
+
+nsIContent*
+ExplicitChildIterator::GetPreviousChild()
+{
+ // If we're already in the inserted-children array, look there first
+ if (mIndexInInserted) {
+ // NB: mIndexInInserted points one past the last returned child so we need
+ // to look *two* indices back in order to return the previous child.
+ MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
+ if (--mIndexInInserted) {
+ return assignedChildren[mIndexInInserted - 1];
+ }
+ mChild = mChild->GetPreviousSibling();
+ } else if (mShadowIterator) {
+ nsIContent* previousChild = mShadowIterator->GetPreviousChild();
+ if (previousChild) {
+ return previousChild;
+ }
+ mShadowIterator = nullptr;
+ mChild = mChild->GetPreviousSibling();
+ } else if (mDefaultChild) {
+ // If we're already in default content, check if there are more nodes there
+ mDefaultChild = mDefaultChild->GetPreviousSibling();
+ if (mDefaultChild) {
+ return mDefaultChild;
+ }
+
+ mChild = mChild->GetPreviousSibling();
+ } else if (mIsFirst) { // at the beginning of the child list
+ return nullptr;
+ } else if (mChild) { // in the middle of the child list
+ mChild = mChild->GetPreviousSibling();
+ } else { // at the end of the child list
+ mChild = mParent->GetLastChild();
+ }
+
+ // Iterate until we find a non-insertion point, or an insertion point with
+ // content.
+ while (mChild) {
+ if (ShadowRoot::IsShadowInsertionPoint(mChild)) {
+ // If the current child being iterated is a shadow insertion point then
+ // the iterator needs to go into the projected ShadowRoot.
+ HTMLShadowElement* shadowElem = HTMLShadowElement::FromContent(mChild);
+ ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot();
+ if (projectedShadow) {
+ // Create a ExplicitChildIterator that begins iterating from the end.
+ mShadowIterator = new ExplicitChildIterator(projectedShadow, false);
+ nsIContent* previousChild = mShadowIterator->GetPreviousChild();
+ if (previousChild) {
+ return previousChild;
+ }
+ mShadowIterator = nullptr;
+ }
+ mChild = mChild->GetPreviousSibling();
+ } else if (nsContentUtils::IsContentInsertionPoint(mChild)) {
+ // If the current child being iterated is a content insertion point
+ // then the iterator needs to return the nodes distributed into
+ // the content insertion point.
+ MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
+ if (!assignedChildren.IsEmpty()) {
+ mIndexInInserted = assignedChildren.Length();
+ return assignedChildren[mIndexInInserted - 1];
+ }
+
+ mDefaultChild = mChild->GetLastChild();
+ if (mDefaultChild) {
+ return mDefaultChild;
+ }
+
+ mChild = mChild->GetPreviousSibling();
+ } else {
+ // mChild is not an insertion point, thus it is the next node to
+ // return from this iterator.
+ break;
+ }
+ }
+
+ if (!mChild) {
+ mIsFirst = true;
+ }
+
+ return mChild;
+}
+
+nsIContent*
+AllChildrenIterator::Get() const
+{
+ switch (mPhase) {
+ case eAtBeforeKid: {
+ nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
+ MOZ_ASSERT(frame, "No frame at eAtBeforeKid phase");
+ nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
+ MOZ_ASSERT(beforeFrame, "No content before frame at eAtBeforeKid phase");
+ return beforeFrame->GetContent();
+ }
+
+ case eAtExplicitKids:
+ return ExplicitChildIterator::Get();
+
+ case eAtAnonKids:
+ return mAnonKids[mAnonKidsIdx];
+
+ case eAtAfterKid: {
+ nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
+ MOZ_ASSERT(frame, "No frame at eAtAfterKid phase");
+ nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
+ MOZ_ASSERT(afterFrame, "No content before frame at eAtBeforeKid phase");
+ return afterFrame->GetContent();
+ }
+
+ default:
+ return nullptr;
+ }
+}
+
+
+bool
+AllChildrenIterator::Seek(nsIContent* aChildToFind)
+{
+ if (mPhase == eAtBegin || mPhase == eAtBeforeKid) {
+ mPhase = eAtExplicitKids;
+ nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
+ if (frame) {
+ nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
+ if (beforeFrame) {
+ if (beforeFrame->GetContent() == aChildToFind) {
+ mPhase = eAtBeforeKid;
+ return true;
+ }
+ }
+ }
+ }
+
+ if (mPhase == eAtExplicitKids) {
+ if (ExplicitChildIterator::Seek(aChildToFind)) {
+ return true;
+ }
+ mPhase = eAtAnonKids;
+ }
+
+ nsIContent* child = nullptr;
+ do {
+ child = GetNextChild();
+ } while (child && child != aChildToFind);
+
+ return child == aChildToFind;
+}
+
+void
+AllChildrenIterator::AppendNativeAnonymousChildren()
+{
+ AppendNativeAnonymousChildrenFromFrame(mOriginalContent->GetPrimaryFrame());
+
+ // The root scroll frame is not the primary frame of the root element.
+ // Detect and handle this case.
+ if (mOriginalContent == mOriginalContent->OwnerDoc()->GetRootElement()) {
+ nsIPresShell* presShell = mOriginalContent->OwnerDoc()->GetShell();
+ nsIFrame* scrollFrame = presShell ? presShell->GetRootScrollFrame() : nullptr;
+ if (scrollFrame) {
+ AppendNativeAnonymousChildrenFromFrame(scrollFrame);
+ }
+ }
+}
+
+void
+AllChildrenIterator::AppendNativeAnonymousChildrenFromFrame(nsIFrame* aFrame)
+{
+ nsIAnonymousContentCreator* ac = do_QueryFrame(aFrame);
+ if (ac) {
+ ac->AppendAnonymousContentTo(mAnonKids, mFlags);
+ }
+}
+
+nsIContent*
+AllChildrenIterator::GetNextChild()
+{
+ if (mPhase == eAtBegin) {
+ mPhase = eAtExplicitKids;
+ nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
+ if (frame) {
+ nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
+ if (beforeFrame) {
+ mPhase = eAtBeforeKid;
+ return beforeFrame->GetContent();
+ }
+ }
+ }
+
+ if (mPhase == eAtBeforeKid) {
+ // Advance into our explicit kids.
+ mPhase = eAtExplicitKids;
+ }
+
+ if (mPhase == eAtExplicitKids) {
+ nsIContent* kid = ExplicitChildIterator::GetNextChild();
+ if (kid) {
+ 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];
+ }
+
+ nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
+ if (frame) {
+ nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
+ if (afterFrame) {
+ mPhase = eAtAfterKid;
+ return afterFrame->GetContent();
+ }
+ }
+ }
+
+ mPhase = eAtEnd;
+ return nullptr;
+}
+
+nsIContent*
+AllChildrenIterator::GetPreviousChild()
+{
+ if (mPhase == eAtEnd) {
+ MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length());
+ mPhase = eAtAnonKids;
+ nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
+ if (frame) {
+ nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
+ if (afterFrame) {
+ mPhase = eAtAfterKid;
+ return afterFrame->GetContent();
+ }
+ }
+ }
+
+ 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 = eAtExplicitKids;
+ }
+
+ if (mPhase == eAtExplicitKids) {
+ nsIContent* kid = ExplicitChildIterator::GetPreviousChild();
+ if (kid) {
+ return kid;
+ }
+
+ nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
+ if (frame) {
+ nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
+ if (beforeFrame) {
+ mPhase = eAtBeforeKid;
+ return beforeFrame->GetContent();
+ }
+ }
+ }
+
+ mPhase = eAtBegin;
+ return nullptr;
+}
+
+static bool
+IsNativeAnonymousImplementationOfPseudoElement(nsIContent* aContent)
+{
+ // First, we need a frame. This leads to the tricky issue of what we can
+ // infer if the frame is null.
+ //
+ // Unlike regular nodes, native anonymous content (NAC) gets created during
+ // frame construction, which happens after the main style traversal. This
+ // means that we have to manually resolve style for those nodes shortly after
+ // they're created, either by (a) invoking ResolvePseudoElementStyle (for PE
+ // NAC), or (b) handing the subtree off to Servo for a mini-traversal (for
+ // non-PE NAC). We have assertions in nsCSSFrameConstructor that we don't do
+ // both.
+ //
+ // Once that happens, the NAC has a frame. So if we have no frame here,
+ // we're either not NAC, or in the process of doing (b). Either way, this
+ // isn't a PE.
+ nsIFrame* f = aContent->GetPrimaryFrame();
+ if (!f) {
+ return false;
+ }
+
+ // Get the pseudo type.
+ CSSPseudoElementType pseudoType = f->StyleContext()->GetPseudoType();
+
+ // In general nodes never get anonymous box style. However, there are a few
+ // special cases:
+ //
+ // * We somewhat-confusingly give text nodes a style context tagged with
+ // ":-moz-text", so we need to check for the anonymous box case here.
+ // * The primary frame for table elements is an anonymous box that inherits
+ // from the table's style.
+ if (pseudoType == CSSPseudoElementType::AnonBox) {
+ MOZ_ASSERT(f->StyleContext()->GetPseudo() == nsCSSAnonBoxes::mozText ||
+ f->StyleContext()->GetPseudo() == nsCSSAnonBoxes::tableWrapper);
+ return false;
+ }
+
+ // Finally check the actual pseudo type.
+ bool isImpl = pseudoType != CSSPseudoElementType::NotPseudo;
+ MOZ_ASSERT_IF(isImpl, aContent->IsRootOfNativeAnonymousSubtree());
+ return isImpl;
+}
+
+/* static */ bool
+StyleChildrenIterator::IsNeeded(const Element* aElement)
+{
+ // If the node is in an anonymous subtree, we conservatively return true to
+ // handle insertion points.
+ if (aElement->IsInAnonymousSubtree()) {
+ return true;
+ }
+
+ // If the node has an XBL binding with anonymous content return true.
+ if (aElement->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsBindingManager* manager = aElement->OwnerDoc()->BindingManager();
+ nsXBLBinding* binding = manager->GetBindingWithContent(aElement);
+ if (binding && binding->GetAnonymousContent()) {
+ return true;
+ }
+ }
+
+ // If the node has native anonymous content, return true.
+ nsIAnonymousContentCreator* ac = do_QueryFrame(aElement->GetPrimaryFrame());
+ if (ac) {
+ return true;
+ }
+
+ // The root element has a scroll frame that is not the primary frame, so we
+ // need to do special checking for that case.
+ if (aElement == aElement->OwnerDoc()->GetRootElement()) {
+ return true;
+ }
+
+ return false;
+}
+
+
+nsIContent*
+StyleChildrenIterator::GetNextChild()
+{
+ while (nsIContent* child = AllChildrenIterator::GetNextChild()) {
+ if (IsNativeAnonymousImplementationOfPseudoElement(child)) {
+ // Skip any native-anonymous children that are used to implement pseudo-
+ // elements. These match pseudo-element selectors instead of being
+ // considered a child of their host, and thus the style system needs to
+ // handle them separately.
+ } else {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/ChildIterator.h b/dom/base/ChildIterator.h
new file mode 100644
index 000000000..ffff8dce5
--- /dev/null
+++ b/dom/base/ChildIterator.h
@@ -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/. */
+
+#ifndef ChildIterator_h
+#define ChildIterator_h
+
+#include "nsIContent.h"
+
+/**
+ * Iterates over the children on a node. If a child is an insertion point,
+ * iterates over the children inserted there instead, or the default content
+ * if no children are inserted there.
+ *
+ * The FlattenedChildIterator expands any anonymous content bound from an XBL
+ * binding's <xbl:content> element.
+ */
+
+#include <stdint.h>
+#include "nsAutoPtr.h"
+
+class nsIContent;
+
+namespace mozilla {
+namespace dom {
+
+// This class iterates normal DOM child nodes of a given DOM node with
+// <xbl:children> nodes replaced by the elements that have been filtered into that
+// insertion point. Any bindings on the given element are ignored for purposes
+// of determining which insertion point children are filtered into. 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 ExplicitChildIterator
+{
+public:
+ explicit ExplicitChildIterator(const nsIContent* aParent,
+ bool aStartAtBeginning = true)
+ : mParent(aParent),
+ mChild(nullptr),
+ mDefaultChild(nullptr),
+ mIndexInInserted(0),
+ mIsFirst(aStartAtBeginning)
+ {
+ }
+
+ ExplicitChildIterator(const ExplicitChildIterator& aOther)
+ : mParent(aOther.mParent), mChild(aOther.mChild),
+ mDefaultChild(aOther.mDefaultChild),
+ mShadowIterator(aOther.mShadowIterator ?
+ new ExplicitChildIterator(*aOther.mShadowIterator) :
+ nullptr),
+ mIndexInInserted(aOther.mIndexInInserted), mIsFirst(aOther.mIsFirst) {}
+
+ ExplicitChildIterator(ExplicitChildIterator&& aOther)
+ : mParent(aOther.mParent), mChild(aOther.mChild),
+ mDefaultChild(aOther.mDefaultChild),
+ mShadowIterator(Move(aOther.mShadowIterator)),
+ mIndexInInserted(aOther.mIndexInInserted), mIsFirst(aOther.mIsFirst) {}
+
+ nsIContent* GetNextChild();
+
+ // Looks for aChildToFind respecting insertion points until aChildToFind is
+ // found. This version can take shortcuts that the two-argument version
+ // can't, so can be faster (and in fact can be O(1) instead of O(N) in many
+ // cases).
+ bool Seek(nsIContent* aChildToFind);
+
+ // Looks for aChildToFind respecting insertion points until aChildToFind is found.
+ // or aBound is found. If aBound is nullptr then the seek is unbounded. Returns
+ // whether aChildToFind was found as an explicit child prior to encountering
+ // aBound.
+ bool Seek(nsIContent* aChildToFind, nsIContent* aBound)
+ {
+ // 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.
+
+ // We can't easily take shortcuts here because we'd have to have a way to
+ // compare aChildToFind to aBound.
+ nsIContent* child;
+ do {
+ child = GetNextChild();
+ } while (child && child != aChildToFind && child != aBound);
+
+ return child == aChildToFind;
+ }
+
+ // Returns the current target of this iterator (which might be an explicit
+ // child of the node, fallback content of an insertion point or
+ // a node distributed to an insertion point.
+ nsIContent* Get() const;
+
+ // The inverse of GetNextChild. Properly steps in and out of insertion
+ // points.
+ nsIContent* GetPreviousChild();
+
+protected:
+ // The parent of the children being iterated. For the FlattenedChildIterator,
+ // if there is a binding attached to the original parent, mParent points to
+ // the <xbl:content> element for the binding.
+ const nsIContent* mParent;
+
+ // The current child. When we encounter an insertion point,
+ // mChild remains as the insertion point whose content we're iterating (and
+ // our state is controled by mDefaultChild or mIndexInInserted depending on
+ // whether the insertion point expands to its default content or not).
+ nsIContent* mChild;
+
+ // If non-null, this points to the current default content for the current
+ // insertion point that we're iterating (i.e. mChild, which must be an
+ // nsXBLChildrenElement or HTMLContentElement). Once this transitions back
+ // to null, we continue iterating at mChild's next sibling.
+ nsIContent* mDefaultChild;
+
+ // If non-null, this points to an iterator of the explicit children of
+ // the ShadowRoot projected by the current shadow element that we're
+ // iterating.
+ nsAutoPtr<ExplicitChildIterator> mShadowIterator;
+
+ // If not zero, we're iterating inserted children for an insertion point. This
+ // is an index into mChild's inserted children array (mChild must be an
+ // nsXBLChildrenElement). The index is one past the "current" child (as
+ // opposed to mChild which represents the "current" child).
+ uint32_t mIndexInInserted;
+
+ // A flag to let us know that we haven't started iterating yet.
+ bool mIsFirst;
+};
+
+// Iterates over the flattened children of a node, which accounts for anonymous
+// children and nodes moved by insertion points. If a node has anonymous
+// children, those are iterated over. 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 ExplicitChildIterator
+{
+public:
+ explicit FlattenedChildIterator(const nsIContent* aParent,
+ bool aStartAtBeginning = true)
+ : ExplicitChildIterator(aParent, aStartAtBeginning), mXBLInvolved(false)
+ {
+ Init(false);
+ }
+
+ FlattenedChildIterator(FlattenedChildIterator&& aOther)
+ : ExplicitChildIterator(Move(aOther)), mXBLInvolved(aOther.mXBLInvolved) {}
+
+ FlattenedChildIterator(const FlattenedChildIterator& aOther)
+ : ExplicitChildIterator(aOther), mXBLInvolved(aOther.mXBLInvolved) {}
+
+ bool XBLInvolved() { return mXBLInvolved; }
+
+protected:
+ /**
+ * This constructor is a hack to help AllChildrenIterator which sometimes
+ * doesn't want to consider XBL.
+ */
+ FlattenedChildIterator(const nsIContent* aParent, uint32_t aFlags,
+ bool aStartAtBeginning = true)
+ : ExplicitChildIterator(aParent, aStartAtBeginning), mXBLInvolved(false)
+ {
+ bool ignoreXBL = aFlags & nsIContent::eAllButXBL;
+ Init(ignoreXBL);
+ }
+
+ void Init(bool aIgnoreXBL);
+
+ // For certain optimizations, nsCSSFrameConstructor needs to know if the
+ // child list of the element that we're iterating matches its .childNodes.
+ bool mXBLInvolved;
+};
+
+/**
+ * AllChildrenIterator traverses the children of an element including before /
+ * after content and optionally XBL children. 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, aFlags, aStartAtBeginning),
+ mOriginalContent(aNode), mAnonKidsIdx(aStartAtBeginning ? UINT32_MAX : 0),
+ mFlags(aFlags), mPhase(aStartAtBeginning ? eAtBegin : eAtEnd) { }
+
+ AllChildrenIterator(AllChildrenIterator&& aOther)
+ : FlattenedChildIterator(Move(aOther)),
+ mOriginalContent(aOther.mOriginalContent),
+ mAnonKids(Move(aOther.mAnonKids)), mAnonKidsIdx(aOther.mAnonKidsIdx),
+ mFlags(aOther.mFlags), mPhase(aOther.mPhase)
+#ifdef DEBUG
+ , mMutationGuard(aOther.mMutationGuard)
+#endif
+ {}
+
+#ifdef DEBUG
+ ~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(nsIContent* aChildToFind);
+
+ nsIContent* GetNextChild();
+ nsIContent* GetPreviousChild();
+ const nsIContent* Parent() const { return mOriginalContent; }
+
+ enum IteratorPhase
+ {
+ eAtBegin,
+ eAtBeforeKid,
+ eAtExplicitKids,
+ eAtAnonKids,
+ eAtAfterKid,
+ eAtEnd
+ };
+ IteratorPhase Phase() const { return mPhase; }
+
+private:
+ // Helpers.
+ void AppendNativeAnonymousChildren();
+ void AppendNativeAnonymousChildrenFromFrame(nsIFrame* aFrame);
+
+ const nsIContent* mOriginalContent;
+
+ // 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. This is identical to AllChildrenIterator with eAllChildren,
+ * _except_ that we detect and skip any native anonymous children that are used
+ * to implement pseudo-elements (since the style system needs to cascade those
+ * using different algorithms).
+ *
+ * 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 StyleChildrenIterator : private AllChildrenIterator {
+public:
+ explicit StyleChildrenIterator(const nsIContent* aContent)
+ : AllChildrenIterator(aContent, nsIContent::eAllChildren)
+ {
+ MOZ_COUNT_CTOR(StyleChildrenIterator);
+ }
+ ~StyleChildrenIterator() { MOZ_COUNT_DTOR(StyleChildrenIterator); }
+
+ nsIContent* GetNextChild();
+
+ // Returns true if we cannot find all the children we need to style by
+ // traversing the siblings of the first child.
+ static bool IsNeeded(const Element* aParent);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/ChromeNodeList.cpp b/dom/base/ChromeNodeList.cpp
new file mode 100644
index 000000000..ffa101971
--- /dev/null
+++ b/dom/base/ChromeNodeList.cpp
@@ -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/. */
+
+#include "mozilla/dom/ChromeNodeList.h"
+#include "mozilla/dom/ChromeNodeListBinding.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+already_AddRefed<ChromeNodeList>
+ChromeNodeList::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
+ nsIDocument* root = win ? win->GetExtantDoc() : nullptr;
+ RefPtr<ChromeNodeList> list = new ChromeNodeList(root);
+ return list.forget();
+}
+
+JSObject*
+ChromeNodeList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ChromeNodeListBinding::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.Throw(NS_ERROR_DOM_TYPE_ERR);
+ return;
+ }
+
+ AppendElement(aNode.AsContent());
+}
+
+void
+ChromeNodeList::Remove(nsINode& aNode, ErrorResult& aError)
+{
+ if (!aNode.IsContent()) {
+ aError.Throw(NS_ERROR_DOM_TYPE_ERR);
+ return;
+ }
+
+ RemoveElement(aNode.AsContent());
+}
diff --git a/dom/base/ChromeNodeList.h b/dom/base/ChromeNodeList.h
new file mode 100644
index 000000000..9908808ac
--- /dev/null
+++ b/dom/base/ChromeNodeList.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/. */
+
+#include "nsCOMArray.h"
+#include "nsContentList.h"
+
+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, ErrorResult& aRv);
+
+ 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 000000000..3cde261f0
--- /dev/null
+++ b/dom/base/ChromeUtils.cpp
@@ -0,0 +1,203 @@
+/* -*- 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 "ChromeUtils.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/BasePrincipal.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ void
+ThreadSafeChromeUtils::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
+ThreadSafeChromeUtils::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
+ThreadSafeChromeUtils::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.ComputeLengthAndData();
+ length = buffer.Length();
+ data = buffer.Data();
+ } else if (aSource.IsArrayBufferView()) {
+ const ArrayBufferView& view = aSource.GetAsArrayBufferView();
+ view.ComputeLengthAndData();
+ 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
+ThreadSafeChromeUtils::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::OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
+ const dom::OriginAttributesDictionary& aAttrs,
+ nsCString& aSuffix)
+
+{
+ GenericOriginAttributes attrs(aAttrs);
+ attrs.CreateSuffix(aSuffix);
+}
+
+/* static */ bool
+ChromeUtils::OriginAttributesMatchPattern(dom::GlobalObject& aGlobal,
+ const dom::OriginAttributesDictionary& aAttrs,
+ const dom::OriginAttributesPatternDictionary& aPattern)
+{
+ GenericOriginAttributes attrs(aAttrs);
+ OriginAttributesPattern pattern(aPattern);
+ return pattern.Matches(attrs);
+}
+
+/* static */ void
+ChromeUtils::CreateOriginAttributesFromOrigin(dom::GlobalObject& aGlobal,
+ const nsAString& aOrigin,
+ dom::OriginAttributesDictionary& aAttrs,
+ ErrorResult& aRv)
+{
+ GenericOriginAttributes attrs;
+ nsAutoCString suffix;
+ if (!attrs.PopulateFromOrigin(NS_ConvertUTF16toUTF8(aOrigin), suffix)) {
+ 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.mAddonId == aB.mAddonId &&
+ aA.mAppId == aB.mAppId &&
+ aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
+ aA.mUserContextId == aB.mUserContextId &&
+ aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
+}
+
+/* static */ bool
+ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB)
+{
+ return aA.mAppId == aB.mAppId &&
+ aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
+ aA.mUserContextId == aB.mUserContextId &&
+ aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h
new file mode 100644
index 000000000..051217c84
--- /dev/null
+++ b/dom/base/ChromeUtils.h
@@ -0,0 +1,103 @@
+/* -*- 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 mozilla_dom_ChromeUtils__
+#define mozilla_dom_ChromeUtils__
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/dom/ThreadSafeChromeUtilsBinding.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+
+namespace devtools {
+class HeapSnapshot;
+} // namespace devtools
+
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+
+class ThreadSafeChromeUtils
+{
+public:
+ // 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 already_AddRefed<devtools::HeapSnapshot> ReadHeapSnapshot(GlobalObject& global,
+ const nsAString& filePath,
+ ErrorResult& rv);
+
+ 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);
+};
+
+class ChromeUtils : public ThreadSafeChromeUtils
+{
+public:
+ 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
+ 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
+ IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB);
+};
+
+} // 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 000000000..0a9305349
--- /dev/null
+++ b/dom/base/Comment.cpp
@@ -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/. */
+
+/*
+ * Implementations of DOM Core's nsIDOMComment node.
+ */
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/CommentBinding.h"
+#include "mozilla/IntegerPrintfMacros.h"
+
+using namespace mozilla;
+using namespace dom;
+
+namespace mozilla {
+namespace dom {
+
+Comment::~Comment()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(Comment, nsGenericDOMDataNode, nsIDOMNode,
+ nsIDOMCharacterData, nsIDOMComment)
+
+bool
+Comment::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~(eCONTENT | eCOMMENT | eDATA_NODE));
+}
+
+nsGenericDOMDataNode*
+Comment::CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo, bool aCloneText) const
+{
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ Comment *it = new Comment(ni.forget());
+ if (it && aCloneText) {
+ it->mText = mText;
+ }
+
+ return it;
+}
+
+#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 CommentBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/Comment.h b/dom/base/Comment.h
new file mode 100644
index 000000000..0ee1c498a
--- /dev/null
+++ b/dom/base/Comment.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_dom_Comment_h
+#define mozilla_dom_Comment_h
+
+#include "mozilla/Attributes.h"
+#include "nsIDOMComment.h"
+#include "nsGenericDOMDataNode.h"
+
+namespace mozilla {
+namespace dom {
+
+class Comment final : public nsGenericDOMDataNode,
+ public nsIDOMComment
+{
+private:
+ void Init()
+ {
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::COMMENT_NODE,
+ "Bad NodeType in aNodeInfo");
+ }
+
+ virtual ~Comment();
+
+public:
+ explicit Comment(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsGenericDOMDataNode(aNodeInfo)
+ {
+ Init();
+ }
+
+ explicit Comment(nsNodeInfoManager* aNodeInfoManager)
+ : nsGenericDOMDataNode(aNodeInfoManager->GetCommentNodeInfo())
+ {
+ Init();
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMNode
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+
+ // nsIDOMCharacterData
+ NS_FORWARD_NSIDOMCHARACTERDATA(nsGenericDOMDataNode::)
+ using nsGenericDOMDataNode::SetData; // Prevent hiding overloaded virtual function.
+
+ // nsIDOMComment
+ // Empty interface
+
+ // nsINode
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+
+ virtual nsGenericDOMDataNode* CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo,
+ bool aCloneText) const override;
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+#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 dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Comment_h
diff --git a/dom/base/Crypto.cpp b/dom/base/Crypto.cpp
new file mode 100644
index 000000000..4de6409ac
--- /dev/null
+++ b/dom/base/Crypto.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 "Crypto.h"
+#include "jsfriendapi.h"
+#include "nsCOMPtr.h"
+#include "nsIRandomGenerator.h"
+#include "MainThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/CryptoBinding.h"
+#include "nsServiceManagerUtils.h"
+
+using mozilla::dom::ContentChild;
+
+namespace mozilla {
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Crypto)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMCrypto)
+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()
+{
+ MOZ_COUNT_CTOR(Crypto);
+}
+
+Crypto::~Crypto()
+{
+ MOZ_COUNT_DTOR(Crypto);
+}
+
+void
+Crypto::Init(nsIGlobalObject* aParent)
+{
+ mParent = do_QueryInterface(aParent);
+ MOZ_ASSERT(mParent);
+}
+
+/* virtual */ JSObject*
+Crypto::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return CryptoBinding::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());
+
+ if (JS_IsTypedArrayObject(view) && JS_GetTypedArraySharedness(view)) {
+ // Throw if the object is mapping shared memory (must opt in).
+ aRv.ThrowTypeError<MSG_TYPEDARRAY_IS_SHARED>(NS_LITERAL_STRING("Argument of Crypto.getRandomValues"));
+ return;
+ }
+
+ // 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:
+ break;
+ default:
+ aRv.Throw(NS_ERROR_DOM_TYPE_MISMATCH_ERR);
+ return;
+ }
+
+ aArray.ComputeLengthAndData();
+ 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);
+}
+
+SubtleCrypto*
+Crypto::Subtle()
+{
+ if(!mSubtle) {
+ mSubtle = new SubtleCrypto(GetParentObject());
+ }
+ return mSubtle;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/Crypto.h b/dom/base/Crypto.h
new file mode 100644
index 000000000..e6f8969d7
--- /dev/null
+++ b/dom/base/Crypto.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_Crypto_h
+#define mozilla_dom_Crypto_h
+
+#include "nsIDOMCrypto.h"
+#include "mozilla/dom/SubtleCrypto.h"
+#include "nsIGlobalObject.h"
+
+#include "nsWrapperCache.h"
+#include "mozilla/dom/TypedArray.h"
+#define NS_DOMCRYPTO_CID \
+ {0x929d9320, 0x251e, 0x11d4, { 0x8a, 0x7c, 0x00, 0x60, 0x08, 0xc8, 0x44, 0xc3} }
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class Crypto : public nsIDOMCrypto,
+ public nsWrapperCache
+{
+protected:
+ virtual ~Crypto();
+
+public:
+ Crypto();
+
+ NS_DECL_NSIDOMCRYPTO
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Crypto)
+
+ void
+ GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+
+ SubtleCrypto*
+ Subtle();
+
+ // WebIDL
+
+ 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 000000000..00ee3d42f
--- /dev/null
+++ b/dom/base/CustomElementRegistry.cpp
@@ -0,0 +1,857 @@
+/* -*- 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/CustomElementRegistry.h"
+
+#include "mozilla/dom/CustomElementRegistryBinding.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/WebComponentsBinding.h"
+#include "nsIParserService.h"
+#include "jsapi.h"
+
+namespace mozilla {
+namespace dom {
+
+void
+CustomElementCallback::Call()
+{
+ ErrorResult rv;
+ switch (mType) {
+ case nsIDocument::eCreated:
+ {
+ // For the duration of this callback invocation, the element is being created
+ // flag must be set to true.
+ mOwnerData->mElementIsBeingCreated = true;
+
+ // The callback hasn't actually been invoked yet, but we need to flip
+ // this now in order to enqueue the attached callback. This is a spec
+ // bug (w3c bug 27437).
+ mOwnerData->mCreatedCallbackInvoked = true;
+
+ // If ELEMENT is in a document and this document has a browsing context,
+ // enqueue attached callback for ELEMENT.
+ nsIDocument* document = mThisObject->GetComposedDoc();
+ if (document && document->GetDocShell()) {
+ nsContentUtils::EnqueueLifecycleCallback(
+ document, nsIDocument::eAttached, mThisObject);
+ }
+
+ static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
+ mOwnerData->mElementIsBeingCreated = false;
+ break;
+ }
+ case nsIDocument::eAttached:
+ static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv);
+ break;
+ case nsIDocument::eDetached:
+ static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv);
+ break;
+ case nsIDocument::eAttributeChanged:
+ static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
+ mArgs.name, mArgs.oldValue, mArgs.newValue, rv);
+ 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);
+}
+
+CustomElementCallback::CustomElementCallback(Element* aThisObject,
+ nsIDocument::ElementCallbackType aCallbackType,
+ mozilla::dom::CallbackFunction* aCallback,
+ CustomElementData* aOwnerData)
+ : mThisObject(aThisObject),
+ mCallback(aCallback),
+ mType(aCallbackType),
+ mOwnerData(aOwnerData)
+{
+}
+
+CustomElementData::CustomElementData(nsIAtom* aType)
+ : mType(aType),
+ mCurrentCallback(-1),
+ mElementIsBeingCreated(false),
+ mCreatedCallbackInvoked(true),
+ mAssociatedMicroTask(-1)
+{
+}
+
+void
+CustomElementData::RunCallbackQueue()
+{
+ // Note: It's possible to re-enter this method.
+ while (static_cast<uint32_t>(++mCurrentCallback) < mCallbackQueue.Length()) {
+ mCallbackQueue[mCurrentCallback]->Call();
+ }
+
+ mCallbackQueue.Clear();
+ mCurrentCallback = -1;
+}
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_CLASS(CustomElementRegistry)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry)
+ tmp->mCustomDefinitions.Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap)
+ 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)
+ for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<LifecycleCallbacks>& callbacks = iter.UserData()->mCallbacks;
+
+ if (callbacks->mAttributeChangedCallback.WasPassed()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mCustomDefinitions->mCallbacks->mAttributeChangedCallback");
+ cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value());
+ }
+
+ if (callbacks->mCreatedCallback.WasPassed()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mCustomDefinitions->mCallbacks->mCreatedCallback");
+ cb.NoteXPCOMChild(callbacks->mCreatedCallback.Value());
+ }
+
+ if (callbacks->mAttachedCallback.WasPassed()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mCustomDefinitions->mCallbacks->mAttachedCallback");
+ cb.NoteXPCOMChild(callbacks->mAttachedCallback.Value());
+ }
+
+ if (callbacks->mDetachedCallback.WasPassed()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mCustomDefinitions->mCallbacks->mDetachedCallback");
+ cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value());
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CustomElementRegistry)
+ for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) {
+ aCallbacks.Trace(&iter.UserData()->mConstructor,
+ "mCustomDefinitions constructor",
+ aClosure);
+ aCallbacks.Trace(&iter.UserData()->mPrototype,
+ "mCustomDefinitions prototype",
+ 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
+
+/* static */ bool
+CustomElementRegistry::IsCustomElementEnabled(JSContext* aCx, JSObject* aObject)
+{
+ return Preferences::GetBool("dom.webcomponents.customelements.enabled") ||
+ Preferences::GetBool("dom.webcomponents.enabled");
+}
+
+/* static */ already_AddRefed<CustomElementRegistry>
+CustomElementRegistry::Create(nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+
+ if (!aWindow->GetDocShell()) {
+ return nullptr;
+ }
+
+ if (!IsCustomElementEnabled()) {
+ return nullptr;
+ }
+
+ RefPtr<CustomElementRegistry> customElementRegistry =
+ new CustomElementRegistry(aWindow);
+ return customElementRegistry.forget();
+}
+
+/* static */ void
+CustomElementRegistry::ProcessTopElementQueue()
+{
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ nsTArray<RefPtr<CustomElementData>>& stack = *sProcessingStack;
+ uint32_t firstQueue = stack.LastIndexOf((CustomElementData*) nullptr);
+
+ for (uint32_t i = firstQueue + 1; i < stack.Length(); ++i) {
+ // Callback queue may have already been processed in an earlier
+ // element queue or in an element queue that was popped
+ // off more recently.
+ if (stack[i]->mAssociatedMicroTask != -1) {
+ stack[i]->RunCallbackQueue();
+ stack[i]->mAssociatedMicroTask = -1;
+ }
+ }
+
+ // If this was actually the base element queue, don't bother trying to pop
+ // the first "queue" marker (sentinel).
+ if (firstQueue != 0) {
+ stack.SetLength(firstQueue);
+ } else {
+ // Don't pop sentinel for base element queue.
+ stack.SetLength(1);
+ }
+}
+
+/* static */ void
+CustomElementRegistry::XPCOMShutdown()
+{
+ sProcessingStack.reset();
+}
+
+/* static */ Maybe<nsTArray<RefPtr<CustomElementData>>>
+CustomElementRegistry::sProcessingStack;
+
+CustomElementRegistry::CustomElementRegistry(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+ , mIsCustomDefinitionRunning(false)
+{
+ mozilla::HoldJSObjects(this);
+
+ if (!sProcessingStack) {
+ sProcessingStack.emplace();
+ // Add the base queue sentinel to the processing stack.
+ sProcessingStack->AppendElement((CustomElementData*) nullptr);
+ }
+}
+
+CustomElementRegistry::~CustomElementRegistry()
+{
+ mozilla::DropJSObjects(this);
+}
+
+CustomElementDefinition*
+CustomElementRegistry::LookupCustomElementDefinition(const nsAString& aLocalName,
+ const nsAString* aIs) const
+{
+ nsCOMPtr<nsIAtom> localNameAtom = NS_Atomize(aLocalName);
+ nsCOMPtr<nsIAtom> typeAtom = aIs ? NS_Atomize(*aIs) : localNameAtom;
+
+ CustomElementDefinition* data = mCustomDefinitions.Get(typeAtom);
+ if (data && data->mLocalName == localNameAtom) {
+ return data;
+ }
+
+ return nullptr;
+}
+
+void
+CustomElementRegistry::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName)
+{
+ 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">.
+ nsCOMPtr<nsIAtom> typeName = aTypeName;
+ if (!typeName) {
+ typeName = info->NameAtom();
+ }
+
+ if (mCustomDefinitions.Get(typeName)) {
+ return;
+ }
+
+ nsTArray<nsWeakPtr>* unresolved = mCandidatesMap.LookupOrAdd(typeName);
+ nsWeakPtr* elem = unresolved->AppendElement();
+ *elem = do_GetWeakReference(aElement);
+ aElement->AddStates(NS_EVENT_STATE_UNRESOLVED);
+
+ return;
+}
+
+void
+CustomElementRegistry::SetupCustomElement(Element* aElement,
+ const nsAString* aTypeExtension)
+{
+ nsCOMPtr<nsIAtom> tagAtom = aElement->NodeInfo()->NameAtom();
+ nsCOMPtr<nsIAtom> typeAtom = aTypeExtension ?
+ NS_Atomize(*aTypeExtension) : tagAtom;
+
+ if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) {
+ // Custom element setup in the parser happens after the "is"
+ // attribute is added.
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true);
+ }
+
+ CustomElementDefinition* data = LookupCustomElementDefinition(
+ aElement->NodeInfo()->LocalName(), aTypeExtension);
+
+ if (!data) {
+ // The type extension doesn't exist in the registry,
+ // thus we don't need to enqueue callback or adjust
+ // the "is" attribute, but it is possibly an upgrade candidate.
+ RegisterUnresolvedElement(aElement, typeAtom);
+ return;
+ }
+
+ if (data->mLocalName != tagAtom) {
+ // The element doesn't match the local name for the
+ // definition, thus the element isn't a custom element
+ // and we don't need to do anything more.
+ return;
+ }
+
+ // Enqueuing the created callback will set the CustomElementData on the
+ // element, causing prototype swizzling to occur in Element::WrapObject.
+ EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data);
+}
+
+void
+CustomElementRegistry::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
+ Element* aCustomElement,
+ LifecycleCallbackArgs* aArgs,
+ CustomElementDefinition* aDefinition)
+{
+ CustomElementData* elementData = aCustomElement->GetCustomElementData();
+
+ // Let DEFINITION be ELEMENT's definition
+ CustomElementDefinition* definition = aDefinition;
+ if (!definition) {
+ mozilla::dom::NodeInfo* info = aCustomElement->NodeInfo();
+
+ // Make sure we get the correct definition in case the element
+ // is a extended custom element e.g. <button is="x-button">.
+ nsCOMPtr<nsIAtom> typeAtom = elementData ?
+ elementData->mType.get() : info->NameAtom();
+
+ definition = mCustomDefinitions.Get(typeAtom);
+ if (!definition || definition->mLocalName != info->NameAtom()) {
+ // Trying to enqueue a callback for an element that is not
+ // a custom element. We are done, nothing to do.
+ return;
+ }
+ }
+
+ if (!elementData) {
+ // Create the custom element data the first time
+ // that we try to enqueue a callback.
+ elementData = new CustomElementData(definition->mType);
+ // aCustomElement takes ownership of elementData
+ aCustomElement->SetCustomElementData(elementData);
+ MOZ_ASSERT(aType == nsIDocument::eCreated,
+ "First callback should be the created callback");
+ }
+
+ // Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
+ CallbackFunction* func = nullptr;
+ switch (aType) {
+ case nsIDocument::eCreated:
+ if (definition->mCallbacks->mCreatedCallback.WasPassed()) {
+ func = definition->mCallbacks->mCreatedCallback.Value();
+ }
+ break;
+
+ case nsIDocument::eAttached:
+ if (definition->mCallbacks->mAttachedCallback.WasPassed()) {
+ func = definition->mCallbacks->mAttachedCallback.Value();
+ }
+ break;
+
+ case nsIDocument::eDetached:
+ if (definition->mCallbacks->mDetachedCallback.WasPassed()) {
+ func = definition->mCallbacks->mDetachedCallback.Value();
+ }
+ break;
+
+ case nsIDocument::eAttributeChanged:
+ if (definition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
+ func = definition->mCallbacks->mAttributeChangedCallback.Value();
+ }
+ break;
+ }
+
+ // If there is no such callback, stop.
+ if (!func) {
+ return;
+ }
+
+ if (aType == nsIDocument::eCreated) {
+ elementData->mCreatedCallbackInvoked = false;
+ } else if (!elementData->mCreatedCallbackInvoked) {
+ // Callbacks other than created callback must not be enqueued
+ // until after the created callback has been invoked.
+ return;
+ }
+
+ // Add CALLBACK to ELEMENT's callback queue.
+ CustomElementCallback* callback = new CustomElementCallback(aCustomElement,
+ aType,
+ func,
+ elementData);
+ // Ownership of callback is taken by mCallbackQueue.
+ elementData->mCallbackQueue.AppendElement(callback);
+ if (aArgs) {
+ callback->SetArgs(*aArgs);
+ }
+
+ if (!elementData->mElementIsBeingCreated) {
+ CustomElementData* lastData =
+ sProcessingStack->SafeLastElement(nullptr);
+
+ // A new element queue needs to be pushed if the queue at the
+ // top of the stack is associated with another microtask level.
+ bool shouldPushElementQueue =
+ (!lastData || lastData->mAssociatedMicroTask <
+ static_cast<int32_t>(nsContentUtils::MicroTaskLevel()));
+
+ // Push a new element queue onto the processing stack when appropriate
+ // (when we enter a new microtask).
+ if (shouldPushElementQueue) {
+ // Push a sentinel value on the processing stack to mark the
+ // boundary between the element queues.
+ sProcessingStack->AppendElement((CustomElementData*) nullptr);
+ }
+
+ sProcessingStack->AppendElement(elementData);
+ elementData->mAssociatedMicroTask =
+ static_cast<int32_t>(nsContentUtils::MicroTaskLevel());
+
+ // Add a script runner to pop and process the element queue at
+ // the top of the processing stack.
+ if (shouldPushElementQueue) {
+ // Lifecycle callbacks enqueued by user agent implementation
+ // should be invoked prior to returning control back to script.
+ // Create a script runner to process the top of the processing
+ // stack as soon as it is safe to run script.
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction(&CustomElementRegistry::ProcessTopElementQueue);
+ nsContentUtils::AddScriptRunner(runnable);
+ }
+ }
+}
+
+void
+CustomElementRegistry::GetCustomPrototype(nsIAtom* aAtom,
+ JS::MutableHandle<JSObject*> aPrototype)
+{
+ mozilla::dom::CustomElementDefinition* definition = mCustomDefinitions.Get(aAtom);
+ if (definition) {
+ aPrototype.set(definition->mPrototype);
+ } else {
+ aPrototype.set(nullptr);
+ }
+}
+
+void
+CustomElementRegistry::UpgradeCandidates(JSContext* aCx,
+ nsIAtom* aKey,
+ CustomElementDefinition* aDefinition)
+{
+ nsAutoPtr<nsTArray<nsWeakPtr>> candidates;
+ mCandidatesMap.RemoveAndForget(aKey, candidates);
+ if (candidates) {
+ for (size_t i = 0; i < candidates->Length(); ++i) {
+ nsCOMPtr<Element> elem = do_QueryReferent(candidates->ElementAt(i));
+ if (!elem) {
+ continue;
+ }
+
+ elem->RemoveStates(NS_EVENT_STATE_UNRESOLVED);
+
+ // Make sure that the element name matches the name in the definition.
+ // (e.g. a definition for x-button extending button should match
+ // <button is="x-button"> but not <x-button>.
+ if (elem->NodeInfo()->NameAtom() != aDefinition->mLocalName) {
+ //Skip over this element because definition does not apply.
+ continue;
+ }
+
+ MOZ_ASSERT(elem->IsHTMLElement(aDefinition->mLocalName));
+ nsWrapperCache* cache;
+ CallQueryInterface(elem, &cache);
+ MOZ_ASSERT(cache, "Element doesn't support wrapper cache?");
+
+ // We want to set the custom prototype in the caller's comparment.
+ // In the case that element is in a different compartment,
+ // this will set the prototype on the element's wrapper and
+ // thus only visible in the wrapper's compartment.
+ JS::RootedObject wrapper(aCx);
+ JS::Rooted<JSObject*> prototype(aCx, aDefinition->mPrototype);
+ if ((wrapper = cache->GetWrapper()) && JS_WrapObject(aCx, &wrapper)) {
+ if (!JS_SetPrototype(aCx, wrapper, prototype)) {
+ continue;
+ }
+ }
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ elem->OwnerDoc(), nsIDocument::eCreated, elem, nullptr, aDefinition);
+ }
+ }
+}
+
+JSObject*
+CustomElementRegistry::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return CustomElementRegistryBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports* CustomElementRegistry::GetParentObject() const
+{
+ return mWindow;
+}
+
+static const char* kLifeCycleCallbackNames[] = {
+ "connectedCallback",
+ "disconnectedCallback",
+ "adoptedCallback",
+ "attributeChangedCallback",
+ // The life cycle callbacks from v0 spec.
+ "createdCallback",
+ "attachedCallback",
+ "detachedCallback"
+};
+
+static void
+CheckLifeCycleCallbacks(JSContext* aCx,
+ JS::Handle<JSObject*> aConstructor,
+ ErrorResult& aRv)
+{
+ for (size_t i = 0; i < ArrayLength(kLifeCycleCallbackNames); ++i) {
+ const char* callbackName = kLifeCycleCallbackNames[i];
+ JS::Rooted<JS::Value> callbackValue(aCx);
+ if (!JS_GetProperty(aCx, aConstructor, callbackName, &callbackValue)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ if (!callbackValue.isUndefined()) {
+ if (!callbackValue.isObject()) {
+ aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_ConvertASCIItoUTF16(callbackName));
+ return;
+ }
+ JS::Rooted<JSObject*> callback(aCx, &callbackValue.toObject());
+ if (!JS::IsCallable(callback)) {
+ aRv.ThrowTypeError<MSG_NOT_CALLABLE>(NS_ConvertASCIItoUTF16(callbackName));
+ return;
+ }
+ }
+ }
+}
+
+// https://html.spec.whatwg.org/multipage/scripting.html#element-definition
+void
+CustomElementRegistry::Define(const nsAString& aName,
+ Function& aFunctionConstructor,
+ const ElementDefinitionOptions& aOptions,
+ ErrorResult& aRv)
+{
+ aRv.MightThrowJSException();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mWindow))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JSContext *cx = jsapi.cx();
+ JS::Rooted<JSObject*> constructor(cx, aFunctionConstructor.Callable());
+
+ /**
+ * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort
+ * these steps.
+ */
+ // For now, all wrappers are constructable if they are callable. So we need to
+ // unwrap constructor to check it is really constructable.
+ JS::Rooted<JSObject*> constructorUnwrapped(cx, js::CheckedUnwrap(constructor));
+ 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;
+ }
+
+ if (!JS::IsConstructor(constructorUnwrapped)) {
+ aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>(NS_LITERAL_STRING("Argument 2 of CustomElementRegistry.define"));
+ return;
+ }
+
+ /**
+ * 2. If name is not a valid custom element name, then throw a "SyntaxError"
+ * DOMException and abort these steps.
+ */
+ nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName));
+ if (!nsContentUtils::IsCustomElementName(nameAtom)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ /**
+ * 3. If this CustomElementRegistry contains an entry with name name, then
+ * throw a "NotSupportedError" DOMException and abort these steps.
+ */
+ if (mCustomDefinitions.Get(nameAtom)) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ /**
+ * 4. If this CustomElementRegistry contains an entry with constructor constructor,
+ * then throw a "NotSupportedError" DOMException and abort these steps.
+ */
+ // TODO: Step 3 of HTMLConstructor also needs a way to look up definition by
+ // using constructor. So I plans to figure out a solution to support both of
+ // them in bug 1274159.
+
+ /**
+ * 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.
+ */
+ nsAutoString localName(aName);
+ if (aOptions.mExtends.WasPassed()) {
+ nsCOMPtr<nsIAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value()));
+ if (nsContentUtils::IsCustomElementName(extendsAtom)) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ nsIParserService* ps = nsContentUtils::GetParserService();
+ if (!ps) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // bgsound and multicol are unknown html element.
+ int32_t tag = ps->HTMLCaseSensitiveAtomTagToId(extendsAtom);
+ if (tag == eHTMLTag_userdefined ||
+ tag == eHTMLTag_bgsound ||
+ tag == eHTMLTag_multicol) {
+ 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.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ JS::Rooted<JSObject*> constructorPrototype(cx);
+ nsAutoPtr<LifecycleCallbacks> callbacksHolder(new LifecycleCallbacks());
+ { // Set mIsCustomDefinitionRunning.
+ /**
+ * 9. Set this CustomElementRegistry's element definition is running flag.
+ */
+ AutoSetRunningFlag as(this);
+
+ { // Enter constructor's compartment.
+ /**
+ * 10.1. Let prototype be Get(constructor, "prototype"). Rethrow any exceptions.
+ */
+ JSAutoCompartment ac(cx, constructor);
+ JS::Rooted<JS::Value> prototypev(cx);
+ // The .prototype on the constructor passed from document.registerElement
+ // is the "expando" of a wrapper. So we should get it from wrapper instead
+ // instead of underlying object.
+ if (!JS_GetProperty(cx, constructor, "prototype", &prototypev)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return;
+ }
+
+ /**
+ * 10.2. If Type(prototype) is not Object, then throw a TypeError exception.
+ */
+ if (!prototypev.isObject()) {
+ aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("constructor.prototype"));
+ return;
+ }
+
+ constructorPrototype = &prototypev.toObject();
+ } // Leave constructor's compartment.
+
+ JS::Rooted<JSObject*> constructorProtoUnwrapped(cx, js::CheckedUnwrap(constructorPrototype));
+ if (!constructorProtoUnwrapped) {
+ // If the caller's compartment does not have permission to access the
+ // unwrapped prototype then throw.
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ { // Enter constructorProtoUnwrapped's compartment
+ JSAutoCompartment ac(cx, constructorProtoUnwrapped);
+
+ /**
+ * 10.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.
+ * 10.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.
+ */
+ // Will do the same checking for the life cycle callbacks from v0 spec.
+ CheckLifeCycleCallbacks(cx, constructorProtoUnwrapped, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ /**
+ * 10.5. Let observedAttributes be an empty sequence<DOMString>.
+ * 10.6. 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.
+ */
+ // TODO: Bug 1293921 - Implement connected/disconnected/adopted/attributeChanged lifecycle callbacks for custom elements
+
+ // Note: We call the init from the constructorProtoUnwrapped's compartment
+ // here.
+ JS::RootedValue rootedv(cx, JS::ObjectValue(*constructorProtoUnwrapped));
+ if (!JS_WrapValue(cx, &rootedv) || !callbacksHolder->Init(cx, rootedv)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ } // Leave constructorProtoUnwrapped's compartment.
+ } // Unset mIsCustomDefinitionRunning
+
+ /**
+ * 11. 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.
+ nsCOMPtr<nsIAtom> localNameAtom(NS_Atomize(localName));
+ LifecycleCallbacks* callbacks = callbacksHolder.forget();
+ CustomElementDefinition* definition =
+ new CustomElementDefinition(nameAtom,
+ localNameAtom,
+ constructor,
+ constructorPrototype,
+ callbacks,
+ 0 /* TODO dependent on HTML imports. Bug 877072 */);
+
+ /**
+ * 12. Add definition to this CustomElementRegistry.
+ */
+ mCustomDefinitions.Put(nameAtom, definition);
+
+ /**
+ * 13. 14. 15. Upgrade candidates
+ */
+ // TODO: Bug 1299363 - Implement custom element v1 upgrade algorithm
+ UpgradeCandidates(cx, nameAtom, definition);
+
+ /**
+ * 16. 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->MaybeResolveWithUndefined();
+ }
+
+}
+
+void
+CustomElementRegistry::Get(JSContext* aCx, const nsAString& aName,
+ JS::MutableHandle<JS::Value> aRetVal)
+{
+ nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName));
+ CustomElementDefinition* data = mCustomDefinitions.Get(nameAtom);
+
+ if (!data) {
+ aRetVal.setUndefined();
+ return;
+ }
+
+ aRetVal.setObject(*data->mConstructor);
+ return;
+}
+
+already_AddRefed<Promise>
+CustomElementRegistry::WhenDefined(const nsAString& aName, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIAtom> nameAtom(NS_Atomize(aName));
+ if (!nsContentUtils::IsCustomElementName(nameAtom)) {
+ promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
+ return promise.forget();
+ }
+
+ if (mCustomDefinitions.Get(nameAtom)) {
+ promise->MaybeResolve(JS::UndefinedHandleValue);
+ return promise.forget();
+ }
+
+ if (mWhenDefinedPromiseMap.Contains(nameAtom)) {
+ mWhenDefinedPromiseMap.Get(nameAtom, getter_AddRefs(promise));
+ } else {
+ mWhenDefinedPromiseMap.Put(nameAtom, promise);
+ }
+
+ return promise.forget();
+}
+
+CustomElementDefinition::CustomElementDefinition(nsIAtom* aType,
+ nsIAtom* aLocalName,
+ JSObject* aConstructor,
+ JSObject* aPrototype,
+ LifecycleCallbacks* aCallbacks,
+ uint32_t aDocOrder)
+ : mType(aType),
+ mLocalName(aLocalName),
+ mConstructor(aConstructor),
+ mPrototype(aPrototype),
+ mCallbacks(aCallbacks),
+ mDocOrder(aDocOrder)
+{
+}
+
+} // namespace dom
+} // namespace mozilla \ No newline at end of file
diff --git a/dom/base/CustomElementRegistry.h b/dom/base/CustomElementRegistry.h
new file mode 100644
index 000000000..ff803a054
--- /dev/null
+++ b/dom/base/CustomElementRegistry.h
@@ -0,0 +1,259 @@
+/* -*- 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_CustomElementRegistry_h
+#define mozilla_dom_CustomElementRegistry_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/FunctionBinding.h"
+
+class nsDocument;
+
+namespace mozilla {
+namespace dom {
+
+struct CustomElementData;
+struct ElementDefinitionOptions;
+struct LifecycleCallbacks;
+class CallbackFunction;
+class Function;
+class Promise;
+
+struct LifecycleCallbackArgs
+{
+ nsString name;
+ nsString oldValue;
+ nsString newValue;
+};
+
+class CustomElementCallback
+{
+public:
+ CustomElementCallback(Element* aThisObject,
+ nsIDocument::ElementCallbackType aCallbackType,
+ CallbackFunction* aCallback,
+ CustomElementData* aOwnerData);
+ void Traverse(nsCycleCollectionTraversalCallback& aCb) const;
+ void Call();
+ void SetArgs(LifecycleCallbackArgs& aArgs)
+ {
+ MOZ_ASSERT(mType == nsIDocument::eAttributeChanged,
+ "Arguments are only used by attribute changed callback.");
+ mArgs = aArgs;
+ }
+
+private:
+ // The this value to use for invocation of the callback.
+ RefPtr<Element> mThisObject;
+ RefPtr<CallbackFunction> mCallback;
+ // The type of callback (eCreated, eAttached, etc.)
+ nsIDocument::ElementCallbackType mType;
+ // Arguments to be passed to the callback,
+ // used by the attribute changed callback.
+ LifecycleCallbackArgs mArgs;
+ // CustomElementData that contains this callback in the
+ // callback queue.
+ CustomElementData* mOwnerData;
+};
+
+// Each custom element has an associated callback queue and an element is
+// being created flag.
+struct CustomElementData
+{
+ NS_INLINE_DECL_REFCOUNTING(CustomElementData)
+
+ explicit CustomElementData(nsIAtom* aType);
+ // Objects in this array are transient and empty after each microtask
+ // checkpoint.
+ nsTArray<nsAutoPtr<CustomElementCallback>> mCallbackQueue;
+ // Custom element type, for <button is="x-button"> or <x-button>
+ // this would be x-button.
+ nsCOMPtr<nsIAtom> mType;
+ // The callback that is next to be processed upon calling RunCallbackQueue.
+ int32_t mCurrentCallback;
+ // Element is being created flag as described in the custom elements spec.
+ bool mElementIsBeingCreated;
+ // Flag to determine if the created callback has been invoked, thus it
+ // determines if other callbacks can be enqueued.
+ bool mCreatedCallbackInvoked;
+ // The microtask level associated with the callbacks in the callback queue,
+ // it is used to determine if a new queue needs to be pushed onto the
+ // processing stack.
+ int32_t mAssociatedMicroTask;
+
+ // Empties the callback queue.
+ void RunCallbackQueue();
+
+private:
+ virtual ~CustomElementData() {}
+};
+
+// The required information for a custom element as defined in:
+// https://html.spec.whatwg.org/multipage/scripting.html#custom-element-definition
+struct CustomElementDefinition
+{
+ CustomElementDefinition(nsIAtom* aType,
+ nsIAtom* aLocalName,
+ JSObject* aConstructor,
+ JSObject* aPrototype,
+ mozilla::dom::LifecycleCallbacks* aCallbacks,
+ uint32_t aDocOrder);
+
+ // The type (name) for this custom element.
+ nsCOMPtr<nsIAtom> mType;
+
+ // The localname to (e.g. <button is=type> -- this would be button).
+ nsCOMPtr<nsIAtom> mLocalName;
+
+ // The custom element constructor.
+ JS::Heap<JSObject *> mConstructor;
+
+ // The prototype to use for new custom elements of this type.
+ JS::Heap<JSObject *> mPrototype;
+
+ // The lifecycle callbacks to call for this custom element.
+ nsAutoPtr<mozilla::dom::LifecycleCallbacks> mCallbacks;
+
+ // A construction stack.
+ // TODO: Bug 1287348 - Implement construction stack for upgrading an element
+
+ // The document custom element order.
+ uint32_t mDocOrder;
+};
+
+class CustomElementRegistry final : public nsISupports,
+ public nsWrapperCache
+{
+ // Allow nsDocument to access mCustomDefinitions and mCandidatesMap.
+ friend class ::nsDocument;
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CustomElementRegistry)
+
+public:
+ static bool IsCustomElementEnabled(JSContext* aCx = nullptr,
+ JSObject* aObject = nullptr);
+ static already_AddRefed<CustomElementRegistry> Create(nsPIDOMWindowInner* aWindow);
+ static void ProcessTopElementQueue();
+
+ static void XPCOMShutdown();
+
+ /**
+ * Looking up a custom element definition.
+ * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
+ */
+ CustomElementDefinition* LookupCustomElementDefinition(
+ const nsAString& aLocalName, const nsAString* aIs = nullptr) const;
+
+ /**
+ * Enqueue created callback or register upgrade candidate for
+ * newly created custom elements, possibly extending an existing type.
+ * ex. <x-button>, <button is="x-button> (type extension)
+ */
+ void SetupCustomElement(Element* aElement, const nsAString* aTypeExtension);
+
+ void EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
+ Element* aCustomElement,
+ LifecycleCallbackArgs* aArgs,
+ CustomElementDefinition* aDefinition);
+
+ void GetCustomPrototype(nsIAtom* aAtom,
+ JS::MutableHandle<JSObject*> aPrototype);
+
+private:
+ explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
+ ~CustomElementRegistry();
+
+ /**
+ * Registers an unresolved custom element that is a candidate for
+ * upgrade when the definition is registered via registerElement.
+ * |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,
+ nsIAtom* aTypeName = nullptr);
+
+ void UpgradeCandidates(JSContext* aCx,
+ nsIAtom* aKey,
+ CustomElementDefinition* aDefinition);
+
+ typedef nsClassHashtable<nsISupportsHashKey, CustomElementDefinition>
+ DefinitionMap;
+ typedef nsClassHashtable<nsISupportsHashKey, nsTArray<nsWeakPtr>>
+ CandidateMap;
+
+ // Hashtable for custom element definitions in web components.
+ // Custom prototypes are stored in the compartment where
+ // registerElement was called.
+ DefinitionMap mCustomDefinitions;
+
+ typedef nsRefPtrHashtable<nsISupportsHashKey, 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;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ // Array representing the processing stack in the custom elements
+ // specification. The processing stack is conceptually a stack of
+ // element queues. Each queue is represented by a sequence of
+ // CustomElementData in this array, separated by nullptr that
+ // represent the boundaries of the items in the stack. The first
+ // queue in the stack is the base element queue.
+ static mozilla::Maybe<nsTArray<RefPtr<CustomElementData>>> sProcessingStack;
+
+ // It is used to prevent reentrant invocations of element definition.
+ bool mIsCustomDefinitionRunning;
+
+private:
+ class MOZ_RAII AutoSetRunningFlag final {
+ public:
+ explicit AutoSetRunningFlag(CustomElementRegistry* aRegistry)
+ : mRegistry(aRegistry)
+ {
+ MOZ_ASSERT(!mRegistry->mIsCustomDefinitionRunning,
+ "IsCustomDefinitionRunning flag should be initially false");
+ mRegistry->mIsCustomDefinitionRunning = true;
+ }
+
+ ~AutoSetRunningFlag() {
+ mRegistry->mIsCustomDefinitionRunning = false;
+ }
+
+ private:
+ CustomElementRegistry* mRegistry;
+ };
+
+public:
+ nsISupports* GetParentObject() const;
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void Define(const nsAString& aName, Function& aFunctionConstructor,
+ const ElementDefinitionOptions& aOptions, ErrorResult& aRv);
+
+ void Get(JSContext* cx, const nsAString& name,
+ JS::MutableHandle<JS::Value> aRetVal);
+
+ already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+
+#endif // mozilla_dom_CustomElementRegistry_h
diff --git a/dom/base/DOMCursor.cpp b/dom/base/DOMCursor.cpp
new file mode 100644
index 000000000..1f92ac9cc
--- /dev/null
+++ b/dom/base/DOMCursor.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 "DOMCursor.h"
+#include "mozilla/dom/DOMCursorBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMCursor, DOMRequest,
+ mCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMCursor)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMDOMCursor)
+NS_INTERFACE_MAP_END_INHERITING(DOMRequest)
+
+NS_IMPL_ADDREF_INHERITED(DOMCursor, DOMRequest)
+NS_IMPL_RELEASE_INHERITED(DOMCursor, DOMRequest)
+
+DOMCursor::DOMCursor(nsPIDOMWindowInner* aWindow, nsICursorContinueCallback* aCallback)
+ : DOMRequest(aWindow)
+ , mCallback(aCallback)
+ , mFinished(false)
+{
+}
+
+DOMCursor::DOMCursor(nsIGlobalObject* aGlobal, nsICursorContinueCallback* aCallback)
+ : DOMRequest(aGlobal)
+ , mCallback(aCallback)
+ , mFinished(false)
+{
+}
+
+void
+DOMCursor::Reset()
+{
+ MOZ_ASSERT(!mFinished);
+
+ // Reset the request state so we can FireSuccess() again.
+ mResult.setUndefined();
+ mDone = false;
+}
+
+void
+DOMCursor::FireDone()
+{
+ Reset();
+ mFinished = true;
+ FireSuccess(JS::UndefinedHandleValue);
+}
+
+NS_IMETHODIMP
+DOMCursor::GetDone(bool *aDone)
+{
+ *aDone = Done();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMCursor::Continue()
+{
+ ErrorResult rv;
+ Continue(rv);
+ return rv.StealNSResult();
+}
+
+void
+DOMCursor::Continue(ErrorResult& aRv)
+{
+ MOZ_ASSERT(mCallback, "If you're creating your own cursor class with no callback, you should override Continue()");
+
+ // We need to have a result here because we must be in a 'success' state.
+ if (mResult.isUndefined()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ Reset();
+ mCallback->HandleContinue();
+}
+
+/* virtual */ JSObject*
+DOMCursor::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMCursorBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/DOMCursor.h b/dom/base/DOMCursor.h
new file mode 100644
index 000000000..715cde021
--- /dev/null
+++ b/dom/base/DOMCursor.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_domcursor_h__
+#define mozilla_dom_domcursor_h__
+
+#include "nsIDOMDOMCursor.h"
+#include "DOMRequest.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMCursor : public DOMRequest
+ , public nsIDOMDOMCursor
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMDOMCURSOR
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMCursor,
+ DOMRequest)
+
+ DOMCursor(nsPIDOMWindowInner* aWindow, nsICursorContinueCallback *aCallback);
+ DOMCursor(nsIGlobalObject* aGlobal, nsICursorContinueCallback *aCallback);
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ bool Done() const
+ {
+ return mFinished;
+ }
+ virtual void Continue(ErrorResult& aRv);
+
+ void Reset();
+ void FireDone();
+
+protected:
+ ~DOMCursor() {}
+
+private:
+ DOMCursor() = delete;
+ // Calling Then() on DOMCursor is a mistake, since the DOMCursor object
+ // should not have a .then() method from JS' point of view.
+ already_AddRefed<mozilla::dom::Promise>
+ Then(JSContext* aCx, AnyCallback* aResolveCallback,
+ AnyCallback* aRejectCallback, ErrorResult& aRv) = delete;
+
+ nsCOMPtr<nsICursorContinueCallback> mCallback;
+ bool mFinished;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_domcursor_h__ */
diff --git a/dom/base/DOMError.cpp b/dom/base/DOMError.cpp
new file mode 100644
index 000000000..1566a03c8
--- /dev/null
+++ b/dom/base/DOMError.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 "mozilla/dom/DOMError.h"
+#include "mozilla/dom/DOMErrorBinding.h"
+#include "mozilla/dom/DOMException.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMError, mWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMError)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMError)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMError)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(DOMError)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+DOMError::DOMError(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+{
+}
+
+DOMError::DOMError(nsPIDOMWindowInner* aWindow, nsresult aValue)
+ : mWindow(aWindow)
+{
+ nsCString name, message;
+ NS_GetNameAndMessageForDOMNSResult(aValue, name, message);
+
+ CopyUTF8toUTF16(name, mName);
+ CopyUTF8toUTF16(message, mMessage);
+}
+
+DOMError::DOMError(nsPIDOMWindowInner* aWindow, const nsAString& aName)
+ : mWindow(aWindow)
+ , mName(aName)
+{
+}
+
+DOMError::DOMError(nsPIDOMWindowInner* aWindow, const nsAString& aName,
+ const nsAString& aMessage)
+ : mWindow(aWindow)
+ , mName(aName)
+ , mMessage(aMessage)
+{
+}
+
+DOMError::~DOMError()
+{
+}
+
+JSObject*
+DOMError::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMErrorBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<DOMError>
+DOMError::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aName, const nsAString& aMessage,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
+
+ // Window is null for chrome code.
+
+ RefPtr<DOMError> ret = new DOMError(window, aName, aMessage);
+ return ret.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/DOMError.h b/dom/base/DOMError.h
new file mode 100644
index 000000000..d8ffdbf0d
--- /dev/null
+++ b/dom/base/DOMError.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/. */
+
+#ifndef mozilla_dom_domerror_h__
+#define mozilla_dom_domerror_h__
+
+#include "mozilla/Attributes.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#define DOMERROR_IID \
+{ 0x220cb63f, 0xa37d, 0x4ba4, \
+ { 0x8e, 0x31, 0xfc, 0xde, 0xec, 0x48, 0xe1, 0x66 } }
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+class DOMError : public nsISupports,
+ public nsWrapperCache
+{
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsString mName;
+ nsString mMessage;
+
+protected:
+ virtual ~DOMError();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMError)
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(DOMERROR_IID)
+
+ // aWindow can be null if this DOMError is not associated with a particular
+ // window.
+
+ explicit DOMError(nsPIDOMWindowInner* aWindow);
+
+ DOMError(nsPIDOMWindowInner* aWindow, nsresult aValue);
+
+ DOMError(nsPIDOMWindowInner* aWindow, const nsAString& aName);
+
+ DOMError(nsPIDOMWindowInner* aWindow, const nsAString& aName,
+ const nsAString& aMessage);
+
+ nsPIDOMWindowInner* GetParentObject() const
+ {
+ return mWindow;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<DOMError>
+ Constructor(const GlobalObject& global, const nsAString& name,
+ const nsAString& message, ErrorResult& aRv);
+
+ void GetName(nsString& aRetval) const
+ {
+ aRetval = mName;
+ }
+
+ void GetMessage(nsString& aRetval) const
+ {
+ aRetval = mMessage;
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DOMError, DOMERROR_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_domerror_h__
diff --git a/dom/base/DOMException.cpp b/dom/base/DOMException.cpp
new file mode 100644
index 000000000..dfda47316
--- /dev/null
+++ b/dom/base/DOMException.cpp
@@ -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/. */
+
+#include "mozilla/dom/DOMException.h"
+
+#include "jsprf.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/Exceptions.h"
+#include "nsContentUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDocument.h"
+#include "nsIDOMDOMException.h"
+#include "nsIException.h"
+#include "nsIProgrammingLanguage.h"
+#include "nsMemory.h"
+#include "prprf.h"
+#include "xpcprivate.h"
+
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/ErrorResult.h"
+
+using namespace mozilla;
+
+enum DOM4ErrorTypeCodeMap {
+ /* DOM4 errors from http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#domexception */
+ IndexSizeError = nsIDOMDOMException::INDEX_SIZE_ERR,
+ HierarchyRequestError = nsIDOMDOMException::HIERARCHY_REQUEST_ERR,
+ WrongDocumentError = nsIDOMDOMException::WRONG_DOCUMENT_ERR,
+ InvalidCharacterError = nsIDOMDOMException::INVALID_CHARACTER_ERR,
+ NoModificationAllowedError = nsIDOMDOMException::NO_MODIFICATION_ALLOWED_ERR,
+ NotFoundError = nsIDOMDOMException::NOT_FOUND_ERR,
+ NotSupportedError = nsIDOMDOMException::NOT_SUPPORTED_ERR,
+ // Can't remove until setNamedItem is removed
+ InUseAttributeError = nsIDOMDOMException::INUSE_ATTRIBUTE_ERR,
+ InvalidStateError = nsIDOMDOMException::INVALID_STATE_ERR,
+ SyntaxError = nsIDOMDOMException::SYNTAX_ERR,
+ InvalidModificationError = nsIDOMDOMException::INVALID_MODIFICATION_ERR,
+ NamespaceError = nsIDOMDOMException::NAMESPACE_ERR,
+ InvalidAccessError = nsIDOMDOMException::INVALID_ACCESS_ERR,
+ TypeMismatchError = nsIDOMDOMException::TYPE_MISMATCH_ERR,
+ SecurityError = nsIDOMDOMException::SECURITY_ERR,
+ NetworkError = nsIDOMDOMException::NETWORK_ERR,
+ AbortError = nsIDOMDOMException::ABORT_ERR,
+ URLMismatchError = nsIDOMDOMException::URL_MISMATCH_ERR,
+ QuotaExceededError = nsIDOMDOMException::QUOTA_EXCEEDED_ERR,
+ TimeoutError = nsIDOMDOMException::TIMEOUT_ERR,
+ InvalidNodeTypeError = nsIDOMDOMException::INVALID_NODE_TYPE_ERR,
+ DataCloneError = nsIDOMDOMException::DATA_CLONE_ERR,
+ InvalidPointerId = nsIDOMDOMException::INVALID_POINTER_ERR,
+ EncodingError = 0,
+
+ /* XXX Should be JavaScript native errors */
+ TypeError = 0,
+ RangeError = 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 const 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!");
+
+ return;
+}
+
+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 {
+namespace dom {
+
+bool Exception::sEverMadeOneFromFactory = false;
+
+NS_IMPL_CLASSINFO(Exception, nullptr, nsIClassInfo::DOM_OBJECT,
+ NS_XPCEXCEPTION_CID)
+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(nsIXPCException)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIException)
+ NS_IMPL_QUERY_CLASSINFO(Exception)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Exception)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Exception)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Exception)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Exception)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mData)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Exception)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mThrownJSVal)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Exception)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mData)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->mThrownJSVal.setNull();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CI_INTERFACE_GETTER(Exception, nsIXPCException)
+
+Exception::Exception(const nsACString& aMessage,
+ nsresult aResult,
+ const nsACString& aName,
+ nsIStackFrame *aLocation,
+ nsISupports *aData)
+: mResult(NS_OK),
+ mInitialized(false),
+ mHoldingJSVal(false)
+{
+ // A little hack... The nsIGenericModule nsIClassInfo scheme relies on there
+ // having been at least one instance made via the factory. Otherwise, the
+ // shared factory/classinsance object never gets created and our QI getter
+ // for our instance's pointer to our nsIClassInfo will always return null.
+ // This is bad because it means that wrapped exceptions will never have a
+ // shared prototype. So... We force one to be created via the factory
+ // *once* and then go about our business.
+ if (!sEverMadeOneFromFactory) {
+ nsCOMPtr<nsIXPCException> e =
+ do_CreateInstance(XPC_EXCEPTION_CONTRACTID);
+ sEverMadeOneFromFactory = true;
+ }
+
+ Initialize(aMessage, aResult, aName, aLocation, aData);
+}
+
+Exception::Exception()
+ : mResult(NS_OK),
+ mInitialized(false),
+ 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;
+ mThrownJSVal.setNull();
+
+ 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;
+ }
+}
+
+NS_IMETHODIMP
+Exception::GetMessageMoz(nsACString& aMessage)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ aMessage.Assign(mMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::GetResult(nsresult* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ *aResult = mResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::GetName(nsACString& aName)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ if (!mName.IsEmpty()) {
+ aName.Assign(mName);
+ } else {
+ aName.Truncate();
+
+ const char* name = nullptr;
+ nsXPCException::NameAndFormatForNSResult(mResult, &name, nullptr);
+
+ if (name) {
+ aName.Assign(name);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::GetFilename(JSContext* aCx, nsAString& aFilename)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ if (mLocation) {
+ return mLocation->GetFilename(aCx, aFilename);
+ }
+
+ aFilename.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::GetLineNumber(JSContext* aCx, uint32_t *aLineNumber)
+{
+ NS_ENSURE_ARG_POINTER(aLineNumber);
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ if (mLocation) {
+ int32_t lineno;
+ nsresult rv = mLocation->GetLineNumber(aCx, &lineno);
+ *aLineNumber = lineno;
+ return rv;
+ }
+
+ *aLineNumber = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::GetColumnNumber(uint32_t* aColumnNumber)
+{
+ NS_ENSURE_ARG_POINTER(aColumnNumber);
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ *aColumnNumber = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::GetLocation(nsIStackFrame** aLocation)
+{
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIStackFrame> location = mLocation;
+ location.forget(aLocation);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::GetData(nsISupports** aData)
+{
+ NS_ENSURE_ARG_POINTER(aData);
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsISupports> data = mData;
+ data.forget(aData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::ToString(JSContext* aCx, nsACString& _retval)
+{
+ NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED);
+
+ static const char defaultMsg[] = "<no message>";
+ static const char defaultLocation[] = "<unknown>";
+ static const char format[] =
+"[Exception... \"%s\" nsresult: \"0x%x (%s)\" location: \"%s\" data: %s]";
+
+ nsCString location;
+
+ if (mLocation) {
+ // we need to free this if it does not fail
+ nsresult rv = mLocation->ToString(aCx, location);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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, mResult, resultName,
+ location.get(), data);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Exception::Initialize(const nsACString& aMessage, nsresult aResult,
+ const nsACString& aName, nsIStackFrame *aLocation,
+ nsISupports *aData)
+{
+ NS_ENSURE_FALSE(mInitialized, NS_ERROR_ALREADY_INITIALIZED);
+
+ mMessage = aMessage;
+ mName = aName;
+ mResult = aResult;
+
+ 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.
+ }
+
+ mData = aData;
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+JSObject*
+Exception::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ExceptionBinding::Wrap(cx, this, aGivenProto);
+}
+
+void
+Exception::GetMessageMoz(nsString& retval)
+{
+ nsCString str;
+#ifdef DEBUG
+ DebugOnly<nsresult> rv =
+#endif
+ GetMessageMoz(str);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ CopyUTF8toUTF16(str, retval);
+}
+
+uint32_t
+Exception::Result() const
+{
+ return (uint32_t)mResult;
+}
+
+void
+Exception::GetName(nsString& retval)
+{
+ nsCString str;
+#ifdef DEBUG
+ DebugOnly<nsresult> rv =
+#endif
+ GetName(str);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ CopyUTF8toUTF16(str, retval);
+}
+
+uint32_t
+Exception::LineNumber(JSContext* aCx) const
+{
+ if (mLocation) {
+ int32_t lineno;
+ if (NS_SUCCEEDED(mLocation->GetLineNumber(aCx, &lineno))) {
+ return lineno;
+ }
+ return 0;
+ }
+
+ return 0;
+}
+
+uint32_t
+Exception::ColumnNumber() const
+{
+ return 0;
+}
+
+already_AddRefed<nsIStackFrame>
+Exception::GetLocation() const
+{
+ nsCOMPtr<nsIStackFrame> location = mLocation;
+ return location.forget();
+}
+
+already_AddRefed<nsISupports>
+Exception::GetData() const
+{
+ nsCOMPtr<nsISupports> data = mData;
+ return data.forget();
+}
+
+void
+Exception::GetStack(JSContext* aCx, nsAString& aStack, ErrorResult& aRv) const
+{
+ if (mLocation) {
+ aRv = mLocation->GetFormattedStack(aCx, aStack);
+ }
+}
+
+void
+Exception::Stringify(JSContext* aCx, nsString& retval)
+{
+ nsCString str;
+#ifdef DEBUG
+ DebugOnly<nsresult> rv =
+#endif
+ ToString(aCx, str);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ CopyUTF8toUTF16(str, retval);
+}
+
+NS_IMPL_ADDREF_INHERITED(DOMException, Exception)
+NS_IMPL_RELEASE_INHERITED(DOMException, Exception)
+NS_INTERFACE_MAP_BEGIN(DOMException)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMDOMException)
+NS_INTERFACE_MAP_END_INHERITING(Exception)
+
+DOMException::DOMException(nsresult aRv, const nsACString& aMessage,
+ const nsACString& aName, uint16_t aCode)
+ : Exception(EmptyCString(), aRv, EmptyCString(), nullptr, nullptr),
+ mName(aName),
+ mMessage(aMessage),
+ mCode(aCode)
+{
+}
+
+NS_IMETHODIMP
+DOMException::GetCode(uint16_t* aCode)
+{
+ NS_ENSURE_ARG_POINTER(aCode);
+ *aCode = mCode;
+
+ // Warn only when the code was changed (other than DOM Core)
+ // or the code is useless (zero)
+ if (NS_ERROR_GET_MODULE(mResult) != NS_ERROR_MODULE_DOM || !mCode) {
+ nsCOMPtr<nsIDocument> doc = nsContentUtils::GetDocumentFromCaller();
+ if (doc) {
+ doc->WarnOnceAbout(nsIDocument::eDOMExceptionCode);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+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%x (%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, mResult, resultName,
+ location.get());
+
+ return NS_OK;
+}
+
+void
+DOMException::GetName(nsString& retval)
+{
+ CopyUTF8toUTF16(mName, retval);
+}
+
+void
+DOMException::GetMessageMoz(nsString& retval)
+{
+ CopyUTF8toUTF16(mMessage, retval);
+}
+
+already_AddRefed<DOMException>
+DOMException::Constructor(GlobalObject& /* unused */,
+ const nsAString& aMessage,
+ const Optional<nsAString>& aName,
+ ErrorResult& aError)
+{
+ nsresult exceptionResult = NS_OK;
+ uint16_t exceptionCode = 0;
+ nsCString name(NS_LITERAL_CSTRING("Error"));
+
+ 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 DOMExceptionBinding::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();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/DOMException.h b/dom/base/DOMException.h
new file mode 100644
index 000000000..0f0bf1dbd
--- /dev/null
+++ b/dom/base/DOMException.h
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "jspubtd.h"
+#include "js/GCAPI.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsID.h"
+#include "nsIDOMDOMException.h"
+#include "nsWrapperCache.h"
+#include "xpcexception.h"
+#include "nsString.h"
+#include "mozilla/dom/BindingDeclarations.h"
+
+class nsIStackFrame;
+class nsString;
+
+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 nsIXPCException,
+ public nsWrapperCache
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_EXCEPTION_IID)
+
+ NS_DEFINE_STATIC_CID_ACCESSOR(NS_XPCEXCEPTION_CID)
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Exception)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIEXCEPTION
+ NS_DECL_NSIXPCEXCEPTION
+
+ // 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(nsString& retval);
+
+ virtual void GetErrorMessage(nsAString& aRetVal)
+ {
+ // Since GetName and GetMessageMoz are non-virtual and they deal with
+ // different member variables in Exception vs. DOMException, have a
+ // virtual method to ensure the right error message creation.
+ nsAutoString name;
+ nsAutoString message;
+ GetName(name);
+ GetMessageMoz(message);
+ CreateErrorMessage(name, message, aRetVal);
+ }
+
+ // The XPCOM GetFilename does the right thing. It might throw, but we want to
+ // return an empty filename in that case anyway, instead of throwing.
+
+ uint32_t LineNumber(JSContext* aCx) const;
+
+ uint32_t ColumnNumber() const;
+
+ already_AddRefed<nsIStackFrame> GetLocation() const;
+
+ already_AddRefed<nsISupports> GetData() const;
+
+ void GetStack(JSContext* aCx, nsAString& aStack, ErrorResult& aRv) const;
+
+ void Stringify(JSContext* aCx, nsString& retval);
+
+ // XPCOM factory ctor.
+ Exception();
+
+ Exception(const nsACString& aMessage,
+ nsresult aResult,
+ const nsACString& aName,
+ nsIStackFrame *aLocation,
+ nsISupports *aData);
+
+protected:
+ virtual ~Exception();
+
+ void CreateErrorMessage(const nsAString& aName, const nsAString& aMessage,
+ nsAString& aRetVal)
+ {
+ // Create similar error message as what ErrorReport::init does in jsexn.cpp.
+ if (!aName.IsEmpty() && !aMessage.IsEmpty()) {
+ aRetVal.Assign(aName);
+ aRetVal.AppendLiteral(": ");
+ aRetVal.Append(aMessage);
+ } else if (!aName.IsEmpty()) {
+ aRetVal.Assign(aName);
+ } else if (!aMessage.IsEmpty()) {
+ aRetVal.Assign(aMessage);
+ } else {
+ aRetVal.Truncate();
+ }
+ }
+
+ nsCString mMessage;
+ nsresult mResult;
+ nsCString mName;
+ nsCOMPtr<nsIStackFrame> mLocation;
+ nsCOMPtr<nsISupports> mData;
+ bool mInitialized;
+
+ bool mHoldingJSVal;
+ JS::Heap<JS::Value> mThrownJSVal;
+
+private:
+ static bool sEverMadeOneFromFactory;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Exception, MOZILLA_EXCEPTION_IID)
+
+class DOMException : public Exception,
+ public nsIDOMDOMException
+{
+public:
+ DOMException(nsresult aRv, const nsACString& aMessage,
+ const nsACString& aName, uint16_t aCode);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMDOMEXCEPTION
+
+ // nsIException overrides
+ NS_IMETHOD ToString(JSContext* aCx, nsACString& aReturn) override;
+
+ // 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,
+ ErrorResult& aError);
+
+ uint16_t Code() const {
+ return mCode;
+ }
+
+ // Intentionally shadow the nsXPCException version.
+ void GetMessageMoz(nsString& retval);
+ void GetName(nsString& retval);
+
+ virtual void GetErrorMessage(nsAString& aRetVal) override
+ {
+ // See the comment in Exception::GetErrorMessage.
+ nsAutoString name;
+ nsAutoString message;
+ GetName(name);
+ GetMessageMoz(message);
+ CreateErrorMessage(name, message, aRetVal);
+ }
+
+ static already_AddRefed<DOMException>
+ Create(nsresult aRv);
+
+ static already_AddRefed<DOMException>
+ Create(nsresult aRv, const nsACString& aMessage);
+
+protected:
+
+ virtual ~DOMException() {}
+
+ nsCString mName;
+ nsCString mMessage;
+
+ 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 000000000..10e8ec9b4
--- /dev/null
+++ b/dom/base/DOMImplementation.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsDOMClassInfoID.h"
+#include "nsIDOMDocument.h"
+#include "DocumentType.h"
+#include "nsTextNode.h"
+
+namespace mozilla {
+namespace dom {
+
+// QueryInterface implementation for DOMImplementation
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMImplementation)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMDOMImplementation)
+ 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)
+
+JSObject*
+DOMImplementation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMImplementationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+DOMImplementation::HasFeature(const nsAString& aFeature,
+ const nsAString& aVersion,
+ bool* aReturn)
+{
+ *aReturn = true;
+ return NS_OK;
+}
+
+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;
+ }
+
+ nsCOMPtr<nsIAtom> 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, NullString(), aRv);
+ return docType.forget();
+}
+
+NS_IMETHODIMP
+DOMImplementation::CreateDocumentType(const nsAString& aQualifiedName,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ nsIDOMDocumentType** aReturn)
+{
+ ErrorResult rv;
+ *aReturn =
+ CreateDocumentType(aQualifiedName, aPublicId, aSystemId, rv).take();
+ return rv.StealNSResult();
+}
+
+nsresult
+DOMImplementation::CreateDocument(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ nsIDOMDocumentType* aDoctype,
+ nsIDocument** aDocument,
+ nsIDOMDocument** aDOMDocument)
+{
+ *aDocument = nullptr;
+ *aDOMDocument = nullptr;
+
+ nsresult rv;
+ if (!aQualifiedName.IsEmpty()) {
+ const nsAFlatString& 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<nsIDOMDocument> document;
+
+ rv = NS_NewDOMDocument(getter_AddRefs(document),
+ aNamespaceURI, aQualifiedName, aDoctype,
+ mDocumentURI, mBaseURI,
+ mOwner->NodePrincipal(),
+ true, scriptHandlingObject,
+ DocumentFlavorLegacyGuess);
+ 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.
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(document);
+
+ if (aNamespaceURI.EqualsLiteral("http://www.w3.org/1999/xhtml")) {
+ doc->SetContentType(NS_LITERAL_STRING("application/xhtml+xml"));
+ } else if (aNamespaceURI.EqualsLiteral("http://www.w3.org/2000/svg")) {
+ doc->SetContentType(NS_LITERAL_STRING("image/svg+xml"));
+ } else {
+ doc->SetContentType(NS_LITERAL_STRING("application/xml"));
+ }
+
+ doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE);
+
+ doc.forget(aDocument);
+ document.forget(aDOMDocument);
+ return NS_OK;
+}
+
+already_AddRefed<nsIDocument>
+DOMImplementation::CreateDocument(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ nsIDOMDocumentType* aDoctype,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIDocument> document;
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ aRv = CreateDocument(aNamespaceURI, aQualifiedName, aDoctype,
+ getter_AddRefs(document), getter_AddRefs(domDocument));
+ return document.forget();
+}
+
+NS_IMETHODIMP
+DOMImplementation::CreateDocument(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ nsIDOMDocumentType* aDoctype,
+ nsIDOMDocument** aReturn)
+{
+ nsCOMPtr<nsIDocument> document;
+ return CreateDocument(aNamespaceURI, aQualifiedName, aDoctype,
+ getter_AddRefs(document), aReturn);
+}
+
+nsresult
+DOMImplementation::CreateHTMLDocument(const nsAString& aTitle,
+ nsIDocument** aDocument,
+ nsIDOMDocument** aDOMDocument)
+{
+ *aDocument = nullptr;
+ *aDOMDocument = nullptr;
+
+ NS_ENSURE_STATE(mOwner);
+
+ nsCOMPtr<nsIDOMDocumentType> doctype;
+ // Indicate that there is no internal subset (not just an empty one)
+ nsresult rv = NS_NewDOMDocumentType(getter_AddRefs(doctype),
+ mOwner->NodeInfoManager(),
+ nsGkAtoms::html, // aName
+ EmptyString(), // aPublicId
+ EmptyString(), // aSystemId
+ NullString()); // aInternalSubset
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ nsCOMPtr<nsIGlobalObject> scriptHandlingObject =
+ do_QueryReferent(mScriptObject);
+
+ NS_ENSURE_STATE(!mScriptObject || scriptHandlingObject);
+
+ nsCOMPtr<nsIDOMDocument> document;
+ rv = NS_NewDOMDocument(getter_AddRefs(document),
+ EmptyString(), EmptyString(),
+ doctype, mDocumentURI, mBaseURI,
+ mOwner->NodePrincipal(),
+ true, scriptHandlingObject,
+ DocumentFlavorLegacyGuess);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(document);
+
+ nsCOMPtr<Element> root = doc->CreateElem(NS_LITERAL_STRING("html"), nullptr,
+ kNameSpaceID_XHTML);
+ rv = doc->AppendChildTo(root, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<Element> head = doc->CreateElem(NS_LITERAL_STRING("head"), nullptr,
+ kNameSpaceID_XHTML);
+ rv = root->AppendChildTo(head, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!DOMStringIsNull(aTitle)) {
+ nsCOMPtr<Element> title = doc->CreateElem(NS_LITERAL_STRING("title"),
+ nullptr, kNameSpaceID_XHTML);
+ rv = head->AppendChildTo(title, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsTextNode> titleText = new nsTextNode(doc->NodeInfoManager());
+ rv = titleText->SetText(aTitle, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = title->AppendChildTo(titleText, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<Element> body = doc->CreateElem(NS_LITERAL_STRING("body"), nullptr,
+ kNameSpaceID_XHTML);
+ rv = root->AppendChildTo(body, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE);
+
+ doc.forget(aDocument);
+ document.forget(aDOMDocument);
+ return NS_OK;
+}
+
+already_AddRefed<nsIDocument>
+DOMImplementation::CreateHTMLDocument(const Optional<nsAString>& aTitle,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIDocument> document;
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ aRv = CreateHTMLDocument(aTitle.WasPassed() ? aTitle.Value()
+ : NullString(),
+ getter_AddRefs(document),
+ getter_AddRefs(domDocument));
+ return document.forget();
+}
+
+NS_IMETHODIMP
+DOMImplementation::CreateHTMLDocument(const nsAString& aTitle,
+ nsIDOMDocument** aReturn)
+{
+ nsCOMPtr<nsIDocument> document;
+ return CreateHTMLDocument(aTitle, getter_AddRefs(document), aReturn);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/DOMImplementation.h b/dom/base/DOMImplementation.h
new file mode 100644
index 000000000..9ecd91290
--- /dev/null
+++ b/dom/base/DOMImplementation.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/. */
+
+#ifndef mozilla_dom_DOMImplementation_h
+#define mozilla_dom_DOMImplementation_h
+
+#include "nsIDOMDOMImplementation.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDocument.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIURI.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsString.h"
+
+class nsIDOMDocument;
+
+namespace mozilla {
+namespace dom {
+class DocumentType;
+
+class DOMImplementation final : public nsIDOMDOMImplementation
+ , public nsWrapperCache
+{
+ ~DOMImplementation()
+ {
+ }
+
+public:
+ DOMImplementation(nsIDocument* aOwner,
+ nsIGlobalObject* aScriptObject,
+ nsIURI* aDocumentURI,
+ nsIURI* aBaseURI)
+ : mOwner(aOwner)
+ , mScriptObject(do_GetWeakReference(aScriptObject))
+ , mDocumentURI(aDocumentURI)
+ , mBaseURI(aBaseURI)
+ {
+ MOZ_ASSERT(aOwner);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMImplementation)
+
+ nsIDocument* GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsIDOMDOMImplementation
+ NS_DECL_NSIDOMDOMIMPLEMENTATION
+
+ bool HasFeature()
+ {
+ return true;
+ }
+
+ already_AddRefed<DocumentType>
+ CreateDocumentType(const nsAString& aQualifiedName,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ ErrorResult& aRv);
+
+ already_AddRefed<nsIDocument>
+ CreateDocument(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ nsIDOMDocumentType* aDoctype,
+ ErrorResult& aRv);
+
+ already_AddRefed<nsIDocument>
+ CreateHTMLDocument(const Optional<nsAString>& aTitle, ErrorResult& aRv);
+
+private:
+ nsresult CreateDocument(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ nsIDOMDocumentType* aDoctype,
+ nsIDocument** aDocument,
+ nsIDOMDocument** aDOMDocument);
+ nsresult CreateHTMLDocument(const nsAString& aTitle,
+ nsIDocument** aDocument,
+ nsIDOMDocument** aDOMDocument);
+
+ nsCOMPtr<nsIDocument> 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 000000000..169c3fe7a
--- /dev/null
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsCSSParser.h"
+#include "nsCSSPropertyID.h"
+#include "nsIFrame.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+
+namespace mozilla {
+namespace 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
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
+ 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_SCRIPT_OBJECTS
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+already_AddRefed<DOMIntersectionObserver>
+DOMIntersectionObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal,
+ mozilla::dom::IntersectionCallback& aCb,
+ mozilla::ErrorResult& aRv)
+{
+ return Constructor(aGlobal, aCb, IntersectionObserverInit(), aRv);
+}
+
+already_AddRefed<DOMIntersectionObserver>
+DOMIntersectionObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal,
+ mozilla::dom::IntersectionCallback& aCb,
+ const mozilla::dom::IntersectionObserverInit& aOptions,
+ mozilla::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);
+
+ observer->mRoot = aOptions.mRoot;
+
+ if (!observer->SetRootMargin(aOptions.mRootMargin)) {
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
+ NS_LITERAL_CSTRING("rootMargin must be specified in pixels or percent."));
+ return nullptr;
+ }
+
+ if (aOptions.mThreshold.IsDoubleSequence()) {
+ const mozilla::dom::Sequence<double>& thresholds = aOptions.mThreshold.GetAsDoubleSequence();
+ observer->mThresholds.SetCapacity(thresholds.Length());
+ for (const auto& thresh : thresholds) {
+ if (thresh < 0.0 || thresh > 1.0) {
+ aRv.ThrowTypeError<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.ThrowTypeError<dom::MSG_THRESHOLD_RANGE_ERROR>();
+ return nullptr;
+ }
+ observer->mThresholds.AppendElement(thresh);
+ }
+
+ return observer.forget();
+}
+
+bool
+DOMIntersectionObserver::SetRootMargin(const nsAString& aString)
+{
+ // By not passing a CSS Loader object we make sure we don't parse in quirks
+ // mode so that pixel/percent and unit-less values will be differentiated.
+ nsCSSParser parser(nullptr);
+ nsCSSValue value;
+ if (!parser.ParseMarginString(aString, nullptr, 0, value, true)) {
+ return false;
+ }
+
+ mRootMargin = value.GetRectValue();
+
+ for (uint32_t i = 0; i < ArrayLength(nsCSSRect::sides); ++i) {
+ nsCSSValue value = mRootMargin.*nsCSSRect::sides[i];
+ if (!(value.IsPixelLengthUnit() || value.IsPercentLengthUnit())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+DOMIntersectionObserver::GetRootMargin(mozilla::dom::DOMString& aRetVal)
+{
+ mRootMargin.AppendToString(eCSSProperty_DOM, aRetVal, nsCSSValue::eNormalized);
+}
+
+void
+DOMIntersectionObserver::GetThresholds(nsTArray<double>& aRetVal)
+{
+ aRetVal = mThresholds;
+}
+
+void
+DOMIntersectionObserver::Observe(Element& aTarget)
+{
+ if (mObservationTargets.Contains(&aTarget)) {
+ return;
+ }
+ aTarget.RegisterIntersectionObserver(this);
+ mObservationTargets.PutEntry(&aTarget);
+ Connect();
+}
+
+void
+DOMIntersectionObserver::Unobserve(Element& aTarget)
+{
+ if (UnlinkTarget(aTarget)) {
+ aTarget.UnregisterIntersectionObserver(this);
+ }
+}
+
+bool
+DOMIntersectionObserver::UnlinkTarget(Element& aTarget)
+{
+ if (!mObservationTargets.Contains(&aTarget)) {
+ return false;
+ }
+ if (mObservationTargets.Count() == 1) {
+ Disconnect();
+ return false;
+ }
+ mObservationTargets.RemoveEntry(&aTarget);
+ return true;
+}
+
+void
+DOMIntersectionObserver::Connect()
+{
+ if (mConnected) {
+ return;
+ }
+ nsIDocument* document = mOwner->GetExtantDoc();
+ document->AddIntersectionObserver(this);
+ mConnected = true;
+}
+
+void
+DOMIntersectionObserver::Disconnect()
+{
+ if (!mConnected) {
+ return;
+ }
+ for (auto iter = mObservationTargets.Iter(); !iter.Done(); iter.Next()) {
+ Element* target = iter.Get()->GetKey();
+ target->UnregisterIntersectionObserver(this);
+ }
+ mObservationTargets.Clear();
+ if (mOwner) {
+ nsIDocument* document = mOwner->GetExtantDoc();
+ document->RemoveIntersectionObserver(this);
+ }
+ mConnected = false;
+}
+
+void
+DOMIntersectionObserver::TakeRecords(nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal)
+{
+ aRetVal.SwapElements(mQueuedEntries);
+ mQueuedEntries.Clear();
+}
+
+static bool
+CheckSimilarOrigin(nsINode* aNode1, nsINode* aNode2)
+{
+ nsIPrincipal* principal1 = aNode1->NodePrincipal();
+ nsIPrincipal* principal2 = aNode2->NodePrincipal();
+ nsAutoCString baseDomain1;
+ nsAutoCString baseDomain2;
+
+ nsresult rv = principal1->GetBaseDomain(baseDomain1);
+ if (NS_FAILED(rv)) {
+ return principal1 == principal2;
+ }
+
+ rv = principal2->GetBaseDomain(baseDomain2);
+ if (NS_FAILED(rv)) {
+ return principal1 == principal2;
+ }
+
+ return baseDomain1 == baseDomain2;
+}
+
+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));
+}
+
+void
+DOMIntersectionObserver::Update(nsIDocument* aDocument, DOMHighResTimeStamp time)
+{
+ Element* root = nullptr;
+ nsIFrame* rootFrame = nullptr;
+ nsRect rootRect;
+
+ if (mRoot) {
+ root = mRoot;
+ rootFrame = root->GetPrimaryFrame();
+ if (rootFrame) {
+ if (rootFrame->GetType() == nsGkAtoms::scrollFrame) {
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame);
+ rootRect = nsLayoutUtils::TransformFrameRectToAncestor(
+ rootFrame,
+ rootFrame->GetContentRectRelativeToSelf(),
+ scrollFrame->GetScrolledFrame());
+ } else {
+ rootRect = nsLayoutUtils::GetAllInFlowRectsUnion(rootFrame,
+ nsLayoutUtils::GetContainingBlockForClientRect(rootFrame),
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ }
+ }
+ } else {
+ nsCOMPtr<nsIPresShell> presShell = aDocument->GetShell();
+ if (presShell) {
+ rootFrame = presShell->GetRootScrollFrame();
+ if (rootFrame) {
+ nsPresContext* presContext = rootFrame->PresContext();
+ while (!presContext->IsRootContentDocument()) {
+ presContext = rootFrame->PresContext()->GetParentPresContext();
+ rootFrame = presContext->PresShell()->GetRootScrollFrame();
+ }
+ root = rootFrame->GetContent()->AsElement();
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame);
+ rootRect = scrollFrame->GetScrollPortRect();
+ }
+ }
+ }
+
+ nsMargin rootMargin;
+ NS_FOR_CSS_SIDES(side) {
+ nscoord basis = side == NS_SIDE_TOP || side == NS_SIDE_BOTTOM ?
+ rootRect.height : rootRect.width;
+ nsCSSValue value = mRootMargin.*nsCSSRect::sides[side];
+ nsStyleCoord coord;
+ if (value.IsPixelLengthUnit()) {
+ coord.SetCoordValue(value.GetPixelLength());
+ } else if (value.IsPercentLengthUnit()) {
+ coord.SetPercentValue(value.GetPercentValue());
+ } else {
+ MOZ_ASSERT_UNREACHABLE("invalid length unit");
+ }
+ rootMargin.Side(side) = nsLayoutUtils::ComputeCBDependentValue(basis, coord);
+ }
+
+ for (auto iter = mObservationTargets.Iter(); !iter.Done(); iter.Next()) {
+ Element* target = iter.Get()->GetKey();
+ nsIFrame* targetFrame = target->GetPrimaryFrame();
+ nsRect targetRect;
+ Maybe<nsRect> intersectionRect;
+
+ if (rootFrame && targetFrame) {
+ // If mRoot is set we are testing intersection with a container element
+ // instead of the implicit root.
+ if (mRoot) {
+ // Skip further processing of this target if it is not in the same
+ // Document as the intersection root, e.g. if root is an element of
+ // the main document and target an element from an embedded iframe.
+ if (target->GetComposedDoc() != root->GetComposedDoc()) {
+ continue;
+ }
+ // Skip further processing of this target if is not a descendant of the
+ // intersection root in the containing block chain. E.g. this would be
+ // the case if the target is in a position:absolute element whose
+ // containing block is an ancestor of root.
+ if (!nsLayoutUtils::IsAncestorFrameCrossDoc(rootFrame, targetFrame)) {
+ continue;
+ }
+ }
+
+ targetRect = nsLayoutUtils::GetAllInFlowRectsUnion(
+ targetFrame,
+ nsLayoutUtils::GetContainingBlockForClientRect(targetFrame),
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS
+ );
+ intersectionRect = Some(targetFrame->GetVisualOverflowRect());
+
+ nsIFrame* containerFrame = nsLayoutUtils::GetCrossDocParentFrame(targetFrame);
+ while (containerFrame && containerFrame != rootFrame) {
+ if (containerFrame->GetType() == nsGkAtoms::scrollFrame) {
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(containerFrame);
+ nsRect subFrameRect = scrollFrame->GetScrollPortRect();
+ nsRect intersectionRectRelativeToContainer =
+ nsLayoutUtils::TransformFrameRectToAncestor(targetFrame,
+ intersectionRect.value(),
+ containerFrame);
+ intersectionRect = EdgeInclusiveIntersection(intersectionRectRelativeToContainer,
+ subFrameRect);
+ if (!intersectionRect) {
+ break;
+ }
+ targetFrame = containerFrame;
+ }
+
+ // TODO: Apply clip-path.
+
+ containerFrame = nsLayoutUtils::GetCrossDocParentFrame(containerFrame);
+ }
+ }
+
+ nsRect rootIntersectionRect = rootRect;
+ bool isInSimilarOriginBrowsingContext = rootFrame && targetFrame &&
+ CheckSimilarOrigin(root, target);
+
+ if (isInSimilarOriginBrowsingContext) {
+ rootIntersectionRect.Inflate(rootMargin);
+ }
+
+ if (intersectionRect.isSome()) {
+ nsRect intersectionRectRelativeToRoot =
+ nsLayoutUtils::TransformFrameRectToAncestor(
+ targetFrame,
+ intersectionRect.value(),
+ nsLayoutUtils::GetContainingBlockForClientRect(rootFrame)
+ );
+ intersectionRect = EdgeInclusiveIntersection(
+ intersectionRectRelativeToRoot,
+ rootIntersectionRect
+ );
+ if (intersectionRect.isSome()) {
+ intersectionRect = Some(nsLayoutUtils::TransformFrameRectToAncestor(
+ nsLayoutUtils::GetContainingBlockForClientRect(rootFrame),
+ intersectionRect.value(),
+ targetFrame->PresContext()->PresShell()->GetRootScrollFrame()
+ ));
+ }
+ }
+
+ double targetArea = targetRect.width * targetRect.height;
+ double intersectionArea = !intersectionRect ?
+ 0 : intersectionRect->width * intersectionRect->height;
+ double intersectionRatio = targetArea > 0.0 ? intersectionArea / targetArea : 0.0;
+
+ size_t threshold = -1;
+ if (intersectionRatio > 0.0) {
+ if (intersectionRatio >= 1.0) {
+ intersectionRatio = 1.0;
+ threshold = mThresholds.Length();
+ } else {
+ for (size_t k = 0; k < mThresholds.Length(); ++k) {
+ if (mThresholds[k] <= intersectionRatio) {
+ threshold = k + 1;
+ } else {
+ break;
+ }
+ }
+ }
+ } else if (intersectionRect.isSome()) {
+ threshold = 0;
+ }
+
+ if (target->UpdateIntersectionObservation(this, threshold)) {
+ QueueIntersectionObserverEntry(
+ target, time,
+ isInSimilarOriginBrowsingContext ? Some(rootIntersectionRect) : Nothing(),
+ targetRect, intersectionRect, intersectionRatio
+ );
+ }
+ }
+}
+
+void
+DOMIntersectionObserver::QueueIntersectionObserverEntry(Element* aTarget,
+ DOMHighResTimeStamp time,
+ const Maybe<nsRect>& aRootRect,
+ const nsRect& aTargetRect,
+ const Maybe<nsRect>& aIntersectionRect,
+ double aIntersectionRatio)
+{
+ RefPtr<DOMRect> rootBounds;
+ if (aRootRect.isSome()) {
+ rootBounds = new DOMRect(this);
+ rootBounds->SetLayoutRect(aRootRect.value());
+ }
+ RefPtr<DOMRect> boundingClientRect = new DOMRect(this);
+ boundingClientRect->SetLayoutRect(aTargetRect);
+ RefPtr<DOMRect> intersectionRect = new DOMRect(this);
+ if (aIntersectionRect.isSome()) {
+ intersectionRect->SetLayoutRect(aIntersectionRect.value());
+ }
+ RefPtr<DOMIntersectionObserverEntry> entry = new DOMIntersectionObserverEntry(
+ this,
+ time,
+ rootBounds.forget(),
+ boundingClientRect.forget(),
+ intersectionRect.forget(),
+ aTarget, aIntersectionRatio);
+ mQueuedEntries.AppendElement(entry.forget());
+}
+
+void
+DOMIntersectionObserver::Notify()
+{
+ if (!mQueuedEntries.Length()) {
+ return;
+ }
+ mozilla::dom::Sequence<mozilla::OwningNonNull<DOMIntersectionObserverEntry>> entries;
+ if (entries.SetCapacity(mQueuedEntries.Length(), mozilla::fallible)) {
+ for (uint32_t i = 0; i < mQueuedEntries.Length(); ++i) {
+ RefPtr<DOMIntersectionObserverEntry> next = mQueuedEntries[i];
+ *entries.AppendElement(mozilla::fallible) = next;
+ }
+ }
+ mQueuedEntries.Clear();
+ mCallback->Call(this, entries, *this);
+}
+
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/DOMIntersectionObserver.h b/dom/base/DOMIntersectionObserver.h
new file mode 100644
index 000000000..3eb10ad38
--- /dev/null
+++ b/dom/base/DOMIntersectionObserver.h
@@ -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/. */
+
+#ifndef DOMIntersectionObserver_h
+#define DOMIntersectionObserver_h
+
+#include "mozilla/dom/IntersectionObserverBinding.h"
+#include "nsCSSValue.h"
+#include "nsTArray.h"
+
+using mozilla::dom::DOMRect;
+using mozilla::dom::Element;
+
+namespace mozilla {
+namespace dom {
+
+class DOMIntersectionObserver;
+
+class DOMIntersectionObserverEntry final : public nsISupports,
+ public nsWrapperCache
+{
+ ~DOMIntersectionObserverEntry() {}
+
+public:
+ DOMIntersectionObserverEntry(nsISupports* aOwner,
+ DOMHighResTimeStamp aTime,
+ RefPtr<DOMRect> aRootBounds,
+ RefPtr<DOMRect> aBoundingClientRect,
+ RefPtr<DOMRect> aIntersectionRect,
+ Element* aTarget,
+ double aIntersectionRatio)
+ : mOwner(aOwner),
+ mTime(aTime),
+ mRootBounds(aRootBounds),
+ mBoundingClientRect(aBoundingClientRect),
+ mIntersectionRect(aIntersectionRect),
+ mTarget(aTarget),
+ mIntersectionRatio(aIntersectionRatio)
+ {
+ }
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMIntersectionObserverEntry)
+
+ nsISupports* GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::IntersectionObserverEntryBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+ DOMHighResTimeStamp Time()
+ {
+ return mTime;
+ }
+
+ DOMRect* GetRootBounds()
+ {
+ return mRootBounds;
+ }
+
+ DOMRect* BoundingClientRect()
+ {
+ return mBoundingClientRect;
+ }
+
+ DOMRect* IntersectionRect()
+ {
+ return mIntersectionRect;
+ }
+
+ double IntersectionRatio()
+ {
+ return mIntersectionRatio;
+ }
+
+ Element* Target()
+ {
+ return mTarget;
+ }
+
+protected:
+ nsCOMPtr<nsISupports> mOwner;
+ DOMHighResTimeStamp mTime;
+ RefPtr<DOMRect> mRootBounds;
+ RefPtr<DOMRect> mBoundingClientRect;
+ RefPtr<DOMRect> mIntersectionRect;
+ RefPtr<Element> mTarget;
+ double mIntersectionRatio;
+};
+
+#define NS_DOM_INTERSECTION_OBSERVER_IID \
+{ 0x8570a575, 0xe303, 0x4d18, \
+ { 0xb6, 0xb1, 0x4d, 0x2b, 0x49, 0xd8, 0xef, 0x94 } }
+
+class DOMIntersectionObserver final : public nsISupports,
+ public nsWrapperCache
+{
+ virtual ~DOMIntersectionObserver() {
+ Disconnect();
+ }
+
+public:
+ DOMIntersectionObserver(already_AddRefed<nsPIDOMWindowInner>&& aOwner,
+ mozilla::dom::IntersectionCallback& aCb)
+ : mOwner(aOwner), mCallback(&aCb), mConnected(false)
+ {
+ }
+ 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 mozilla::dom::GlobalObject& aGlobal,
+ mozilla::dom::IntersectionCallback& aCb,
+ mozilla::ErrorResult& aRv);
+ static already_AddRefed<DOMIntersectionObserver>
+ Constructor(const mozilla::dom::GlobalObject& aGlobal,
+ mozilla::dom::IntersectionCallback& aCb,
+ const mozilla::dom::IntersectionObserverInit& aOptions,
+ mozilla::ErrorResult& aRv);
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::IntersectionObserverBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+ nsISupports* GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ Element* GetRoot() const {
+ return mRoot;
+ }
+
+ void GetRootMargin(mozilla::dom::DOMString& aRetVal);
+ void GetThresholds(nsTArray<double>& aRetVal);
+ void Observe(Element& aTarget);
+ void Unobserve(Element& aTarget);
+
+ bool UnlinkTarget(Element& aTarget);
+ void Disconnect();
+
+ void TakeRecords(nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal);
+
+ mozilla::dom::IntersectionCallback* IntersectionCallback() { return mCallback; }
+
+ bool SetRootMargin(const nsAString& aString);
+
+ void Update(nsIDocument* aDocument, DOMHighResTimeStamp time);
+ void Notify();
+
+protected:
+ void Connect();
+ void QueueIntersectionObserverEntry(Element* aTarget,
+ DOMHighResTimeStamp time,
+ const Maybe<nsRect>& aRootRect,
+ const nsRect& aTargetRect,
+ const Maybe<nsRect>& aIntersectionRect,
+ double aIntersectionRatio);
+
+ nsCOMPtr<nsPIDOMWindowInner> mOwner;
+ RefPtr<mozilla::dom::IntersectionCallback> mCallback;
+ RefPtr<Element> mRoot;
+ nsCSSRect mRootMargin;
+ nsTArray<double> mThresholds;
+ nsTHashtable<nsPtrHashKey<Element>> mObservationTargets;
+ nsTArray<RefPtr<DOMIntersectionObserverEntry>> mQueuedEntries;
+ bool mConnected;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(DOMIntersectionObserver, NS_DOM_INTERSECTION_OBSERVER_IID)
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/DOMMatrix.cpp b/dom/base/DOMMatrix.cpp
new file mode 100644
index 000000000..0a8b25526
--- /dev/null
+++ b/dom/base/DOMMatrix.cpp
@@ -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/. */
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMMatrixBinding.h"
+#include "mozilla/dom/DOMPointBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ToJSValue.h"
+
+#include "mozilla/dom/DOMPoint.h"
+#include "mozilla/dom/DOMMatrix.h"
+
+#include "SVGTransformListParser.h"
+#include "SVGTransform.h"
+
+#include <math.h>
+
+namespace mozilla {
+namespace dom {
+
+static const double radPerDegree = 2.0 * M_PI / 360.0;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMMatrixReadOnly, mParent)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMatrixReadOnly, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMatrixReadOnly, Release)
+
+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 aScale,
+ double aOriginX,
+ double aOriginY) const
+{
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->ScaleSelf(aScale, aOriginX, aOriginY);
+
+ 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,
+ double aScaleZ,
+ double aOriginX,
+ double aOriginY,
+ double aOriginZ) const
+{
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->ScaleNonUniformSelf(aScaleX, aScaleY, aScaleZ, aOriginX, aOriginY, aOriginZ);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrixReadOnly::Rotate(double aAngle,
+ double aOriginX ,
+ double aOriginY) const
+{
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->RotateSelf(aAngle, aOriginX, aOriginY);
+
+ 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 DOMMatrix& other) const
+{
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ retval->MultiplySelf(other);
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrixReadOnly::FlipX() const
+{
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ if (mMatrix3D) {
+ gfx::Matrix4x4 m;
+ m._11 = -1;
+ retval->mMatrix3D = new gfx::Matrix4x4(m * *mMatrix3D);
+ } else {
+ gfx::Matrix m;
+ m._11 = -1;
+ retval->mMatrix2D = new gfx::Matrix(mMatrix2D ? m * *mMatrix2D : m);
+ }
+
+ return retval.forget();
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrixReadOnly::FlipY() const
+{
+ RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
+ if (mMatrix3D) {
+ gfx::Matrix4x4 m;
+ m._22 = -1;
+ retval->mMatrix3D = new gfx::Matrix4x4(m * *mMatrix3D);
+ } else {
+ gfx::Matrix m;
+ m._22 = -1;
+ retval->mMatrix2D = new gfx::Matrix(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::Identity() 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::Matrix4x4 tempMatrix(gfx::Matrix4x4::From2D(*mMatrix2D));
+
+ gfx::Point4D 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::Point 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)
+{
+ nsAutoString matrixStr;
+ if (mMatrix3D) {
+ matrixStr.AppendPrintf("matrix3d(%g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g)",
+ M11(), M12(), M13(), M14(),
+ M21(), M22(), M23(), M24(),
+ M31(), M32(), M33(), M34(),
+ M41(), M42(), M43(), M44());
+ } else {
+ matrixStr.AppendPrintf("matrix(%g, %g, %g, %g, %g, %g)", A(), B(), C(), D(), E(), F());
+ }
+
+ aResult = matrixStr;
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrix::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports());
+ return obj.forget();
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrix::Constructor(const GlobalObject& aGlobal, const nsAString& aTransformList, ErrorResult& aRv)
+{
+ RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports());
+
+ obj = obj->SetMatrixValue(aTransformList, aRv);
+ return obj.forget();
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrix::Constructor(const GlobalObject& aGlobal, const DOMMatrixReadOnly& aOther, ErrorResult& aRv)
+{
+ RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), aOther);
+ return obj.forget();
+}
+
+template <typename T> void SetDataInMatrix(DOMMatrix* 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 {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ }
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrix::Constructor(const GlobalObject& aGlobal, const Float32Array& aArray32, ErrorResult& aRv)
+{
+ RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports());
+ aArray32.ComputeLengthAndData();
+ SetDataInMatrix(obj, aArray32.Data(), aArray32.Length(), aRv);
+
+ return obj.forget();
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrix::Constructor(const GlobalObject& aGlobal, const Float64Array& aArray64, ErrorResult& aRv)
+{
+ RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports());
+ aArray64.ComputeLengthAndData();
+ SetDataInMatrix(obj, aArray64.Data(), aArray64.Length(), aRv);
+
+ return obj.forget();
+}
+
+already_AddRefed<DOMMatrix>
+DOMMatrix::Constructor(const GlobalObject& aGlobal, const Sequence<double>& aNumberSequence, ErrorResult& aRv)
+{
+ RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports());
+ SetDataInMatrix(obj, aNumberSequence.Elements(), aNumberSequence.Length(), aRv);
+
+ return obj.forget();
+}
+
+void DOMMatrix::Ensure3DMatrix()
+{
+ if (!mMatrix3D) {
+ mMatrix3D = new gfx::Matrix4x4(gfx::Matrix4x4::From2D(*mMatrix2D));
+ mMatrix2D = nullptr;
+ }
+}
+
+DOMMatrix*
+DOMMatrix::MultiplySelf(const DOMMatrix& aOther)
+{
+ if (aOther.Identity()) {
+ return this;
+ }
+
+ if (aOther.Is2D()) {
+ if (mMatrix3D) {
+ *mMatrix3D = gfx::Matrix4x4::From2D(*aOther.mMatrix2D) * *mMatrix3D;
+ } else {
+ *mMatrix2D = *aOther.mMatrix2D * *mMatrix2D;
+ }
+ } else {
+ Ensure3DMatrix();
+ *mMatrix3D = *aOther.mMatrix3D * *mMatrix3D;
+ }
+
+ return this;
+}
+
+DOMMatrix*
+DOMMatrix::PreMultiplySelf(const DOMMatrix& aOther)
+{
+ if (aOther.Identity()) {
+ return this;
+ }
+
+ if (aOther.Is2D()) {
+ if (mMatrix3D) {
+ *mMatrix3D = *mMatrix3D * gfx::Matrix4x4::From2D(*aOther.mMatrix2D);
+ } else {
+ *mMatrix2D = *mMatrix2D * *aOther.mMatrix2D;
+ }
+ } else {
+ Ensure3DMatrix();
+ *mMatrix3D = *mMatrix3D * *aOther.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 aScale, double aOriginX, double aOriginY)
+{
+ ScaleNonUniformSelf(aScale, aScale, 1.0, aOriginX, aOriginY, 0);
+
+ return this;
+}
+
+DOMMatrix*
+DOMMatrix::Scale3dSelf(double aScale, double aOriginX,
+ double aOriginY, double aOriginZ)
+{
+ ScaleNonUniformSelf(aScale, aScale, aScale, aOriginX, aOriginY, aOriginZ);
+
+ return this;
+}
+
+DOMMatrix*
+DOMMatrix::ScaleNonUniformSelf(double aScaleX,
+ double aScaleY,
+ double aScaleZ,
+ double aOriginX,
+ double aOriginY,
+ double aOriginZ)
+{
+ if (aScaleX == 1.0 && aScaleY == 1.0 && aScaleZ == 1.0) {
+ return this;
+ }
+
+ TranslateSelf(aOriginX, aOriginY, aOriginZ);
+
+ if (mMatrix3D || aScaleZ != 1.0 || aOriginZ != 0) {
+ Ensure3DMatrix();
+ gfx::Matrix4x4 m;
+ m._11 = aScaleX;
+ m._22 = aScaleY;
+ m._33 = aScaleZ;
+ *mMatrix3D = m * *mMatrix3D;
+ } else {
+ gfx::Matrix m;
+ m._11 = aScaleX;
+ m._22 = aScaleY;
+ *mMatrix2D = m * *mMatrix2D;
+ }
+
+ TranslateSelf(-aOriginX, -aOriginY, -aOriginZ);
+
+ return this;
+}
+
+DOMMatrix*
+DOMMatrix::RotateFromVectorSelf(double aX, double aY)
+{
+ if (aX == 0.0 || aY == 0.0) {
+ return this;
+ }
+
+ RotateSelf(atan2(aY, aX) / radPerDegree);
+
+ return this;
+}
+
+DOMMatrix*
+DOMMatrix::RotateSelf(double aAngle, double aOriginX, double aOriginY)
+{
+ if (fmod(aAngle, 360) == 0) {
+ return this;
+ }
+
+ TranslateSelf(aOriginX, aOriginY);
+
+ if (mMatrix3D) {
+ RotateAxisAngleSelf(0, 0, 1, aAngle);
+ } else {
+ *mMatrix2D = mMatrix2D->PreRotate(aAngle * radPerDegree);
+ }
+
+ TranslateSelf(-aOriginX, -aOriginY);
+
+ return this;
+}
+
+DOMMatrix*
+DOMMatrix::RotateAxisAngleSelf(double aX, double aY,
+ double aZ, double aAngle)
+{
+ if (fmod(aAngle, 360) == 0) {
+ return this;
+ }
+
+ aAngle *= radPerDegree;
+
+ Ensure3DMatrix();
+ gfx::Matrix4x4 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::Matrix4x4 m;
+ m._21 = tan(aSx * radPerDegree);
+ *mMatrix3D = m * *mMatrix3D;
+ } else {
+ gfx::Matrix 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::Matrix4x4 m;
+ m._12 = tan(aSy * radPerDegree);
+ *mMatrix3D = m * *mMatrix3D;
+ } else {
+ gfx::Matrix 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 = new gfx::Matrix4x4();
+ mMatrix3D->SetNAN();
+ }
+
+ return this;
+}
+
+DOMMatrix*
+DOMMatrix::SetMatrixValue(const nsAString& aTransformList, ErrorResult& aRv)
+{
+ SVGTransformListParser parser(aTransformList);
+ if (!parser.Parse()) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ } else {
+ mMatrix3D = nullptr;
+ mMatrix2D = new gfx::Matrix();
+ gfxMatrix result;
+ const nsTArray<nsSVGTransform>& mItems = parser.GetTransformList();
+
+ for (uint32_t i = 0; i < mItems.Length(); ++i) {
+ result.PreMultiply(mItems[i].GetMatrix());
+ }
+
+ SetA(result._11);
+ SetB(result._12);
+ SetC(result._21);
+ SetD(result._22);
+ SetE(result._31);
+ SetF(result._32);
+ }
+
+ return this;
+}
+
+JSObject*
+DOMMatrix::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMMatrixBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/DOMMatrix.h b/dom/base/DOMMatrix.h
new file mode 100644
index 000000000..db5d03f8a
--- /dev/null
+++ b/dom/base/DOMMatrix.h
@@ -0,0 +1,258 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/gfx/Matrix.h" // for Matrix4x4
+
+namespace mozilla {
+namespace dom {
+
+class GlobalObject;
+class DOMMatrix;
+class DOMPoint;
+struct DOMPointInit;
+
+class DOMMatrixReadOnly : public nsWrapperCache
+{
+public:
+ explicit DOMMatrixReadOnly(nsISupports* aParent)
+ : mParent(aParent), mMatrix2D(new gfx::Matrix())
+ {
+ }
+
+ DOMMatrixReadOnly(nsISupports* aParent, const DOMMatrixReadOnly& other)
+ : mParent(aParent)
+ {
+ if (other.mMatrix2D) {
+ mMatrix2D = new gfx::Matrix(*other.mMatrix2D);
+ } else {
+ mMatrix3D = new gfx::Matrix4x4(*other.mMatrix3D);
+ }
+ }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMMatrixReadOnly)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMMatrixReadOnly)
+
+#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
+
+ already_AddRefed<DOMMatrix> Translate(double aTx,
+ double aTy,
+ double aTz = 0) const;
+ already_AddRefed<DOMMatrix> Scale(double aScale,
+ double aOriginX = 0,
+ double aOriginY = 0) 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 = 1.0,
+ double aScaleZ = 1.0,
+ double aOriginX = 0,
+ double aOriginY = 0,
+ double aOriginZ = 0) const;
+ already_AddRefed<DOMMatrix> Rotate(double aAngle,
+ double aOriginX = 0,
+ double aOriginY = 0) 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 DOMMatrix& aOther) const;
+ already_AddRefed<DOMMatrix> FlipX() const;
+ already_AddRefed<DOMMatrix> FlipY() const;
+ already_AddRefed<DOMMatrix> Inverse() const;
+
+ bool Is2D() const;
+ bool Identity() 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);
+protected:
+ nsCOMPtr<nsISupports> mParent;
+ nsAutoPtr<gfx::Matrix> mMatrix2D;
+ nsAutoPtr<gfx::Matrix4x4> mMatrix3D;
+
+ virtual ~DOMMatrixReadOnly() {}
+
+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)
+ {}
+
+ static already_AddRefed<DOMMatrix>
+ Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
+ static already_AddRefed<DOMMatrix>
+ Constructor(const GlobalObject& aGlobal, const nsAString& aTransformList, ErrorResult& aRv);
+ static already_AddRefed<DOMMatrix>
+ Constructor(const GlobalObject& aGlobal, const DOMMatrixReadOnly& aOther, ErrorResult& aRv);
+ static already_AddRefed<DOMMatrix>
+ Constructor(const GlobalObject& aGlobal, const Float32Array& aArray32, ErrorResult& aRv);
+ static already_AddRefed<DOMMatrix>
+ Constructor(const GlobalObject& aGlobal, const Float64Array& aArray64, ErrorResult& aRv);
+ static already_AddRefed<DOMMatrix>
+ Constructor(const GlobalObject& aGlobal, const Sequence<double>& aNumberSequence, ErrorResult& aRv);
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+#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)
+
+#undef Set2DMatrixMember
+#undef Set3DMatrixMember
+
+ DOMMatrix* MultiplySelf(const DOMMatrix& aOther);
+ DOMMatrix* PreMultiplySelf(const DOMMatrix& aOther);
+ DOMMatrix* TranslateSelf(double aTx,
+ double aTy,
+ double aTz = 0);
+ DOMMatrix* ScaleSelf(double aScale,
+ double aOriginX = 0,
+ double aOriginY = 0);
+ DOMMatrix* Scale3dSelf(double aScale,
+ double aOriginX = 0,
+ double aOriginY = 0,
+ double aOriginZ = 0);
+ DOMMatrix* ScaleNonUniformSelf(double aScaleX,
+ double aScaleY = 1,
+ double aScaleZ = 1,
+ double aOriginX = 0,
+ double aOriginY = 0,
+ double aOriginZ = 0);
+ DOMMatrix* RotateSelf(double aAngle,
+ double aOriginX = 0,
+ double aOriginY = 0);
+ 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 nsAString& aTransformList, ErrorResult& aRv);
+protected:
+ void Ensure3DMatrix();
+
+ virtual ~DOMMatrix() {}
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /*MOZILLA_DOM_DOMMATRIX_H_*/
diff --git a/dom/base/DOMParser.cpp b/dom/base/DOMParser.cpp
new file mode 100644
index 000000000..55911d477
--- /dev/null
+++ b/dom/base/DOMParser.cpp
@@ -0,0 +1,495 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMDocument.h"
+#include "nsNetUtil.h"
+#include "nsIStreamListener.h"
+#include "nsStringStream.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsCRT.h"
+#include "nsStreamUtils.h"
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsError.h"
+#include "nsPIDOMWindow.h"
+#include "nsNullPrincipal.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+DOMParser::DOMParser()
+ : mAttemptedInit(false)
+{
+}
+
+DOMParser::~DOMParser()
+{
+}
+
+// QueryInterface implementation for DOMParser
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMParser)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMParser)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMParser)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMParser, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMParser)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMParser)
+
+static const char*
+StringFromSupportedType(SupportedType aType)
+{
+ return SupportedTypeValues::strings[static_cast<int>(aType)].value;
+}
+
+already_AddRefed<nsIDocument>
+DOMParser::ParseFromString(const nsAString& aStr, SupportedType aType,
+ ErrorResult& rv)
+{
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ rv = ParseFromString(aStr,
+ StringFromSupportedType(aType),
+ getter_AddRefs(domDocument));
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument));
+ return document.forget();
+}
+
+NS_IMETHODIMP
+DOMParser::ParseFromString(const char16_t *str,
+ const char *contentType,
+ nsIDOMDocument **aResult)
+{
+ NS_ENSURE_ARG(str);
+ // Converting a string to an enum value manually is a bit of a pain,
+ // so let's just use a helper that takes a content-type string.
+ return ParseFromString(nsDependentString(str), contentType, aResult);
+}
+
+nsresult
+DOMParser::ParseFromString(const nsAString& str,
+ const char *contentType,
+ nsIDOMDocument **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+
+ if (!nsCRT::strcmp(contentType, "text/html")) {
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ rv = SetUpDocument(DocumentFlavorHTML, getter_AddRefs(domDocument));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(domDocument);
+
+ // Keep the XULXBL state, base URL and principal setting in sync with the
+ // XML case
+
+ if (nsContentUtils::IsSystemPrincipal(mOriginalPrincipal)) {
+ document->ForceEnableXULXBL();
+ }
+
+ // Make sure to give this document the right base URI
+ document->SetBaseURI(mBaseURI);
+ // And the right principal
+ document->SetPrincipal(mPrincipal);
+
+ rv = nsContentUtils::ParseDocumentHTML(str, document, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ domDocument.forget(aResult);
+ return rv;
+ }
+
+ nsAutoCString utf8str;
+ // Convert from UTF16 to UTF8 using fallible allocations
+ if (!AppendUTF16toUTF8(str, utf8str, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // The new stream holds a reference to the buffer
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ utf8str.get(), utf8str.Length(),
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return ParseFromStream(stream, "UTF-8", utf8str.Length(), contentType, aResult);
+}
+
+already_AddRefed<nsIDocument>
+DOMParser::ParseFromBuffer(const Sequence<uint8_t>& aBuf, uint32_t aBufLen,
+ SupportedType aType, ErrorResult& rv)
+{
+ if (aBufLen > aBuf.Length()) {
+ rv.Throw(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY);
+ return nullptr;
+ }
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ rv = DOMParser::ParseFromBuffer(aBuf.Elements(), aBufLen,
+ StringFromSupportedType(aType),
+ getter_AddRefs(domDocument));
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument));
+ return document.forget();
+}
+
+already_AddRefed<nsIDocument>
+DOMParser::ParseFromBuffer(const Uint8Array& aBuf, uint32_t aBufLen,
+ SupportedType aType, ErrorResult& rv)
+{
+ aBuf.ComputeLengthAndData();
+
+ if (aBufLen > aBuf.Length()) {
+ rv.Throw(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY);
+ return nullptr;
+ }
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ rv = DOMParser::ParseFromBuffer(aBuf.Data(), aBufLen,
+ StringFromSupportedType(aType),
+ getter_AddRefs(domDocument));
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument));
+ return document.forget();
+}
+
+NS_IMETHODIMP
+DOMParser::ParseFromBuffer(const uint8_t *buf,
+ uint32_t bufLen,
+ const char *contentType,
+ nsIDOMDocument **aResult)
+{
+ NS_ENSURE_ARG_POINTER(buf);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // The new stream holds a reference to the buffer
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ reinterpret_cast<const char *>(buf),
+ bufLen, NS_ASSIGNMENT_DEPEND);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return ParseFromStream(stream, nullptr, bufLen, contentType, aResult);
+}
+
+
+already_AddRefed<nsIDocument>
+DOMParser::ParseFromStream(nsIInputStream* aStream,
+ const nsAString& aCharset,
+ int32_t aContentLength,
+ SupportedType aType,
+ ErrorResult& rv)
+{
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ rv = DOMParser::ParseFromStream(aStream,
+ NS_ConvertUTF16toUTF8(aCharset).get(),
+ aContentLength,
+ StringFromSupportedType(aType),
+ getter_AddRefs(domDocument));
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument));
+ return document.forget();
+}
+
+NS_IMETHODIMP
+DOMParser::ParseFromStream(nsIInputStream *stream,
+ const char *charset,
+ int32_t contentLength,
+ const char *contentType,
+ nsIDOMDocument **aResult)
+{
+ NS_ENSURE_ARG(stream);
+ NS_ENSURE_ARG(contentType);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ bool svg = nsCRT::strcmp(contentType, "image/svg+xml") == 0;
+
+ // For now, we can only create XML documents.
+ //XXXsmaug Should we create an HTMLDocument (in XHTML mode)
+ // for "application/xhtml+xml"?
+ if ((nsCRT::strcmp(contentType, "text/xml") != 0) &&
+ (nsCRT::strcmp(contentType, "application/xml") != 0) &&
+ (nsCRT::strcmp(contentType, "application/xhtml+xml") != 0) &&
+ !svg)
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsresult rv;
+
+ // Put the nsCOMPtr out here so we hold a ref to the stream as needed
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ if (!NS_InputStreamIsBuffered(stream)) {
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream,
+ 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ stream = bufferedStream;
+ }
+
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ rv = SetUpDocument(svg ? DocumentFlavorSVG : DocumentFlavorLegacyGuess,
+ getter_AddRefs(domDocument));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a fake channel
+ nsCOMPtr<nsIChannel> parserChannel;
+ NS_NewInputStreamChannel(getter_AddRefs(parserChannel),
+ mDocumentURI,
+ nullptr, // aStream
+ mOriginalPrincipal,
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
+ nsIContentPolicy::TYPE_OTHER,
+ nsDependentCString(contentType));
+ NS_ENSURE_STATE(parserChannel);
+
+ if (charset) {
+ parserChannel->SetContentCharset(nsDependentCString(charset));
+ }
+
+ // Tell the document to start loading
+ nsCOMPtr<nsIStreamListener> listener;
+
+ // 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? Then we wouldn't need to mess around with
+ // SetPrincipal, etc, probably!
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(domDocument));
+ if (!document) return NS_ERROR_FAILURE;
+
+ // Keep the XULXBL state, base URL and principal setting in sync with the
+ // HTML case
+
+ if (nsContentUtils::IsSystemPrincipal(mOriginalPrincipal)) {
+ document->ForceEnableXULXBL();
+ }
+
+ rv = document->StartDocumentLoad(kLoadAsData, parserChannel,
+ nullptr, nullptr,
+ getter_AddRefs(listener),
+ false);
+
+ // Make sure to give this document the right base URI
+ document->SetBaseURI(mBaseURI);
+
+ // And the right principal
+ document->SetPrincipal(mPrincipal);
+
+ if (NS_FAILED(rv) || !listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Now start pumping data to the listener
+ nsresult status;
+
+ rv = listener->OnStartRequest(parserChannel, nullptr);
+ if (NS_FAILED(rv))
+ parserChannel->Cancel(rv);
+ parserChannel->GetStatus(&status);
+
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
+ rv = listener->OnDataAvailable(parserChannel, nullptr, stream, 0,
+ contentLength);
+ if (NS_FAILED(rv))
+ parserChannel->Cancel(rv);
+ parserChannel->GetStatus(&status);
+ }
+
+ rv = listener->OnStopRequest(parserChannel, nullptr, 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)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ domDocument.swap(*aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMParser::Init(nsIPrincipal* principal, nsIURI* documentURI,
+ nsIURI* baseURI, nsIGlobalObject* aScriptObject)
+{
+ NS_ENSURE_STATE(!mAttemptedInit);
+ mAttemptedInit = true;
+ NS_ENSURE_ARG(principal || documentURI);
+ mDocumentURI = documentURI;
+
+ if (!mDocumentURI) {
+ principal->GetURI(getter_AddRefs(mDocumentURI));
+ // If we have the system principal, then we'll just use the null principals
+ // uri.
+ if (!mDocumentURI && !nsContentUtils::IsSystemPrincipal(principal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ mScriptHandlingObject = do_GetWeakReference(aScriptObject);
+ mPrincipal = principal;
+ nsresult rv;
+ if (!mPrincipal) {
+ // BUG 1237080 -- in this case we're getting a chrome privilege scripted
+ // DOMParser object creation without an explicit principal set. This is
+ // now deprecated.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ nullptr,
+ nsContentUtils::eDOM_PROPERTIES,
+ "ChromeScriptedDOMParserWithoutPrincipal",
+ nullptr,
+ 0,
+ documentURI);
+
+ PrincipalOriginAttributes attrs;
+ mPrincipal = BasePrincipal::CreateCodebasePrincipal(mDocumentURI, attrs);
+ NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE);
+ mOriginalPrincipal = mPrincipal;
+ } else {
+ mOriginalPrincipal = mPrincipal;
+ if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
+ // Don't give DOMParsers the system principal. Use a null
+ // principal instead.
+ mPrincipal = nsNullPrincipal::Create();
+
+ if (!mDocumentURI) {
+ rv = mPrincipal->GetURI(getter_AddRefs(mDocumentURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ mBaseURI = baseURI;
+ // Note: if mBaseURI is null, fine. Leave it like that; that will use the
+ // documentURI as the base. Otherwise for null principals we'll get
+ // nsDocument::SetBaseURI giving errors.
+
+ NS_POSTCONDITION(mPrincipal, "Must have principal");
+ NS_POSTCONDITION(mOriginalPrincipal, "Must have original principal");
+ NS_POSTCONDITION(mDocumentURI, "Must have document URI");
+ return NS_OK;
+}
+
+/*static */already_AddRefed<DOMParser>
+DOMParser::Constructor(const GlobalObject& aOwner,
+ nsIPrincipal* aPrincipal, nsIURI* aDocumentURI,
+ nsIURI* aBaseURI, ErrorResult& rv)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+ RefPtr<DOMParser> domParser = new DOMParser(aOwner.GetAsSupports());
+ rv = domParser->InitInternal(aOwner.GetAsSupports(), aPrincipal, aDocumentURI,
+ aBaseURI);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return domParser.forget();
+}
+
+/*static */already_AddRefed<DOMParser>
+DOMParser::Constructor(const GlobalObject& aOwner,
+ ErrorResult& rv)
+{
+ RefPtr<DOMParser> domParser = new DOMParser(aOwner.GetAsSupports());
+ rv = domParser->InitInternal(aOwner.GetAsSupports(),
+ nsContentUtils::SubjectPrincipal(),
+ nullptr, nullptr);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return domParser.forget();
+}
+
+nsresult
+DOMParser::InitInternal(nsISupports* aOwner, nsIPrincipal* prin,
+ nsIURI* documentURI, nsIURI* baseURI)
+{
+ AttemptedInitMarker marker(&mAttemptedInit);
+ if (!documentURI) {
+ // No explicit documentURI; grab document and base URIs off the window our
+ // constructor was called on. Error out if anything untoward happens.
+
+ // Note that this is a behavior change as far as I can tell -- we're now
+ // using the base URI and document URI of the window off of which the
+ // DOMParser is created, not the window in which parse*() is called.
+ // Does that matter?
+
+ // Also note that |cx| matches what GetDocumentFromContext() would return,
+ // while GetDocumentFromCaller() gives us the window that the DOMParser()
+ // call was made on.
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aOwner);
+ if (!window) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ baseURI = window->GetDocBaseURI();
+ documentURI = window->GetDocumentURI();
+ if (!documentURI) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ nsCOMPtr<nsIGlobalObject> scriptglobal = do_QueryInterface(aOwner);
+ return Init(prin, documentURI, baseURI, scriptglobal);
+}
+
+void
+DOMParser::Init(nsIPrincipal* aPrincipal, nsIURI* aDocumentURI,
+ nsIURI* aBaseURI, mozilla::ErrorResult& rv)
+{
+ AttemptedInitMarker marker(&mAttemptedInit);
+
+ nsCOMPtr<nsIPrincipal> principal = aPrincipal;
+ if (!principal && !aDocumentURI) {
+ principal = nsContentUtils::SubjectPrincipal();
+ }
+
+ rv = Init(principal, aDocumentURI, aBaseURI, GetEntryGlobal());
+}
+
+nsresult
+DOMParser::SetUpDocument(DocumentFlavor aFlavor, nsIDOMDocument** aResult)
+{
+ // We should really QI to nsIGlobalObject here, but nsDocument 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 nsDocument
+ // off of nsIScriptGlobalObject, but that's a yak to shave another day.
+ nsCOMPtr<nsIScriptGlobalObject> scriptHandlingObject =
+ do_QueryReferent(mScriptHandlingObject);
+ nsresult rv;
+ if (!mPrincipal) {
+ NS_ENSURE_TRUE(!mAttemptedInit, NS_ERROR_NOT_INITIALIZED);
+ AttemptedInitMarker marker(&mAttemptedInit);
+
+ nsCOMPtr<nsIPrincipal> prin = nsNullPrincipal::Create();
+ rv = Init(prin, nullptr, nullptr, scriptHandlingObject);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ASSERTION(mPrincipal, "Must have principal by now");
+ NS_ASSERTION(mDocumentURI, "Must have document URI by now");
+
+ // Here we have to cheat a little bit... Setting the base URI won't
+ // work if the document has a null principal, so use
+ // mOriginalPrincipal when creating the document, then reset the
+ // principal.
+ return NS_NewDOMDocument(aResult, EmptyString(), EmptyString(), nullptr,
+ mDocumentURI, mBaseURI,
+ mOriginalPrincipal,
+ true,
+ scriptHandlingObject,
+ aFlavor);
+}
diff --git a/dom/base/DOMParser.h b/dom/base/DOMParser.h
new file mode 100644
index 000000000..77c14bc7c
--- /dev/null
+++ b/dom/base/DOMParser.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_DOMParser_h_
+#define mozilla_dom_DOMParser_h_
+
+#include "nsCOMPtr.h"
+#include "nsIDocument.h"
+#include "nsIDOMParser.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/DOMParserBinding.h"
+#include "mozilla/dom/TypedArray.h"
+
+class nsIDocument;
+
+namespace mozilla {
+namespace dom {
+
+class DOMParser final : public nsIDOMParser,
+ public nsSupportsWeakReference,
+ public nsWrapperCache
+{
+ typedef mozilla::dom::GlobalObject GlobalObject;
+
+ virtual ~DOMParser();
+
+public:
+ DOMParser();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(DOMParser,
+ nsIDOMParser)
+
+ // nsIDOMParser
+ NS_DECL_NSIDOMPARSER
+
+ // WebIDL API
+ static already_AddRefed<DOMParser>
+ Constructor(const GlobalObject& aOwner,
+ mozilla::ErrorResult& rv);
+
+ static already_AddRefed<DOMParser>
+ Constructor(const GlobalObject& aOwner,
+ nsIPrincipal* aPrincipal, nsIURI* aDocumentURI, nsIURI* aBaseURI,
+ mozilla::ErrorResult& rv);
+
+ already_AddRefed<nsIDocument>
+ ParseFromString(const nsAString& aStr, mozilla::dom::SupportedType aType,
+ mozilla::ErrorResult& rv);
+
+ already_AddRefed<nsIDocument>
+ ParseFromBuffer(const mozilla::dom::Sequence<uint8_t>& aBuf,
+ uint32_t aBufLen, mozilla::dom::SupportedType aType,
+ mozilla::ErrorResult& rv);
+
+ already_AddRefed<nsIDocument>
+ ParseFromBuffer(const mozilla::dom::Uint8Array& aBuf, uint32_t aBufLen,
+ mozilla::dom::SupportedType aType,
+ mozilla::ErrorResult& rv);
+
+ already_AddRefed<nsIDocument>
+ ParseFromStream(nsIInputStream* aStream, const nsAString& aCharset,
+ int32_t aContentLength, mozilla::dom::SupportedType aType,
+ mozilla::ErrorResult& rv);
+
+ void Init(nsIPrincipal* aPrincipal, nsIURI* aDocumentURI,
+ nsIURI* aBaseURI, mozilla::ErrorResult& rv);
+
+ nsISupports* GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::DOMParserBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+private:
+ explicit DOMParser(nsISupports* aOwner) : mOwner(aOwner), mAttemptedInit(false)
+ {
+ MOZ_ASSERT(aOwner);
+ }
+
+ nsresult InitInternal(nsISupports* aOwner, nsIPrincipal* prin,
+ nsIURI* documentURI, nsIURI* baseURI);
+
+ nsresult SetUpDocument(DocumentFlavor aFlavor, nsIDOMDocument** aResult);
+
+ // Helper for ParseFromString
+ nsresult ParseFromString(const nsAString& str, const char *contentType,
+ nsIDOMDocument **aResult);
+
+ class AttemptedInitMarker {
+ public:
+ explicit AttemptedInitMarker(bool* aAttemptedInit) :
+ mAttemptedInit(aAttemptedInit)
+ {}
+
+ ~AttemptedInitMarker() {
+ *mAttemptedInit = true;
+ }
+
+ private:
+ bool* mAttemptedInit;
+ };
+
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIPrincipal> mOriginalPrincipal;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsWeakPtr mScriptHandlingObject;
+
+ bool mAttemptedInit;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/DOMPoint.cpp b/dom/base/DOMPoint.cpp
new file mode 100644
index 000000000..0ea5d05ae
--- /dev/null
+++ b/dom/base/DOMPoint.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/DOMPoint.h"
+
+#include "mozilla/dom/DOMPointBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMPointReadOnly, mParent)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMPointReadOnly, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMPointReadOnly, Release)
+
+already_AddRefed<DOMPoint>
+DOMPoint::Constructor(const GlobalObject& aGlobal, const DOMPointInit& aParams,
+ ErrorResult& aRV)
+{
+ 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, ErrorResult& aRV)
+{
+ RefPtr<DOMPoint> obj =
+ new DOMPoint(aGlobal.GetAsSupports(), aX, aY, aZ, aW);
+ return obj.forget();
+}
+
+JSObject*
+DOMPoint::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMPointBinding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/dom/base/DOMPoint.h b/dom/base/DOMPoint.h
new file mode 100644
index 000000000..d9a429be5
--- /dev/null
+++ b/dom/base/DOMPoint.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+
+namespace mozilla {
+namespace dom {
+
+class GlobalObject;
+struct DOMPointInit;
+
+class DOMPointReadOnly : public nsWrapperCache
+{
+public:
+ DOMPointReadOnly(nsISupports* aParent, double aX, double aY,
+ double aZ, double aW)
+ : mParent(aParent)
+ , mX(aX)
+ , mY(aY)
+ , mZ(aZ)
+ , mW(aW)
+ {
+ }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMPointReadOnly)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMPointReadOnly)
+
+ double X() const { return mX; }
+ double Y() const { return mY; }
+ double Z() const { return mZ; }
+ double W() const { return mW; }
+
+protected:
+ virtual ~DOMPointReadOnly() {}
+
+ 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>
+ Constructor(const GlobalObject& aGlobal, const DOMPointInit& aParams,
+ ErrorResult& aRV);
+ static already_AddRefed<DOMPoint>
+ Constructor(const GlobalObject& aGlobal, double aX, double aY,
+ double aZ, double aW, ErrorResult& aRV);
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ 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 000000000..3e10f05e5
--- /dev/null
+++ b/dom/base/DOMQuad.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/DOMQuad.h"
+
+#include "mozilla/dom/DOMQuadBinding.h"
+#include "mozilla/dom/DOMPoint.h"
+#include "mozilla/dom/DOMRect.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMQuad, mParent, mBounds, mPoints[0],
+ mPoints[1], mPoints[2], mPoints[3])
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMQuad, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMQuad, Release)
+
+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()
+{
+}
+
+JSObject*
+DOMQuad::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMQuadBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DOMQuad>
+DOMQuad::Constructor(const GlobalObject& aGlobal,
+ const DOMPointInit& aP1,
+ const DOMPointInit& aP2,
+ const DOMPointInit& aP3,
+ const DOMPointInit& aP4,
+ ErrorResult& aRV)
+{
+ RefPtr<DOMQuad> obj = new DOMQuad(aGlobal.GetAsSupports());
+ obj->mPoints[0] = DOMPoint::Constructor(aGlobal, aP1, aRV);
+ obj->mPoints[1] = DOMPoint::Constructor(aGlobal, aP2, aRV);
+ obj->mPoints[2] = DOMPoint::Constructor(aGlobal, aP3, aRV);
+ obj->mPoints[3] = DOMPoint::Constructor(aGlobal, aP4, aRV);
+ return obj.forget();
+}
+
+already_AddRefed<DOMQuad>
+DOMQuad::Constructor(const GlobalObject& aGlobal, const DOMRectReadOnly& aRect,
+ ErrorResult& aRV)
+{
+ 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();
+}
+
+class DOMQuad::QuadBounds final : public DOMRectReadOnly
+{
+public:
+ explicit QuadBounds(DOMQuad* aQuad)
+ : DOMRectReadOnly(aQuad->GetParentObject())
+ , mQuad(aQuad)
+ {}
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(QuadBounds, DOMRectReadOnly)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual double X() const override
+ {
+ double x1, x2;
+ GetHorizontalMinMax(&x1, &x2);
+ return x1;
+ }
+ virtual double Y() const override
+ {
+ double y1, y2;
+ GetVerticalMinMax(&y1, &y2);
+ return y1;
+ }
+ virtual double Width() const override
+ {
+ double x1, x2;
+ GetHorizontalMinMax(&x1, &x2);
+ return x2 - x1;
+ }
+ virtual double Height() const override
+ {
+ double y1, y2;
+ GetVerticalMinMax(&y1, &y2);
+ return y2 - y1;
+ }
+
+ void GetHorizontalMinMax(double* aX1, double* aX2) const
+ {
+ double x1, x2;
+ x1 = x2 = mQuad->Point(0)->X();
+ for (uint32_t i = 1; i < 4; ++i) {
+ double x = mQuad->Point(i)->X();
+ x1 = std::min(x1, x);
+ x2 = std::max(x2, x);
+ }
+ *aX1 = x1;
+ *aX2 = x2;
+ }
+
+ void GetVerticalMinMax(double* aY1, double* aY2) const
+ {
+ double y1, y2;
+ y1 = y2 = mQuad->Point(0)->Y();
+ for (uint32_t i = 1; i < 4; ++i) {
+ double y = mQuad->Point(i)->Y();
+ y1 = std::min(y1, y);
+ y2 = std::max(y2, y);
+ }
+ *aY1 = y1;
+ *aY2 = y2;
+ }
+
+protected:
+ virtual ~QuadBounds() {}
+
+ RefPtr<DOMQuad> mQuad;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMQuad::QuadBounds, DOMRectReadOnly, mQuad)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMQuad::QuadBounds)
+NS_INTERFACE_MAP_END_INHERITING(DOMRectReadOnly)
+
+NS_IMPL_ADDREF_INHERITED(DOMQuad::QuadBounds, DOMRectReadOnly)
+NS_IMPL_RELEASE_INHERITED(DOMQuad::QuadBounds, DOMRectReadOnly)
+
+DOMRectReadOnly*
+DOMQuad::Bounds() const
+{
+ if (!mBounds) {
+ mBounds = new QuadBounds(const_cast<DOMQuad*>(this));
+ }
+ return mBounds;
+}
diff --git a/dom/base/DOMQuad.h b/dom/base/DOMQuad.h
new file mode 100644
index 000000000..b42df6d2d
--- /dev/null
+++ b/dom/base/DOMQuad.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_DOMQUAD_H_
+#define MOZILLA_DOMQUAD_H_
+
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ErrorResult.h"
+#include "Units.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMRectReadOnly;
+class DOMPoint;
+struct DOMPointInit;
+
+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_SCRIPT_HOLDER_NATIVE_CLASS(DOMQuad)
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<DOMQuad>
+ Constructor(const GlobalObject& aGlobal,
+ const DOMPointInit& aP1,
+ const DOMPointInit& aP2,
+ const DOMPointInit& aP3,
+ const DOMPointInit& aP4,
+ ErrorResult& aRV);
+ static already_AddRefed<DOMQuad>
+ Constructor(const GlobalObject& aGlobal, const DOMRectReadOnly& aRect,
+ ErrorResult& aRV);
+
+ DOMRectReadOnly* Bounds() 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) { return mPoints[aIndex]; }
+
+protected:
+ class QuadBounds;
+
+ nsCOMPtr<nsISupports> mParent;
+ RefPtr<DOMPoint> mPoints[4];
+ mutable RefPtr<QuadBounds> mBounds; // allocated lazily
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /*MOZILLA_DOMRECT_H_*/
diff --git a/dom/base/DOMRect.cpp b/dom/base/DOMRect.cpp
new file mode 100644
index 000000000..2c44c6a07
--- /dev/null
+++ b/dom/base/DOMRect.cpp
@@ -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/. */
+
+#include "mozilla/dom/DOMRect.h"
+
+#include "nsPresContext.h"
+#include "mozilla/dom/DOMRectListBinding.h"
+#include "mozilla/dom/DOMRectBinding.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 DOMRectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// -----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(DOMRect, DOMRectReadOnly, nsIDOMClientRect)
+
+#define FORWARD_GETTER(_name) \
+ NS_IMETHODIMP \
+ DOMRect::Get ## _name(float* aResult) \
+ { \
+ *aResult = float(_name()); \
+ return NS_OK; \
+ }
+
+FORWARD_GETTER(Left)
+FORWARD_GETTER(Top)
+FORWARD_GETTER(Right)
+FORWARD_GETTER(Bottom)
+FORWARD_GETTER(Width)
+FORWARD_GETTER(Height)
+
+JSObject*
+DOMRect::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ MOZ_ASSERT(mParent);
+ return DOMRectBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DOMRect>
+DOMRect::Constructor(const GlobalObject& aGlobal, ErrorResult& aRV)
+{
+ RefPtr<DOMRect> obj =
+ new DOMRect(aGlobal.GetAsSupports(), 0.0, 0.0, 0.0, 0.0);
+ return obj.forget();
+}
+
+already_AddRefed<DOMRect>
+DOMRect::Constructor(const GlobalObject& aGlobal, double aX, double aY,
+ double aWidth, double aHeight, ErrorResult& aRV)
+{
+ RefPtr<DOMRect> obj =
+ new DOMRect(aGlobal.GetAsSupports(), aX, aY, aWidth, aHeight);
+ return obj.forget();
+}
+
+// -----------------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMRectList, mParent, mArray)
+
+NS_INTERFACE_TABLE_HEAD(DOMRectList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(DOMRectList, nsIDOMClientRectList)
+ 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)
+
+
+NS_IMETHODIMP
+DOMRectList::GetLength(uint32_t* aLength)
+{
+ *aLength = Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRectList::Item(uint32_t aIndex, nsIDOMClientRect** aReturn)
+{
+ NS_IF_ADDREF(*aReturn = Item(aIndex));
+ return NS_OK;
+}
+
+JSObject*
+DOMRectList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::DOMRectListBinding::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/nsPresContext::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 000000000..d3d103000
--- /dev/null
+++ b/dom/base/DOMRect.h
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMClientRect.h"
+#include "nsIDOMClientRectList.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ErrorResult.h"
+#include <algorithm>
+
+struct nsRect;
+
+namespace mozilla {
+namespace dom {
+
+class DOMRectReadOnly : public nsISupports
+ , public nsWrapperCache
+{
+protected:
+ virtual ~DOMRectReadOnly() {}
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMRectReadOnly)
+
+ explicit DOMRectReadOnly(nsISupports* aParent)
+ : mParent(aParent)
+ {
+ }
+
+ nsISupports* GetParentObject() const
+ {
+ MOZ_ASSERT(mParent);
+ return mParent;
+ }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual double X() const = 0;
+ virtual double Y() const = 0;
+ virtual double Width() const = 0;
+ virtual double Height() const = 0;
+
+ double Left() const
+ {
+ double x = X(), w = Width();
+ return std::min(x, x + w);
+ }
+ double Top() const
+ {
+ double y = Y(), h = Height();
+ return std::min(y, y + h);
+ }
+ double Right() const
+ {
+ double x = X(), w = Width();
+ return std::max(x, x + w);
+ }
+ double Bottom() const
+ {
+ double y = Y(), h = Height();
+ return std::max(y, y + h);
+ }
+
+protected:
+ nsCOMPtr<nsISupports> mParent;
+};
+
+class DOMRect final : public DOMRectReadOnly
+ , public nsIDOMClientRect
+{
+public:
+ explicit DOMRect(nsISupports* aParent, double aX = 0, double aY = 0,
+ double aWidth = 0, double aHeight = 0)
+ : DOMRectReadOnly(aParent)
+ , mX(aX)
+ , mY(aY)
+ , mWidth(aWidth)
+ , mHeight(aHeight)
+ {
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMCLIENTRECT
+
+ static already_AddRefed<DOMRect>
+ Constructor(const GlobalObject& aGlobal, ErrorResult& aRV);
+ static already_AddRefed<DOMRect>
+ Constructor(const GlobalObject& aGlobal, double aX, double aY,
+ double aWidth, double aHeight, ErrorResult& aRV);
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void SetRect(float aX, float aY, float aWidth, float aHeight) {
+ mX = aX; mY = aY; mWidth = aWidth; mHeight = aHeight;
+ }
+ void SetLayoutRect(const nsRect& aLayoutRect);
+
+ virtual double X() const override
+ {
+ return mX;
+ }
+ virtual double Y() const override
+ {
+ return mY;
+ }
+ virtual double Width() const override
+ {
+ return mWidth;
+ }
+ virtual double Height() const override
+ {
+ return mHeight;
+ }
+
+ void SetX(double aX)
+ {
+ mX = aX;
+ }
+ void SetY(double aY)
+ {
+ mY = aY;
+ }
+ void SetWidth(double aWidth)
+ {
+ mWidth = aWidth;
+ }
+ void SetHeight(double aHeight)
+ {
+ mHeight = aHeight;
+ }
+
+protected:
+ double mX, mY, mWidth, mHeight;
+
+private:
+ ~DOMRect() {};
+};
+
+class DOMRectList final : public nsIDOMClientRectList,
+ public nsWrapperCache
+{
+ ~DOMRectList() {}
+
+public:
+ explicit DOMRectList(nsISupports *aParent) : mParent(aParent)
+ {
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMRectList)
+
+ NS_DECL_NSIDOMCLIENTRECTLIST
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject()
+ {
+ return mParent;
+ }
+
+ void Append(DOMRect* aElement) { mArray.AppendElement(aElement); }
+
+ static DOMRectList* FromSupports(nsISupports* aSupports)
+ {
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIDOMClientRectList> list_qi = do_QueryInterface(aSupports);
+
+ // If this assertion fires the QI implementation for the object in
+ // question doesn't use the nsIDOMClientRectList pointer as the nsISupports
+ // pointer. That must be fixed, or we'll crash...
+ NS_ASSERTION(list_qi == static_cast<nsIDOMClientRectList*>(aSupports),
+ "Uh, fix QI!");
+ }
+#endif
+
+ return static_cast<DOMRectList*>(aSupports);
+ }
+
+ 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 dom
+} // namespace mozilla
+
+#endif /*MOZILLA_DOMRECT_H_*/
diff --git a/dom/base/DOMRequest.cpp b/dom/base/DOMRequest.cpp
new file mode 100644
index 000000000..ce6cd1dcd
--- /dev/null
+++ b/dom/base/DOMRequest.cpp
@@ -0,0 +1,382 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "DOMError.h"
+#include "nsThreadUtils.h"
+#include "DOMCursor.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::DOMError;
+using mozilla::dom::DOMRequest;
+using mozilla::dom::DOMRequestService;
+using mozilla::dom::DOMCursor;
+using mozilla::dom::Promise;
+using mozilla::dom::AutoJSAPI;
+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()
+{
+ mResult.setUndefined();
+ mozilla::DropJSObjects(this);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(DOMRequest)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DOMRequest,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DOMRequest,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+ tmp->mResult.setUndefined();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DOMRequest,
+ DOMEventTargetHelper)
+ // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
+ // DOMEventTargetHelper does it for us.
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResult)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DOMRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMDOMRequest)
+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 DOMRequestBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_EVENT_HANDLER(DOMRequest, success)
+NS_IMPL_EVENT_HANDLER(DOMRequest, error)
+
+NS_IMETHODIMP
+DOMRequest::GetReadyState(nsAString& aReadyState)
+{
+ DOMRequestReadyState readyState = ReadyState();
+ switch (readyState) {
+ case DOMRequestReadyState::Pending:
+ aReadyState.AssignLiteral("pending");
+ break;
+ case DOMRequestReadyState::Done:
+ aReadyState.AssignLiteral("done");
+ break;
+ default:
+ MOZ_CRASH("Unrecognized readyState.");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequest::GetResult(JS::MutableHandle<JS::Value> aResult)
+{
+ GetResult(nullptr, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequest::GetError(nsISupports** aError)
+{
+ NS_IF_ADDREF(*aError = GetError());
+ return NS_OK;
+}
+
+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(NS_LITERAL_STRING("success"), 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;
+ mError = new DOMError(GetOwner(), aError);
+
+ FireEvent(NS_LITERAL_STRING("error"), 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 = new DOMError(GetOwner(), aError);
+
+ FireEvent(NS_LITERAL_STRING("error"), true, true);
+
+ if (mPromise) {
+ mPromise->MaybeRejectBrokenly(mError);
+ }
+}
+
+void
+DOMRequest::FireDetailedError(DOMError* 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!");
+ NS_ASSERTION(aError, "No detailed error provided");
+
+ mDone = true;
+ mError = aError;
+
+ FireEvent(NS_LITERAL_STRING("error"), true, true);
+
+ if (mPromise) {
+ mPromise->MaybeRejectBrokenly(mError);
+ }
+}
+
+void
+DOMRequest::FireEvent(const nsAString& aType, bool aBubble, bool aCancelable)
+{
+ if (NS_FAILED(CheckInnerWindowCorrectness())) {
+ return;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ event->InitEvent(aType, aBubble, aCancelable);
+ event->SetTrusted(true);
+
+ bool dummy;
+ DispatchEvent(event, &dummy);
+}
+
+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::GetGlobalForObjectCrossCompartment(global);
+ mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv);
+}
+
+NS_IMPL_ISUPPORTS(DOMRequestService, nsIDOMRequestService)
+
+NS_IMETHODIMP
+DOMRequestService::CreateRequest(mozIDOMWindow* aWindow,
+ nsIDOMDOMRequest** aRequest)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_STATE(aWindow);
+ auto* win = nsPIDOMWindowInner::From(aWindow);
+ NS_ADDREF(*aRequest = new DOMRequest(win));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequestService::CreateCursor(mozIDOMWindow* aWindow,
+ nsICursorContinueCallback* aCallback,
+ nsIDOMDOMCursor** aCursor)
+{
+ NS_ENSURE_STATE(aWindow);
+ auto* win = nsPIDOMWindowInner::From(aWindow);
+ NS_ADDREF(*aCursor = new DOMCursor(win, aCallback));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequestService::FireSuccess(nsIDOMDOMRequest* aRequest,
+ JS::Handle<JS::Value> aResult)
+{
+ NS_ENSURE_STATE(aRequest);
+ static_cast<DOMRequest*>(aRequest)->FireSuccess(aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequestService::FireError(nsIDOMDOMRequest* aRequest,
+ const nsAString& aError)
+{
+ NS_ENSURE_STATE(aRequest);
+ static_cast<DOMRequest*>(aRequest)->FireError(aError);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequestService::FireDetailedError(nsIDOMDOMRequest* aRequest,
+ nsISupports* aError)
+{
+ NS_ENSURE_STATE(aRequest);
+ nsCOMPtr<DOMError> err = do_QueryInterface(aError);
+ NS_ENSURE_STATE(err);
+ static_cast<DOMRequest*>(aRequest)->FireDetailedError(err);
+
+ return NS_OK;
+}
+
+class FireSuccessAsyncTask : public mozilla::Runnable
+{
+
+ FireSuccessAsyncTask(DOMRequest* aRequest,
+ const JS::Value& aResult) :
+ 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) :
+ mReq(aRequest),
+ mError(aError)
+ {
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ mReq->FireError(mError);
+ return NS_OK;
+ }
+private:
+ RefPtr<DOMRequest> mReq;
+ nsString mError;
+};
+
+NS_IMETHODIMP
+DOMRequestService::FireSuccessAsync(nsIDOMDOMRequest* aRequest,
+ JS::Handle<JS::Value> aResult)
+{
+ NS_ENSURE_STATE(aRequest);
+ return FireSuccessAsyncTask::Dispatch(static_cast<DOMRequest*>(aRequest), aResult);
+}
+
+NS_IMETHODIMP
+DOMRequestService::FireErrorAsync(nsIDOMDOMRequest* aRequest,
+ const nsAString& aError)
+{
+ NS_ENSURE_STATE(aRequest);
+ nsCOMPtr<nsIRunnable> asyncTask =
+ new FireErrorAsyncTask(static_cast<DOMRequest*>(aRequest), aError);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(asyncTask));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMRequestService::FireDone(nsIDOMDOMCursor* aCursor) {
+ NS_ENSURE_STATE(aCursor);
+ static_cast<DOMCursor*>(aCursor)->FireDone();
+
+ return NS_OK;
+}
diff --git a/dom/base/DOMRequest.h b/dom/base/DOMRequest.h
new file mode 100644
index 000000000..5009452b0
--- /dev/null
+++ b/dom/base/DOMRequest.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 mozilla_dom_domrequest_h__
+#define mozilla_dom_domrequest_h__
+
+#include "nsIDOMDOMRequest.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/DOMRequestBinding.h"
+
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class AnyCallback;
+class Promise;
+
+class DOMRequest : public DOMEventTargetHelper,
+ public nsIDOMDOMRequest
+{
+protected:
+ JS::Heap<JS::Value> mResult;
+ RefPtr<DOMError> mError;
+ RefPtr<Promise> mPromise;
+ bool mDone;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMDOMREQUEST
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
+
+ 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);
+ }
+
+ DOMError* 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(DOMError* 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() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMREQUESTSERVICE
+
+ // Returns an owning reference! No one should call this but the factory.
+ static DOMRequestService* FactoryCreate()
+ {
+ DOMRequestService* res = new DOMRequestService;
+ NS_ADDREF(res);
+ return res;
+ }
+};
+
+} // 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.jsm b/dom/base/DOMRequestHelper.jsm
new file mode 100644
index 000000000..3d594872c
--- /dev/null
+++ b/dom/base/DOMRequestHelper.jsm
@@ -0,0 +1,336 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.
+ */
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+this.EXPORTED_SYMBOLS = ["DOMRequestIpcHelper"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsIMessageListenerManager");
+
+this.DOMRequestIpcHelper = 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: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
+ Ci.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: function(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;
+ } else {
+ throw Cr.NS_ERROR_FAILURE;
+ }
+ }
+
+ aMsg.weakRef ? cpmm.addWeakMessageListener(name, this)
+ : 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: function(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 ?
+ cpmm.removeWeakMessageListener(aName, this)
+ : 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: function(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.
+ let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ this.innerWindowID = util.currentInnerWindowID;
+ }
+
+ this._destroyed = false;
+
+ Services.obs.addObserver(this, "inner-window-destroyed",
+ /* weak-ref */ true);
+ },
+
+ destroyDOMRequestHelper: function() {
+ 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 ? cpmm.removeWeakMessageListener(aName, this)
+ : 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: function(aSubject, aTopic, aData) {
+ if (aTopic !== "inner-window-destroyed") {
+ return;
+ }
+
+ let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (wId != this.innerWindowID) {
+ return;
+ }
+
+ this.destroyDOMRequestHelper();
+ },
+
+ getRequestId: function(aRequest) {
+ if (!this._requests) {
+ this._requests = {};
+ }
+
+ let id = "id" + this._getRandomId();
+ this._requests[id] = aRequest;
+ return id;
+ },
+
+ getPromiseResolverId: function(aPromiseResolver) {
+ // Delegates to getRequest() since the lookup table is agnostic about
+ // storage.
+ return this.getRequestId(aPromiseResolver);
+ },
+
+ getRequest: function(aId) {
+ if (this._requests && this._requests[aId]) {
+ return this._requests[aId];
+ }
+ },
+
+ getPromiseResolver: function(aId) {
+ // Delegates to getRequest() since the lookup table is agnostic about
+ // storage.
+ return this.getRequest(aId);
+ },
+
+ removeRequest: function(aId) {
+ if (this._requests && this._requests[aId]) {
+ delete this._requests[aId];
+ }
+ },
+
+ removePromiseResolver: function(aId) {
+ // Delegates to getRequest() since the lookup table is agnostic about
+ // storage.
+ this.removeRequest(aId);
+ },
+
+ takeRequest: function(aId) {
+ if (!this._requests || !this._requests[aId]) {
+ return null;
+ }
+ let request = this._requests[aId];
+ delete this._requests[aId];
+ return request;
+ },
+
+ takePromiseResolver: function(aId) {
+ // Delegates to getRequest() since the lookup table is agnostic about
+ // storage.
+ return this.takeRequest(aId);
+ },
+
+ _getRandomId: function() {
+ return Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+ },
+
+ createRequest: function() {
+ // If we don't have a valid window object, throw.
+ if (!this._window) {
+ Cu.reportError("DOMRequestHelper trying to create a DOMRequest without a valid window, failing.");
+ throw 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: function(aPromiseInit) {
+ // If we don't have a valid window object, throw.
+ if (!this._window) {
+ Cu.reportError("DOMRequestHelper trying to create a Promise without a valid window, failing.");
+ throw 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: function(aCallback) {
+ return this.createPromise(function(aResolve, aReject) {
+ let resolverId = this.getPromiseResolverId({ resolve: aResolve, reject: aReject });
+ aCallback(resolverId);
+ }.bind(this));
+ },
+
+ forEachRequest: function(aCallback) {
+ if (!this._requests) {
+ return;
+ }
+
+ Object.keys(this._requests).forEach((aKey) => {
+ if (this.getRequest(aKey) instanceof this._window.DOMRequest) {
+ aCallback(aKey);
+ }
+ });
+ },
+
+ forEachPromiseResolver: function(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 000000000..cae00338e
--- /dev/null
+++ b/dom/base/DOMStringList.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 {
+namespace 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()
+{
+}
+
+JSObject*
+DOMStringList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMStringListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/DOMStringList.h b/dom/base/DOMStringList.h
new file mode 100644
index 000000000..d4bc2632d
--- /dev/null
+++ b/dom/base/DOMStringList.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_DOMStringList_h
+#define mozilla_dom_DOMStringList_h
+
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMStringList : public nsISupports,
+ public nsWrapperCache
+{
+protected:
+ virtual ~DOMStringList();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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 mNames should really be a fallible array; otherwise this
+ // return value is meaningless.
+ return mNames.AppendElement(aName) != nullptr;
+ }
+
+ void Clear()
+ {
+ mNames.Clear();
+ }
+
+ nsTArray<nsString>& StringArray()
+ {
+ return mNames;
+ }
+
+ void CopyList(nsTArray<nsString>& aNames)
+ {
+ aNames = mNames;
+ }
+
+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 dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_DOMStringList_h */
diff --git a/dom/base/DOMTokenListSupportedTokens.h b/dom/base/DOMTokenListSupportedTokens.h
new file mode 100644
index 000000000..f68f48554
--- /dev/null
+++ b/dom/base/DOMTokenListSupportedTokens.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/. */
+
+/*
+ * 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 {
+namespace 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 dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DOMTokenListSupportedTokens_h
diff --git a/dom/base/DirectionalityUtils.cpp b/dom/base/DirectionalityUtils.cpp
new file mode 100644
index 000000000..d9a6c4524
--- /dev/null
+++ b/dom/base/DirectionalityUtils.cpp
@@ -0,0 +1,1070 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, nsGenericDOMDataNode::SetTextInternal
+ gets called.
+ */
+
+#include "mozilla/dom/DirectionalityUtils.h"
+
+#include "nsINode.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsUnicodeProperties.h"
+#include "nsTextFragment.h"
+#include "nsAttrValue.h"
+#include "nsTextNode.h"
+#include "nsCheapSets.h"
+
+namespace mozilla {
+
+using mozilla::dom::Element;
+
+/**
+ * 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.
+ */
+static bool
+DoesNotParticipateInAutoDirection(const Element* aElement)
+{
+ mozilla::dom::NodeInfo* nodeInfo = aElement->NodeInfo();
+ return (!aElement->IsHTMLElement() ||
+ nodeInfo->Equals(nsGkAtoms::script) ||
+ nodeInfo->Equals(nsGkAtoms::style) ||
+ nodeInfo->Equals(nsGkAtoms::textarea) ||
+ aElement->IsInAnonymousSubtree());
+}
+
+static inline bool
+IsBdiWithoutDirAuto(const Element* aElement)
+{
+ // We are testing for bdi elements without explicit dir="auto", so we can't
+ // use the HasDirAuto() flag, since that will return true for bdi element with
+ // no dir attribute or an invalid dir attribute
+ return (aElement->IsHTMLElement(nsGkAtoms::bdi) &&
+ (!aElement->HasValidDir() || aElement->HasFixedDir()));
+}
+
+/**
+ * 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) ||
+ IsBdiWithoutDirAuto(aElement) ||
+ aElement->HasFixedDir());
+}
+
+/**
+ * Returns the directionality of a Unicode character
+ */
+static Directionality
+GetDirectionFromChar(uint32_t ch)
+{
+ switch(mozilla::unicode::GetBidiCat(ch)) {
+ case eCharType_RightToLeft:
+ case eCharType_RightToLeftArabic:
+ return eDir_RTL;
+
+ case eCharType_LeftToRight:
+ return eDir_LTR;
+
+ default:
+ return eDir_NotSet;
+ }
+}
+
+inline static bool NodeAffectsDirAutoAncestor(nsINode* aTextNode)
+{
+ Element* parent = aTextNode->GetParentElement();
+ return (parent &&
+ !DoesNotParticipateInAutoDirection(parent) &&
+ parent->NodeOrAncestorHasDirAuto());
+}
+
+/**
+ * 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
+ */
+static Directionality
+GetDirectionFromText(const char16_t* aText, const uint32_t aLength,
+ uint32_t* aFirstStrong = nullptr)
+{
+ const char16_t* start = aText;
+ const char16_t* end = aText + aLength;
+
+ while (start < end) {
+ uint32_t current = start - aText;
+ uint32_t ch = *start++;
+
+ if (NS_IS_HIGH_SURROGATE(ch) &&
+ start < end &&
+ NS_IS_LOW_SURROGATE(*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 nsTextFragment* aFrag,
+ uint32_t* aFirstStrong = nullptr)
+{
+ if (aFrag->Is2b()) {
+ return GetDirectionFromText(aFrag->Get2b(), aFrag->GetLength(),
+ aFirstStrong);
+ }
+
+ return GetDirectionFromText(aFrag->Get1b(), aFrag->GetLength(),
+ aFirstStrong);
+}
+
+/**
+ * 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 = true,
+ nsINode* aChangedNode = nullptr)
+{
+ MOZ_ASSERT(aElement, "Must have an element");
+ MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
+
+ if (DoesNotParticipateInAutoDirection(aElement)) {
+ return nullptr;
+ }
+
+ nsIContent* child = aElement->GetFirstChild();
+ while (child) {
+ if (child->IsElement() &&
+ DoesNotAffectDirectionOfAncestors(child->AsElement())) {
+ child = child->GetNextNonChildNode(aElement);
+ continue;
+ }
+
+ if (child->NodeType() == nsIDOMNode::TEXT_NODE &&
+ child != aChangedNode) {
+ Directionality textNodeDir = GetDirectionFromText(child->GetText());
+ if (textNodeDir != eDir_NotSet) {
+ // We found a descendant text node with strong directional characters.
+ // Set the directionality of aElement to the corresponding value.
+ aElement->SetDirectionality(textNodeDir, aNotify);
+ return static_cast<nsTextNode*>(child);
+ }
+ }
+ child = child->GetNextNode(aElement);
+ }
+
+ // 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, nsIAtom* 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();
+ }
+
+ ~nsTextNodeDirectionalityMap()
+ {
+ MOZ_COUNT_DTOR(nsTextNodeDirectionalityMap);
+ }
+
+ static void
+ nsTextNodeDirectionalityMapPropertyDestructor(void* aObject,
+ nsIAtom* 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->DeleteProperty(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() == nsIDOMNode::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)
+ {
+ MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
+ 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)
+ {
+ MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
+ // 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->DeleteProperty(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->DeleteProperty(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");
+
+ Directionality dir = eDir_LTR;
+
+ if (aElement->HasValidDir()) {
+ dir = aElement->GetDirectionality();
+ } else {
+ Element* parent = aElement->GetParentElement();
+ if (parent) {
+ // If the element 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->GetDirectionality();
+ if (parentDir != eDir_NotSet) {
+ dir = parentDir;
+ }
+ } else {
+ // If there is no parent element and no dir attribute, the directionality
+ // is LTR.
+ dir = eDir_LTR;
+ }
+
+ aElement->SetDirectionality(dir, aNotify);
+ }
+ return dir;
+}
+
+void
+SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
+ bool aNotify)
+{
+ for (nsIContent* child = aElement->GetFirstChild(); child; ) {
+ if (!child->IsElement()) {
+ child = child->GetNextNode(aElement);
+ continue;
+ }
+
+ Element* element = child->AsElement();
+ if (element->HasValidDir() || element->HasDirAuto()) {
+ child = child->GetNextNonChildNode(aElement);
+ continue;
+ }
+ element->SetDirectionality(aDir, aNotify);
+ child = child->GetNextNode(aElement);
+ }
+}
+
+/**
+ * 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;
+ Element* parent = aElement->GetParentElement();
+
+ while (parent && parent->NodeOrAncestorHasDirAuto()) {
+ 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, parent);
+ }
+ }
+ if (parent->HasDirAuto()) {
+ setByNode = WalkDescendantsSetDirectionFromText(parent, aNotify);
+ if (setByNode) {
+ nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parent);
+ }
+ break;
+ }
+ parent = parent->GetParentElement();
+ }
+}
+
+void
+WalkDescendantsResetAutoDirection(Element* aElement)
+{
+ nsIContent* child = aElement->GetFirstChild();
+ while (child) {
+ if (child->HasDirAuto()) {
+ child = child->GetNextNonChildNode(aElement);
+ continue;
+ }
+
+ if (child->NodeType() == nsIDOMNode::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);
+ }
+}
+
+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)) {
+
+ bool setAncestorDirAutoFlag =
+#ifdef DEBUG
+ true;
+#else
+ !aElement->AncestorHasDirAuto();
+#endif
+
+ if (setAncestorDirAutoFlag) {
+ nsIContent* child = aElement->GetFirstChild();
+ while (child) {
+ if (child->IsElement() &&
+ DoesNotAffectDirectionOfAncestors(child->AsElement())) {
+ child = child->GetNextNonChildNode(aElement);
+ continue;
+ }
+
+ MOZ_ASSERT(!aElement->AncestorHasDirAuto() ||
+ child->AncestorHasDirAuto(),
+ "AncestorHasDirAuto set on node but not its children");
+ child->SetAncestorHasDirAuto();
+ child = child->GetNextNode(aElement);
+ }
+ }
+ }
+
+ nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
+ if (textNode) {
+ nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
+ }
+}
+
+void
+WalkDescendantsClearAncestorDirAuto(Element* aElement)
+{
+ nsIContent* child = aElement->GetFirstChild();
+ while (child) {
+ if (child->HasDirAuto()) {
+ child = child->GetNextNonChildNode(aElement);
+ continue;
+ }
+
+ child->ClearAncestorHasDirAuto();
+ child = child->GetNextNode(aElement);
+ }
+}
+
+void SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir,
+ bool aNotify = true)
+{
+ MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
+ "Must be a text node");
+
+ Element* parent = aTextNode->GetParentElement();
+ while (parent && parent->NodeOrAncestorHasDirAuto()) {
+ if (DoesNotParticipateInAutoDirection(parent) || parent->HasFixedDir()) {
+ break;
+ }
+
+ if (parent->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) {
+ 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, parent
+ );
+ }
+ parent->SetDirectionality(aDir, aNotify);
+ nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parent);
+ SetDirectionalityOnDescendants(parent, 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 = parent->GetParentElement();
+ }
+}
+
+bool
+TextNodeWillChangeDirection(nsIContent* aTextNode, Directionality* aOldDir,
+ uint32_t aOffset)
+{
+ if (!NodeAffectsDirAutoAncestor(aTextNode)) {
+ nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
+ return false;
+ }
+
+ uint32_t firstStrong;
+ *aOldDir = GetDirectionFromText(aTextNode->GetText(), &firstStrong);
+ return (aOffset <= firstStrong);
+}
+
+void
+TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
+ bool aNotify)
+{
+ Directionality newDir = GetDirectionFromText(aTextNode->GetText());
+ 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;
+ }
+
+ Element* parent = aTextNode->GetParentElement();
+ if (parent && parent->NodeOrAncestorHasDirAuto()) {
+ aTextNode->SetAncestorHasDirAuto();
+ }
+
+ Directionality dir = GetDirectionFromText(aTextNode->GetText());
+ if (dir != eDir_NotSet) {
+ SetAncestorDirectionIfAuto(aTextNode, dir);
+ }
+}
+
+void
+ResetDirectionSetByTextNode(nsTextNode* aTextNode)
+{
+ if (!NodeAffectsDirAutoAncestor(aTextNode)) {
+ nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
+ return;
+ }
+
+ Directionality dir = GetDirectionFromText(aTextNode->GetText());
+ if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
+ nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
+ }
+}
+
+void
+SetDirectionalityFromValue(Element* aElement, const nsAString& value,
+ bool aNotify)
+{
+ Directionality dir = GetDirectionFromText(PromiseFlatString(value).get(),
+ value.Length());
+ if (dir == eDir_NotSet) {
+ dir = eDir_LTR;
+ }
+
+ aElement->SetDirectionality(dir, aNotify);
+}
+
+void
+OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
+ bool hadValidDir, bool hadDirAuto, bool aNotify)
+{
+ if (aElement->IsHTMLElement(nsGkAtoms::input)) {
+ 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(mozilla::dom::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();
+
+ nsIContent* child = aElement->GetFirstChild();
+ if (child) {
+ // If we are binding an element to the tree that already has descendants,
+ // and the parent has NodeHasDirAuto or NodeAncestorHasDirAuto, we need
+ // to set NodeAncestorHasDirAuto on all the element's descendants, except
+ // for nodes that don't affect the direction of their ancestors.
+ do {
+ if (child->IsElement() &&
+ DoesNotAffectDirectionOfAncestors(child->AsElement())) {
+ child = child->GetNextNonChildNode(aElement);
+ continue;
+ }
+
+ child->SetAncestorHasDirAuto();
+ child = child->GetNextNode(aElement);
+ } while (child);
+
+ // 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(mozilla::dom::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 000000000..ed22792ab
--- /dev/null
+++ b/dom/base/DirectionalityUtils.h
@@ -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/. */
+
+#ifndef DirectionalityUtils_h___
+#define DirectionalityUtils_h___
+
+#include "nscore.h"
+
+class nsIContent;
+class nsAString;
+class nsAttrValue;
+class nsTextNode;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+
+enum Directionality : uint8_t {
+ eDir_NotSet,
+ eDir_RTL,
+ eDir_LTR,
+ eDir_Auto
+};
+
+/**
+ * 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);
+
+/**
+ * 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(mozilla::dom::Element* aElement);
+
+/**
+ * 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(nsIContent* 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/DocGroup.cpp b/dom/base/DocGroup.cpp
new file mode 100644
index 000000000..226879985
--- /dev/null
+++ b/dom/base/DocGroup.cpp
@@ -0,0 +1,55 @@
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/TabGroup.h"
+#include "mozilla/Telemetry.h"
+#include "nsIURI.h"
+#include "nsIEffectiveTLDService.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsIDocShell.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ void
+DocGroup::GetKey(nsIPrincipal* aPrincipal, nsACString& aKey)
+{
+ aKey.Truncate();
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ // GetBaseDomain works fine if |uri| is null, but it outputs a warning
+ // which ends up cluttering the logs.
+ if (NS_SUCCEEDED(rv) && uri) {
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (tldService) {
+ rv = tldService->GetBaseDomain(uri, 0, aKey);
+ if (NS_FAILED(rv)) {
+ aKey.Truncate();
+ }
+ }
+ }
+}
+
+void
+DocGroup::RemoveDocument(nsIDocument* aDocument)
+{
+ MOZ_ASSERT(mDocuments.Contains(aDocument));
+ mDocuments.RemoveElement(aDocument);
+}
+
+DocGroup::DocGroup(TabGroup* aTabGroup, const nsACString& aKey)
+ : mKey(aKey), mTabGroup(aTabGroup)
+{
+ // This method does not add itself to mTabGroup->mDocGroups as the caller does it for us.
+}
+
+DocGroup::~DocGroup()
+{
+ MOZ_ASSERT(mDocuments.IsEmpty());
+ mTabGroup->mDocGroups.RemoveEntry(mKey);
+}
+
+NS_IMPL_ISUPPORTS(DocGroup, nsISupports)
+
+}
+}
diff --git a/dom/base/DocGroup.h b/dom/base/DocGroup.h
new file mode 100644
index 000000000..f4f7ac8ad
--- /dev/null
+++ b/dom/base/DocGroup.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsISupports.h"
+#include "nsISupportsImpl.h"
+#include "nsIPrincipal.h"
+#include "nsTHashtable.h"
+#include "nsString.h"
+
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+// 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 "unit of related browsing contexts"
+//
+// 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 TabGroup is a set of browsing contexts which are all "related". Within a
+// TabGroup, browsing contexts are broken into "similar-origin" DocGroups. In
+// more detail, a DocGroup is actually a collection of documents, and a
+// TabGroup is a collection of DocGroups. A TabGroup typically will contain
+// (through its DocGroups) the documents from one or more tabs related by
+// window.opener. A DocGroup is a member of exactly one TabGroup.
+
+class TabGroup;
+
+class DocGroup final : public nsISupports
+{
+public:
+ typedef nsTArray<nsIDocument*>::iterator Iterator;
+ friend class TabGroup;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static void GetKey(nsIPrincipal* aPrincipal, nsACString& aString);
+ bool MatchesKey(const nsACString& aKey)
+ {
+ return aKey == mKey;
+ }
+ TabGroup* GetTabGroup()
+ {
+ return mTabGroup;
+ }
+ void RemoveDocument(nsIDocument* aWindow);
+
+ // Iterators for iterating over every document within the DocGroup
+ Iterator begin()
+ {
+ return mDocuments.begin();
+ }
+ Iterator end()
+ {
+ return mDocuments.end();
+ }
+
+private:
+ DocGroup(TabGroup* aTabGroup, const nsACString& aKey);
+ ~DocGroup();
+
+ nsCString mKey;
+ RefPtr<TabGroup> mTabGroup;
+ nsTArray<nsIDocument*> mDocuments;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // defined(DocGroup_h)
diff --git a/dom/base/DocumentFragment.cpp b/dom/base/DocumentFragment.cpp
new file mode 100644
index 000000000..3eb2a0790
--- /dev/null
+++ b/dom/base/DocumentFragment.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/. */
+
+/*
+ * Implementation of DOM Core's nsIDOMDocumentFragment.
+ */
+
+#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 "nsIDocument.h"
+#include "mozilla/IntegerPrintfMacros.h"
+
+namespace mozilla {
+namespace dom {
+
+JSObject*
+DocumentFragment::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DocumentFragmentBinding::Wrap(aCx, this, aGivenProto);
+}
+
+bool
+DocumentFragment::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~(eCONTENT | eDOCUMENT_FRAGMENT));
+}
+
+NS_IMETHODIMP
+DocumentFragment::QuerySelector(const nsAString& aSelector,
+ nsIDOMElement **aReturn)
+{
+ return nsINode::QuerySelector(aSelector, aReturn);
+}
+
+NS_IMETHODIMP
+DocumentFragment::QuerySelectorAll(const nsAString& aSelector,
+ nsIDOMNodeList **aReturn)
+{
+ return nsINode::QuerySelectorAll(aSelector, aReturn);
+}
+
+#ifdef DEBUG
+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();
+}
+
+// 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(nsIDOMDocumentFragment)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMNode)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
+ 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 dom
+} // namespace mozilla
diff --git a/dom/base/DocumentFragment.h b/dom/base/DocumentFragment.h
new file mode 100644
index 000000000..68a7f0ff4
--- /dev/null
+++ b/dom/base/DocumentFragment.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMDocumentFragment.h"
+
+class nsIAtom;
+class nsAString;
+class nsIDocument;
+class nsIContent;
+
+namespace mozilla {
+namespace dom {
+
+class Element;
+
+class DocumentFragment : public FragmentOrElement,
+ public nsIDOMDocumentFragment
+{
+private:
+ void Init()
+ {
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::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
+
+ // interface nsIDOMNode
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+
+ // interface nsIDOMDocumentFragment
+ NS_DECL_NSIDOMDOCUMENTFRAGMENT
+
+ explicit DocumentFragment(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : FragmentOrElement(aNodeInfo), mHost(nullptr)
+ {
+ Init();
+ }
+
+ explicit DocumentFragment(nsNodeInfoManager* aNodeInfoManager)
+ : FragmentOrElement(aNodeInfoManager->GetNodeInfo(
+ nsGkAtoms::documentFragmentNodeName,
+ nullptr, kNameSpaceID_None,
+ nsIDOMNode::DOCUMENT_FRAGMENT_NODE)),
+ mHost(nullptr)
+ {
+ Init();
+ }
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsIContent
+ nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAString& aValue, bool aNotify)
+ {
+ return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
+ }
+ virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override
+ {
+ return NS_OK;
+ }
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override
+ {
+ return NS_OK;
+ }
+ virtual const nsAttrName* GetAttrNameAt(uint32_t aIndex) const override
+ {
+ return nullptr;
+ }
+ virtual BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const override
+ {
+ return BorrowedAttrInfo(nullptr, nullptr);
+ }
+ virtual uint32_t GetAttrCount() const override
+ {
+ return 0;
+ }
+
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override
+ {
+ NS_ASSERTION(false, "Trying to bind a fragment to a tree");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual void UnbindFromTree(bool aDeep, bool aNullParent) override
+ {
+ NS_ASSERTION(false, "Trying to unbind a fragment from a tree");
+ return;
+ }
+
+ virtual Element* GetNameSpaceElement() override
+ {
+ return nullptr;
+ }
+
+ nsIContent* GetHost() const
+ {
+ return mHost;
+ }
+
+ void SetHost(nsIContent* aHost)
+ {
+ mHost = aHost;
+ }
+
+ static already_AddRefed<DocumentFragment>
+ Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
+
+#ifdef DEBUG
+ 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()
+ {
+ }
+
+ nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+ nsIContent* mHost; // Weak
+};
+
+} // namespace dom
+} // namespace mozilla
+
+
+#endif // mozilla_dom_DocumentFragment_h__
diff --git a/dom/base/DocumentType.cpp b/dom/base/DocumentType.cpp
new file mode 100644
index 000000000..d6a5cce85
--- /dev/null
+++ b/dom/base/DocumentType.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/. */
+
+/*
+ * Implementation of DOM Core's nsIDOMDocumentType node.
+ */
+
+#include "mozilla/dom/DocumentType.h"
+#include "nsGkAtoms.h"
+#include "nsCOMPtr.h"
+#include "nsDOMString.h"
+#include "nsNodeInfoManager.h"
+#include "nsIXPConnect.h"
+#include "xpcpublic.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/DocumentTypeBinding.h"
+
+nsresult
+NS_NewDOMDocumentType(nsIDOMDocumentType** aDocType,
+ nsNodeInfoManager *aNodeInfoManager,
+ nsIAtom *aName,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ const nsAString& aInternalSubset)
+{
+ NS_ENSURE_ARG_POINTER(aDocType);
+ mozilla::ErrorResult rv;
+ *aDocType = NS_NewDOMDocumentType(aNodeInfoManager, aName, aPublicId,
+ aSystemId, aInternalSubset, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<mozilla::dom::DocumentType>
+NS_NewDOMDocumentType(nsNodeInfoManager* aNodeInfoManager,
+ nsIAtom *aName,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ const nsAString& aInternalSubset,
+ mozilla::ErrorResult& rv)
+{
+ if (!aName) {
+ rv.Throw(NS_ERROR_INVALID_POINTER);
+ return nullptr;
+ }
+
+ already_AddRefed<mozilla::dom::NodeInfo> ni =
+ aNodeInfoManager->GetNodeInfo(nsGkAtoms::documentTypeNodeName, nullptr,
+ kNameSpaceID_None,
+ nsIDOMNode::DOCUMENT_TYPE_NODE,
+ aName);
+
+ RefPtr<mozilla::dom::DocumentType> docType =
+ new mozilla::dom::DocumentType(ni, aPublicId, aSystemId, aInternalSubset);
+ return docType.forget();
+}
+
+namespace mozilla {
+namespace dom {
+
+JSObject*
+DocumentType::WrapNode(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DocumentTypeBinding::Wrap(cx, this, aGivenProto);
+}
+
+DocumentType::DocumentType(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ const nsAString& aInternalSubset) :
+ DocumentTypeForward(aNodeInfo),
+ mPublicId(aPublicId),
+ mSystemId(aSystemId),
+ mInternalSubset(aInternalSubset)
+{
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE,
+ "Bad NodeType in aNodeInfo");
+}
+
+DocumentType::~DocumentType()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(DocumentType, nsGenericDOMDataNode, nsIDOMNode,
+ nsIDOMDocumentType)
+
+bool
+DocumentType::IsNodeOfType(uint32_t aFlags) const
+{
+ // Don't claim to be eDATA_NODE since we're just inheriting
+ // nsGenericDOMDataNode for convinience. Doctypes aren't really
+ // data nodes (they have a null .nodeValue and don't implement
+ // nsIDOMCharacterData)
+ return !(aFlags & ~eCONTENT);
+}
+
+const nsTextFragment*
+DocumentType::GetText()
+{
+ return nullptr;
+}
+
+NS_IMETHODIMP
+DocumentType::GetName(nsAString& aName)
+{
+ aName = NodeName();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentType::GetPublicId(nsAString& aPublicId)
+{
+ aPublicId = mPublicId;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentType::GetSystemId(nsAString& aSystemId)
+{
+ aSystemId = mSystemId;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentType::GetInternalSubset(nsAString& aInternalSubset)
+{
+ aInternalSubset = mInternalSubset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentType::MozRemove()
+{
+ Remove();
+ return NS_OK;
+}
+
+nsGenericDOMDataNode*
+DocumentType::CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo, bool aCloneText) const
+{
+ already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ return new DocumentType(ni, mPublicId, mSystemId, mInternalSubset);
+}
+
+} // namespace dom
+} // namespace mozilla
+
diff --git a/dom/base/DocumentType.h b/dom/base/DocumentType.h
new file mode 100644
index 000000000..50493541f
--- /dev/null
+++ b/dom/base/DocumentType.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 DOM Core's nsIDOMDocumentType node.
+ */
+
+#ifndef DocumentType_h
+#define DocumentType_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMDocumentType.h"
+#include "nsIContent.h"
+#include "nsGenericDOMDataNode.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace 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 DocumentTypeForward : public nsGenericDOMDataNode,
+ public nsIDOMDocumentType
+{
+public:
+ explicit DocumentTypeForward(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericDOMDataNode(aNodeInfo)
+ {
+ }
+
+ // nsIDOMNode
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+};
+
+class DocumentType final : public DocumentTypeForward
+{
+public:
+ DocumentType(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ const nsAString& aInternalSubset);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMNode
+ // Forwarded by base class
+
+ // nsIDOMDocumentType
+ NS_DECL_NSIDOMDOCUMENTTYPE
+
+ // nsINode
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+ virtual void GetNodeValueInternal(nsAString& aNodeValue) override
+ {
+ SetDOMStringToNull(aNodeValue);
+ }
+ virtual void SetNodeValueInternal(const nsAString& aNodeValue,
+ mozilla::ErrorResult& aError) override
+ {
+ }
+
+ // nsIContent overrides
+ virtual const nsTextFragment* GetText() override;
+
+ virtual nsGenericDOMDataNode* CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo,
+ bool aCloneText) const override;
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+protected:
+ virtual ~DocumentType();
+
+ virtual JSObject* WrapNode(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsString mPublicId;
+ nsString mSystemId;
+ nsString mInternalSubset;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+already_AddRefed<mozilla::dom::DocumentType>
+NS_NewDOMDocumentType(nsNodeInfoManager* aNodeInfoManager,
+ nsIAtom *aName,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ const nsAString& aInternalSubset,
+ mozilla::ErrorResult& rv);
+
+nsresult
+NS_NewDOMDocumentType(nsIDOMDocumentType** aDocType,
+ nsNodeInfoManager* aNodeInfoManager,
+ nsIAtom *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 000000000..886acc670
--- /dev/null
+++ b/dom/base/Element.cpp
@@ -0,0 +1,3932 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMElement, implements nsIContent, provides
+ * utility methods for subclasses, and so forth.
+ */
+
+#include "mozilla/dom/ElementInlines.h"
+
+#include "AnimationCommon.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/Grid.h"
+#include "nsDOMAttributeMap.h"
+#include "nsIAtom.h"
+#include "nsIContentInlines.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIDocumentInlines.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMDocument.h"
+#include "nsIContentIterator.h"
+#include "nsFocusManager.h"
+#include "nsFrameManager.h"
+#include "nsILinkHandler.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIURL.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsStyleConsts.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsIDOMEvent.h"
+#include "nsDOMCID.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsDOMCSSAttrDeclaration.h"
+#include "nsNameSpaceManager.h"
+#include "nsContentList.h"
+#include "nsVariant.h"
+#include "nsDOMTokenList.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsError.h"
+#include "nsDOMString.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIDOMMutationEvent.h"
+#include "mozilla/dom/AnimatableBinding.h"
+#include "mozilla/dom/KeyframeAnimationOptionsBinding.h"
+#include "mozilla/AnimationComparator.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+#include "nsNodeUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsDocument.h"
+#include "nsAttrValueOrString.h"
+#include "nsAttrValueInlines.h"
+#include "nsCSSPseudoElements.h"
+#ifdef MOZ_XUL
+#include "nsXULElement.h"
+#endif /* MOZ_XUL */
+#include "nsSVGElement.h"
+#include "nsFrameSelection.h"
+#ifdef DEBUG
+#include "nsRange.h"
+#endif
+
+#include "nsBindingManager.h"
+#include "nsXBLBinding.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIBoxObject.h"
+#include "mozilla/dom/DOMRect.h"
+#include "nsSVGUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "ChildIterator.h"
+
+#include "nsIDOMEventListener.h"
+#include "nsIWebNavigation.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+
+#include "nsNodeInfoManager.h"
+#include "nsICategoryManager.h"
+#include "nsIDOMDocumentType.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIEditor.h"
+#include "nsIEditorIMESupport.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIControllers.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/css/StyleRule.h" /* For nsCSSSelectorList */
+#include "nsCSSRuleProcessor.h"
+#include "nsRuleProcessorData.h"
+#include "nsTextNode.h"
+
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#endif /* MOZ_XUL */
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsCCUncollectableMarker.h"
+
+#include "mozAutoDocUpdate.h"
+
+#include "nsCSSParser.h"
+#include "prprf.h"
+#include "nsDOMMutationObserver.h"
+#include "nsWrapperCacheInlines.h"
+#include "xpcpublic.h"
+#include "nsIScriptError.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/NodeListBinding.h"
+
+#include "nsStyledElement.h"
+#include "nsXBLService.h"
+#include "nsITextControlElement.h"
+#include "nsITextControlFrame.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/dom/CSSPseudoElement.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/dom/KeyframeEffectBinding.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/VRDisplay.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
+#include "nsComputedDOMStyle.h"
+#include "nsDOMStringMap.h"
+#include "DOMIntersectionObserver.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIAtom*
+nsIContent::DoGetID() const
+{
+ MOZ_ASSERT(HasID(), "Unexpected call");
+ MOZ_ASSERT(IsElement(), "Only elements can have IDs");
+
+ return AsElement()->GetParsedAttr(nsGkAtoms::id)->GetAtomValue();
+}
+
+const nsAttrValue*
+nsIContent::DoGetClasses() const
+{
+ MOZ_ASSERT(HasFlag(NODE_MAY_HAVE_CLASS), "Unexpected call");
+ MOZ_ASSERT(IsElement(), "Only elements can have classes");
+
+ if (IsSVGElement()) {
+ const nsAttrValue* animClass =
+ static_cast<const nsSVGElement*>(this)->GetAnimatedClassName();
+ if (animClass) {
+ return animClass;
+ }
+ }
+
+ return AsElement()->GetParsedAttr(nsGkAtoms::_class);
+}
+
+NS_IMETHODIMP
+Element::QueryInterface(REFNSIID aIID, void** aInstancePtr)
+{
+ NS_ASSERTION(aInstancePtr,
+ "QueryInterface requires a non-NULL destination!");
+ nsresult rv = FragmentOrElement::QueryInterface(aIID, aInstancePtr);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+
+ // Give the binding manager a chance to get an interface for this element.
+ return OwnerDoc()->BindingManager()->GetBindingImplementation(this, aIID,
+ aInstancePtr);
+}
+
+EventStates
+Element::IntrinsicState() const
+{
+ return IsEditable() ? NS_EVENT_STATE_MOZ_READWRITE :
+ NS_EVENT_STATE_MOZ_READONLY;
+}
+
+void
+Element::NotifyStateChange(EventStates aStates)
+{
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ nsAutoScriptBlocker scriptBlocker;
+ doc->ContentStateChanged(this, aStates);
+ }
+}
+
+void
+Element::UpdateLinkState(EventStates aState)
+{
+ MOZ_ASSERT(!aState.HasAtLeastOneOfStates(~(NS_EVENT_STATE_VISITED |
+ NS_EVENT_STATE_UNVISITED)),
+ "Unexpected link state bits");
+ mState =
+ (mState & ~(NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)) |
+ aState;
+}
+
+void
+Element::UpdateState(bool aNotify)
+{
+ EventStates oldState = mState;
+ mState = IntrinsicState() | (oldState & ESM_MANAGED_STATES);
+ if (aNotify) {
+ EventStates changedStates = oldState ^ mState;
+ if (!changedStates.IsEmpty()) {
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ nsAutoScriptBlocker scriptBlocker;
+ doc->ContentStateChanged(this, changedStates);
+ }
+ }
+ }
+}
+
+void
+nsIContent::UpdateEditableState(bool aNotify)
+{
+ // Guaranteed to be non-element content
+ NS_ASSERTION(!IsElement(), "What happened here?");
+ nsIContent *parent = GetParent();
+
+ // Skip over unknown native anonymous content to avoid setting a flag we
+ // can't clear later
+ bool isUnknownNativeAnon = false;
+ if (IsInNativeAnonymousSubtree()) {
+ isUnknownNativeAnon = true;
+ nsCOMPtr<nsIContent> root = this;
+ while (root && !root->IsRootOfNativeAnonymousSubtree()) {
+ root = root->GetParent();
+ }
+ // root should always be true here, but isn't -- bug 999416
+ if (root) {
+ nsIFrame* rootFrame = root->GetPrimaryFrame();
+ if (rootFrame) {
+ nsContainerFrame* parentFrame = rootFrame->GetParent();
+ nsITextControlFrame* textCtrl = do_QueryFrame(parentFrame);
+ isUnknownNativeAnon = !textCtrl;
+ }
+ }
+ }
+
+ SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE) &&
+ !isUnknownNativeAnon);
+}
+
+void
+Element::UpdateEditableState(bool aNotify)
+{
+ nsIContent *parent = GetParent();
+
+ SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
+ 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(NS_EVENT_STATE_MOZ_READONLY);
+ AddStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
+ } else {
+ RemoveStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
+ AddStatesSilently(NS_EVENT_STATE_MOZ_READONLY);
+ }
+ }
+}
+
+int32_t
+Element::TabIndex()
+{
+ const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(nsGkAtoms::tabindex);
+ if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
+ return attrVal->GetIntegerValue();
+ }
+
+ return TabIndexDefault();
+}
+
+void
+Element::Focus(mozilla::ErrorResult& aError)
+{
+ nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(this);
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && domElement) {
+ aError = fm->SetFocus(domElement, 0);
+ }
+}
+
+void
+Element::SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError)
+{
+ nsAutoString value;
+ value.AppendInt(aTabIndex);
+
+ SetAttr(nsGkAtoms::tabindex, value, aError);
+}
+
+void
+Element::Blur(mozilla::ErrorResult& aError)
+{
+ if (!ShouldBlur(this)) {
+ return;
+ }
+
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc) {
+ return;
+ }
+
+ nsPIDOMWindowOuter* win = doc->GetWindow();
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (win && fm) {
+ aError = fm->ClearFocus(win);
+ }
+}
+
+EventStates
+Element::StyleStateFromLocks() const
+{
+ EventStates locks = LockedStyleStates();
+ EventStates state = mState | locks;
+
+ if (locks.HasState(NS_EVENT_STATE_VISITED)) {
+ return state & ~NS_EVENT_STATE_UNVISITED;
+ }
+ if (locks.HasState(NS_EVENT_STATE_UNVISITED)) {
+ return state & ~NS_EVENT_STATE_VISITED;
+ }
+ return state;
+}
+
+EventStates
+Element::LockedStyleStates() const
+{
+ EventStates* locks =
+ static_cast<EventStates*>(GetProperty(nsGkAtoms::lockedStyleStates));
+ if (locks) {
+ return *locks;
+ }
+ return EventStates();
+}
+
+void
+Element::NotifyStyleStateChange(EventStates aStates)
+{
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ nsIPresShell *presShell = doc->GetShell();
+ if (presShell) {
+ nsAutoScriptBlocker scriptBlocker;
+ presShell->ContentStateChanged(doc, this, aStates);
+ }
+ }
+}
+
+void
+Element::LockStyleStates(EventStates aStates)
+{
+ EventStates* locks = new EventStates(LockedStyleStates());
+
+ *locks |= aStates;
+
+ if (aStates.HasState(NS_EVENT_STATE_VISITED)) {
+ *locks &= ~NS_EVENT_STATE_UNVISITED;
+ }
+ if (aStates.HasState(NS_EVENT_STATE_UNVISITED)) {
+ *locks &= ~NS_EVENT_STATE_VISITED;
+ }
+
+ SetProperty(nsGkAtoms::lockedStyleStates, locks,
+ nsINode::DeleteProperty<EventStates>);
+ SetHasLockedStyleStates();
+
+ NotifyStyleStateChange(aStates);
+}
+
+void
+Element::UnlockStyleStates(EventStates aStates)
+{
+ EventStates* locks = new EventStates(LockedStyleStates());
+
+ *locks &= ~aStates;
+
+ if (locks->IsEmpty()) {
+ DeleteProperty(nsGkAtoms::lockedStyleStates);
+ ClearHasLockedStyleStates();
+ delete locks;
+ }
+ else {
+ SetProperty(nsGkAtoms::lockedStyleStates, locks,
+ nsINode::DeleteProperty<EventStates>);
+ }
+
+ NotifyStyleStateChange(aStates);
+}
+
+void
+Element::ClearStyleStateLocks()
+{
+ EventStates locks = LockedStyleStates();
+
+ DeleteProperty(nsGkAtoms::lockedStyleStates);
+ ClearHasLockedStyleStates();
+
+ NotifyStyleStateChange(locks);
+}
+
+bool
+Element::GetBindingURL(nsIDocument *aDocument, css::URLValue **aResult)
+{
+ // If we have a frame the frame has already loaded the binding. And
+ // otherwise, don't do anything else here unless we're dealing with
+ // XUL or an HTML element that may have a plugin-related overlay
+ // (i.e. object, embed, or applet).
+ bool isXULorPluginElement = (IsXULElement() ||
+ IsHTMLElement(nsGkAtoms::object) ||
+ IsHTMLElement(nsGkAtoms::embed) ||
+ IsHTMLElement(nsGkAtoms::applet));
+ nsCOMPtr<nsIPresShell> shell = aDocument->GetShell();
+ if (!shell || GetPrimaryFrame() || !isXULorPluginElement) {
+ *aResult = nullptr;
+
+ return true;
+ }
+
+ // Get the computed -moz-binding directly from the style context
+ RefPtr<nsStyleContext> sc =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(this, nullptr, shell);
+ NS_ENSURE_TRUE(sc, false);
+
+ *aResult = sc->StyleDisplay()->mBinding;
+
+ return true;
+}
+
+JSObject*
+Element::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ JS::Rooted<JSObject*> givenProto(aCx, aGivenProto);
+ JS::Rooted<JSObject*> customProto(aCx);
+
+ if (!givenProto) {
+ // Custom element prototype swizzling.
+ CustomElementData* data = GetCustomElementData();
+ if (data) {
+ // If this is a registered custom element then fix the prototype.
+ nsContentUtils::GetCustomPrototype(OwnerDoc(), NodeInfo()->NamespaceID(),
+ data->mType, &customProto);
+ if (customProto &&
+ NodePrincipal()->SubsumesConsideringDomain(nsContentUtils::ObjectPrincipal(customProto))) {
+ // Just go ahead and create with the right proto up front. Set
+ // customProto to null to flag that we don't need to do any post-facto
+ // proto fixups here.
+ givenProto = customProto;
+ customProto = nullptr;
+ }
+ }
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, nsINode::WrapObject(aCx, givenProto));
+ if (!obj) {
+ return nullptr;
+ }
+
+ if (customProto) {
+ // We want to set the custom prototype in the compartment where it was
+ // registered. In the case that |obj| and |prototype| are in different
+ // compartments, this will set the prototype on the |obj|'s wrapper and
+ // thus only visible in the wrapper's compartment, since we know obj's
+ // principal does not subsume customProto's in this case.
+ JSAutoCompartment ac(aCx, customProto);
+ JS::Rooted<JSObject*> wrappedObj(aCx, obj);
+ if (!JS_WrapObject(aCx, &wrappedObj) ||
+ !JS_SetPrototype(aCx, wrappedObj, customProto)) {
+ return nullptr;
+ }
+ }
+
+ nsIDocument* doc;
+ if (HasFlag(NODE_FORCE_XBL_BINDINGS)) {
+ doc = OwnerDoc();
+ }
+ else {
+ doc = GetComposedDoc();
+ }
+
+ if (!doc) {
+ // There's no baseclass that cares about this call so we just
+ // return here.
+ return obj;
+ }
+
+ // We must ensure that the XBL Binding is installed before we hand
+ // back this object.
+
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) && GetXBLBinding()) {
+ // There's already a binding for this element so nothing left to
+ // be done here.
+
+ // In theory we could call ExecuteAttachedHandler here when it's safe to
+ // run script if we also removed the binding from the PAQ queue, but that
+ // seems like a scary change that would mosly just add more
+ // inconsistencies.
+ return obj;
+ }
+
+ // Make sure the style context goes away _before_ we load the binding
+ // since that can destroy the relevant presshell.
+ mozilla::css::URLValue *bindingURL;
+ bool ok = GetBindingURL(doc, &bindingURL);
+ if (!ok) {
+ dom::Throw(aCx, NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ if (!bindingURL) {
+ // No binding, nothing left to do here.
+ return obj;
+ }
+
+ nsCOMPtr<nsIURI> uri = bindingURL->GetURI();
+ nsCOMPtr<nsIPrincipal> principal = bindingURL->mOriginPrincipal.get();
+
+ // We have a binding that must be installed.
+ bool dummy;
+
+ nsXBLService* xblService = nsXBLService::GetInstance();
+ if (!xblService) {
+ dom::Throw(aCx, NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ {
+ // Make a scope so that ~nsRefPtr can GC before returning obj.
+ RefPtr<nsXBLBinding> binding;
+ xblService->LoadBindings(this, uri, principal, getter_AddRefs(binding), &dummy);
+
+ if (binding) {
+ if (nsContentUtils::IsSafeToRunScript()) {
+ binding->ExecuteAttachedHandler();
+ }
+ else {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(binding, &nsXBLBinding::ExecuteAttachedHandler));
+ }
+ }
+ }
+
+ return obj;
+}
+
+/* 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;
+}
+
+void
+Element::GetClassList(nsISupports** aClassList)
+{
+ NS_ADDREF(*aClassList = ClassList());
+}
+
+void
+Element::GetAttributeNames(nsTArray<nsString>& aResult)
+{
+ uint32_t count = mAttrsAndChildren.AttrCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ const nsAttrName* name = mAttrsAndChildren.AttrNameAt(i);
+ name->GetQualifiedName(*aResult.AppendElement());
+ }
+}
+
+already_AddRefed<nsIHTMLCollection>
+Element::GetElementsByTagName(const nsAString& aLocalName)
+{
+ return NS_GetContentList(this, kNameSpaceID_Unknown, aLocalName);
+}
+
+void
+Element::GetElementsByTagName(const nsAString& aLocalName,
+ nsIDOMHTMLCollection** aResult)
+{
+ *aResult = GetElementsByTagName(aLocalName).take();
+}
+
+nsIFrame*
+Element::GetStyledFrame()
+{
+ nsIFrame *frame = GetPrimaryFrame(Flush_Layout);
+ return frame ? nsLayoutUtils::GetStyleFrame(frame) : nullptr;
+}
+
+nsIScrollableFrame*
+Element::GetScrollFrame(nsIFrame **aStyledFrame, bool aFlushLayout)
+{
+ // it isn't clear what to return for SVG nodes, so just return nothing
+ if (IsSVGElement()) {
+ if (aStyledFrame) {
+ *aStyledFrame = nullptr;
+ }
+ return nullptr;
+ }
+
+ // Inline version of GetStyledFrame to use Flush_None if needed.
+ nsIFrame* frame = GetPrimaryFrame(aFlushLayout ? Flush_Layout : Flush_None);
+ if (frame) {
+ frame = nsLayoutUtils::GetStyleFrame(frame);
+ }
+
+ if (aStyledFrame) {
+ *aStyledFrame = frame;
+ }
+ if (!frame) {
+ return nullptr;
+ }
+
+ // menu frames implement GetScrollTargetFrame but we don't want
+ // to use it here. Similar for comboboxes.
+ nsIAtom* type = frame->GetType();
+ if (type != nsGkAtoms::menuFrame && type != nsGkAtoms::comboboxControlFrame) {
+ nsIScrollableFrame *scrollFrame = frame->GetScrollTargetFrame();
+ if (scrollFrame)
+ return scrollFrame;
+ }
+
+ nsIDocument* doc = OwnerDoc();
+ bool quirksMode = doc->GetCompatibilityMode() == eCompatibility_NavQuirks;
+ Element* elementWithRootScrollInfo =
+ quirksMode ? doc->GetBodyElement() : doc->GetRootElement();
+ if (this == elementWithRootScrollInfo) {
+ // In quirks mode, the scroll info for the body element should map to the
+ // root scrollable frame.
+ // In strict mode, the scroll info for the root element should map to the
+ // the root scrollable frame.
+ return frame->PresContext()->PresShell()->GetRootScrollFrameAsScrollable();
+ }
+
+ return nullptr;
+}
+
+void
+Element::ScrollIntoView()
+{
+ ScrollIntoView(ScrollIntoViewOptions());
+}
+
+void
+Element::ScrollIntoView(bool aTop)
+{
+ ScrollIntoViewOptions options;
+ if (!aTop) {
+ options.mBlock = ScrollLogicalPosition::End;
+ }
+ ScrollIntoView(options);
+}
+
+void
+Element::ScrollIntoView(const ScrollIntoViewOptions &aOptions)
+{
+ nsIDocument *document = GetComposedDoc();
+ if (!document) {
+ return;
+ }
+
+ // Get the presentation shell
+ nsCOMPtr<nsIPresShell> presShell = document->GetShell();
+ if (!presShell) {
+ return;
+ }
+
+ int16_t vpercent = (aOptions.mBlock == ScrollLogicalPosition::Start)
+ ? nsIPresShell::SCROLL_TOP
+ : nsIPresShell::SCROLL_BOTTOM;
+
+ uint32_t flags = nsIPresShell::SCROLL_OVERFLOW_HIDDEN;
+ if (aOptions.mBehavior == ScrollBehavior::Smooth) {
+ flags |= nsIPresShell::SCROLL_SMOOTH;
+ } else if (aOptions.mBehavior == ScrollBehavior::Auto) {
+ flags |= nsIPresShell::SCROLL_SMOOTH_AUTO;
+ }
+
+ presShell->ScrollContentIntoView(this,
+ nsIPresShell::ScrollAxis(
+ vpercent,
+ nsIPresShell::SCROLL_ALWAYS),
+ nsIPresShell::ScrollAxis(),
+ flags);
+}
+
+void
+Element::Scroll(const CSSIntPoint& aScroll, const ScrollOptions& aOptions)
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ nsIScrollableFrame::ScrollMode scrollMode = nsIScrollableFrame::INSTANT;
+ if (aOptions.mBehavior == ScrollBehavior::Smooth) {
+ scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+ } else if (aOptions.mBehavior == ScrollBehavior::Auto) {
+ ScrollbarStyles styles = sf->GetScrollbarStyles();
+ if (styles.mScrollBehavior == NS_STYLE_SCROLL_BEHAVIOR_SMOOTH) {
+ scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+ }
+ }
+
+ 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) {
+ CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels();
+ scrollPos += CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScrollDif),
+ mozilla::ToZeroIfNonfinite(aYScrollDif));
+ Scroll(scrollPos, ScrollOptions());
+ }
+}
+
+void
+Element::ScrollBy(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);
+ }
+}
+
+int32_t
+Element::ScrollTop()
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ? sf->GetScrollPositionCSSPixels().y : 0;
+}
+
+void
+Element::SetScrollTop(int32_t aScrollTop)
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ nsIScrollableFrame::ScrollMode scrollMode = nsIScrollableFrame::INSTANT;
+ if (sf->GetScrollbarStyles().mScrollBehavior == NS_STYLE_SCROLL_BEHAVIOR_SMOOTH) {
+ scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+ }
+ sf->ScrollToCSSPixels(CSSIntPoint(sf->GetScrollPositionCSSPixels().x,
+ aScrollTop),
+ scrollMode);
+ }
+}
+
+int32_t
+Element::ScrollLeft()
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ? sf->GetScrollPositionCSSPixels().x : 0;
+}
+
+void
+Element::SetScrollLeft(int32_t aScrollLeft)
+{
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (sf) {
+ nsIScrollableFrame::ScrollMode scrollMode = nsIScrollableFrame::INSTANT;
+ if (sf->GetScrollbarStyles().mScrollBehavior == NS_STYLE_SCROLL_BEHAVIOR_SMOOTH) {
+ scrollMode = nsIScrollableFrame::SMOOTH_MSD;
+ }
+
+ sf->ScrollToCSSPixels(CSSIntPoint(aScrollLeft,
+ sf->GetScrollPositionCSSPixels().y),
+ scrollMode);
+ }
+}
+
+
+bool
+Element::ScrollByNoFlush(int32_t aDx, int32_t aDy)
+{
+ nsIScrollableFrame* sf = GetScrollFrame(nullptr, false);
+ if (!sf) {
+ return false;
+ }
+
+ nsWeakFrame weakRef(sf->GetScrolledFrame());
+
+ CSSIntPoint before = sf->GetScrollPositionCSSPixels();
+ sf->ScrollToCSSPixelsApproximate(CSSIntPoint(before.x + aDx, before.y + aDy));
+
+ // The frame was destroyed, can't keep on scrolling.
+ if (!weakRef.IsAlive()) {
+ return false;
+ }
+
+ CSSIntPoint after = sf->GetScrollPositionCSSPixels();
+ return (before != after);
+}
+
+void
+Element::MozScrollSnap()
+{
+ nsIScrollableFrame* sf = GetScrollFrame(nullptr, false);
+ if (sf) {
+ sf->ScrollSnap();
+ }
+}
+
+static nsSize GetScrollRectSizeForOverflowVisibleFrame(nsIFrame* aFrame)
+{
+ if (!aFrame) {
+ return nsSize(0,0);
+ }
+
+ nsRect paddingRect = aFrame->GetPaddingRectRelativeToSelf();
+ nsOverflowAreas 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()
+{
+ if (IsSVGElement())
+ return 0;
+
+ nsIScrollableFrame* sf = GetScrollFrame();
+ nscoord height;
+ if (sf) {
+ height = sf->GetScrollRange().height + sf->GetScrollPortRect().height;
+ } else {
+ height = GetScrollRectSizeForOverflowVisibleFrame(GetStyledFrame()).height;
+ }
+
+ return nsPresContext::AppUnitsToIntCSSPixels(height);
+}
+
+int32_t
+Element::ScrollWidth()
+{
+ if (IsSVGElement())
+ return 0;
+
+ nsIScrollableFrame* sf = GetScrollFrame();
+ nscoord width;
+ if (sf) {
+ width = sf->GetScrollRange().width + sf->GetScrollPortRect().width;
+ } else {
+ width = GetScrollRectSizeForOverflowVisibleFrame(GetStyledFrame()).width;
+ }
+
+ return nsPresContext::AppUnitsToIntCSSPixels(width);
+}
+
+nsRect
+Element::GetClientAreaRect()
+{
+ nsIFrame* styledFrame;
+ nsIScrollableFrame* sf = GetScrollFrame(&styledFrame);
+
+ if (sf) {
+ return sf->GetScrollPortRect();
+ }
+
+ if (styledFrame &&
+ (styledFrame->StyleDisplay()->mDisplay != StyleDisplay::Inline ||
+ styledFrame->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 styledFrame->GetPaddingRect() - styledFrame->GetPositionIgnoringScrolling();
+ }
+
+ // SVG nodes reach here and just return 0
+ return nsRect(0, 0, 0, 0);
+}
+
+already_AddRefed<DOMRect>
+Element::GetBoundingClientRect()
+{
+ RefPtr<DOMRect> rect = new DOMRect(this);
+
+ nsIFrame* frame = GetPrimaryFrame(Flush_Layout);
+ if (!frame) {
+ // display:none, perhaps? Return the empty rect
+ return rect.forget();
+ }
+
+ nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(frame,
+ nsLayoutUtils::GetContainingBlockForClientRect(frame),
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ rect->SetLayoutRect(r);
+ return rect.forget();
+}
+
+already_AddRefed<DOMRectList>
+Element::GetClientRects()
+{
+ RefPtr<DOMRectList> rectList = new DOMRectList(this);
+
+ nsIFrame* frame = GetPrimaryFrame(Flush_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(nsIAtom* aId)
+{
+ NS_ASSERTION(HasID(), "Node doesn't have an ID?");
+ if (IsInShadowTree()) {
+ ShadowRoot* containingShadow = GetContainingShadow();
+ containingShadow->AddToIdTable(this, aId);
+ } else {
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc && (!IsInAnonymousSubtree() || doc->IsXULDocument())) {
+ doc->AddToIdTable(this, aId);
+ }
+ }
+}
+
+void
+Element::RemoveFromIdTable()
+{
+ if (!HasID()) {
+ return;
+ }
+
+ nsIAtom* 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 {
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc && (!IsInAnonymousSubtree() || doc->IsXULDocument())) {
+ doc->RemoveFromIdTable(this, id);
+ }
+ }
+}
+
+already_AddRefed<ShadowRoot>
+Element::CreateShadowRoot(ErrorResult& aError)
+{
+ nsAutoScriptBlocker scriptBlocker;
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfo->NodeInfoManager()->GetNodeInfo(
+ nsGkAtoms::documentFragmentNodeName, nullptr, kNameSpaceID_None,
+ nsIDOMNode::DOCUMENT_FRAGMENT_NODE);
+
+ RefPtr<nsXBLDocumentInfo> docInfo = new nsXBLDocumentInfo(OwnerDoc());
+
+ nsXBLPrototypeBinding* protoBinding = new nsXBLPrototypeBinding();
+ aError = protoBinding->Init(NS_LITERAL_CSTRING("shadowroot"),
+ docInfo, nullptr, true);
+ if (aError.Failed()) {
+ delete protoBinding;
+ return nullptr;
+ }
+
+ nsIDocument* doc = GetComposedDoc();
+ nsIContent* destroyedFramesFor = nullptr;
+ if (doc) {
+ nsIPresShell* shell = doc->GetShell();
+ if (shell) {
+ shell->DestroyFramesFor(this, &destroyedFramesFor);
+ MOZ_ASSERT(!shell->FrameManager()->GetDisplayContentsStyleFor(this));
+ }
+ }
+ MOZ_ASSERT(!GetPrimaryFrame());
+
+ // Unlike for XBL, false is the default for inheriting style.
+ protoBinding->SetInheritsStyle(false);
+
+ // Calling SetPrototypeBinding takes ownership of protoBinding.
+ docInfo->SetPrototypeBinding(NS_LITERAL_CSTRING("shadowroot"), protoBinding);
+
+ RefPtr<ShadowRoot> shadowRoot = new ShadowRoot(this, nodeInfo.forget(),
+ protoBinding);
+
+ shadowRoot->SetIsComposedDocParticipant(IsInComposedDoc());
+
+ // Replace the old ShadowRoot with the new one and let the old
+ // ShadowRoot know about the younger ShadowRoot because the old
+ // ShadowRoot is projected into the younger ShadowRoot's shadow
+ // insertion point (if it exists).
+ ShadowRoot* olderShadow = GetShadowRoot();
+ SetShadowRoot(shadowRoot);
+ if (olderShadow) {
+ olderShadow->SetYoungerShadow(shadowRoot);
+
+ // Unbind children of older shadow root because they
+ // are no longer in the composed tree.
+ for (nsIContent* child = olderShadow->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->UnbindFromTree(true, false);
+ }
+
+ olderShadow->SetIsComposedDocParticipant(false);
+ }
+
+ // xblBinding takes ownership of docInfo.
+ RefPtr<nsXBLBinding> xblBinding = new nsXBLBinding(shadowRoot, protoBinding);
+ shadowRoot->SetAssociatedBinding(xblBinding);
+ xblBinding->SetBoundElement(this);
+
+ SetXBLBinding(xblBinding);
+
+ // Recreate the frame for the bound content because binding a ShadowRoot
+ // changes how things are rendered.
+ if (doc) {
+ MOZ_ASSERT(doc == GetComposedDoc());
+ nsIPresShell* shell = doc->GetShell();
+ if (shell) {
+ shell->CreateFramesFor(destroyedFramesFor);
+ }
+ }
+
+ return shadowRoot.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DestinationInsertionPointList, mParent,
+ mDestinationPoints)
+
+NS_INTERFACE_TABLE_HEAD(DestinationInsertionPointList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(DestinationInsertionPointList, nsINodeList, nsIDOMNodeList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(DestinationInsertionPointList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DestinationInsertionPointList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DestinationInsertionPointList)
+
+DestinationInsertionPointList::DestinationInsertionPointList(Element* aElement)
+ : mParent(aElement)
+{
+ nsTArray<nsIContent*>* destPoints = aElement->GetExistingDestInsertionPoints();
+ if (destPoints) {
+ for (uint32_t i = 0; i < destPoints->Length(); i++) {
+ mDestinationPoints.AppendElement(destPoints->ElementAt(i));
+ }
+ }
+}
+
+DestinationInsertionPointList::~DestinationInsertionPointList()
+{
+}
+
+nsIContent*
+DestinationInsertionPointList::Item(uint32_t aIndex)
+{
+ return mDestinationPoints.SafeElementAt(aIndex);
+}
+
+NS_IMETHODIMP
+DestinationInsertionPointList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsIContent* item = Item(aIndex);
+ if (!item) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return CallQueryInterface(item, aReturn);
+}
+
+uint32_t
+DestinationInsertionPointList::Length() const
+{
+ return mDestinationPoints.Length();
+}
+
+NS_IMETHODIMP
+DestinationInsertionPointList::GetLength(uint32_t* aLength)
+{
+ *aLength = Length();
+ return NS_OK;
+}
+
+int32_t
+DestinationInsertionPointList::IndexOf(nsIContent* aContent)
+{
+ return mDestinationPoints.IndexOf(aContent);
+}
+
+JSObject*
+DestinationInsertionPointList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NodeListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<DestinationInsertionPointList>
+Element::GetDestinationInsertionPoints()
+{
+ RefPtr<DestinationInsertionPointList> list =
+ new DestinationInsertionPointList(this);
+ return list.forget();
+}
+
+void
+Element::GetAttribute(const nsAString& aName, DOMString& aReturn)
+{
+ const nsAttrValue* val =
+ mAttrsAndChildren.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();
+ }
+ }
+}
+
+void
+Element::SetAttribute(const nsAString& aName,
+ const nsAString& aValue,
+ ErrorResult& aError)
+{
+ aError = nsContentUtils::CheckQName(aName, false);
+ if (aError.Failed()) {
+ return;
+ }
+
+ nsAutoString nameToUse;
+ const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse);
+ if (!name) {
+ nsCOMPtr<nsIAtom> nameAtom = NS_Atomize(nameToUse);
+ if (!nameAtom) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ aError = SetAttr(kNameSpaceID_None, nameAtom, aValue, true);
+ return;
+ }
+
+ aError = SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(),
+ aValue, true);
+ return;
+}
+
+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)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eGetAttributeNode);
+ return Attributes()->GetNamedItem(aName);
+}
+
+already_AddRefed<Attr>
+Element::SetAttributeNode(Attr& aNewAttr, ErrorResult& aError)
+{
+ // XXXbz can we just remove this warning and the one in setAttributeNodeNS and
+ // alias setAttributeNode to setAttributeNodeNS?
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eSetAttributeNode);
+
+ 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;
+ }
+
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eRemoveAttributeNode);
+ 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 =
+ nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNamespaceURI,
+ nsContentUtils::IsChromeDoc(OwnerDoc()));
+
+ if (nsid == kNameSpaceID_Unknown) {
+ // Unknown namespace means no attribute.
+ SetDOMStringToNull(aReturn);
+ return;
+ }
+
+ nsCOMPtr<nsIAtom> name = NS_Atomize(aLocalName);
+ bool hasAttr = GetAttr(nsid, name, aReturn);
+ if (!hasAttr) {
+ SetDOMStringToNull(aReturn);
+ }
+}
+
+void
+Element::SetAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ const nsAString& aValue,
+ ErrorResult& aError)
+{
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ aError =
+ nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName,
+ mNodeInfo->NodeInfoManager(),
+ nsIDOMNode::ATTRIBUTE_NODE,
+ getter_AddRefs(ni));
+ if (aError.Failed()) {
+ return;
+ }
+
+ aError = SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(),
+ aValue, true);
+}
+
+void
+Element::RemoveAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ ErrorResult& aError)
+{
+ nsCOMPtr<nsIAtom> name = NS_Atomize(aLocalName);
+ int32_t nsid =
+ nsContentUtils::NameSpaceManager()->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)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eGetAttributeNodeNS);
+
+ 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)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eSetAttributeNodeNS);
+ 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 =
+ nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
+ nameSpaceId);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+
+ NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!");
+
+ return NS_GetContentList(this, nameSpaceId, aLocalName);
+}
+
+nsresult
+Element::GetElementsByTagNameNS(const nsAString& namespaceURI,
+ const nsAString& localName,
+ nsIDOMHTMLCollection** aResult)
+{
+ mozilla::ErrorResult rv;
+ nsCOMPtr<nsIHTMLCollection> list =
+ GetElementsByTagNameNS(namespaceURI, localName, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ list.forget(aResult);
+ return NS_OK;
+}
+
+bool
+Element::HasAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName) const
+{
+ int32_t nsid =
+ nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNamespaceURI,
+ nsContentUtils::IsChromeDoc(OwnerDoc()));
+
+ if (nsid == kNameSpaceID_Unknown) {
+ // Unknown namespace means no attr...
+ return false;
+ }
+
+ nsCOMPtr<nsIAtom> name = NS_Atomize(aLocalName);
+ return HasAttr(nsid, name);
+}
+
+already_AddRefed<nsIHTMLCollection>
+Element::GetElementsByClassName(const nsAString& aClassNames)
+{
+ return nsContentUtils::GetElementsByClassName(this, aClassNames);
+}
+
+nsresult
+Element::GetElementsByClassName(const nsAString& aClassNames,
+ nsIDOMHTMLCollection** aResult)
+{
+ *aResult =
+ nsContentUtils::GetElementsByClassName(this, aClassNames).take();
+ return NS_OK;
+}
+
+/**
+ * Returns the count of descendants (inclusive of aContent) in
+ * the uncomposed document that are explicitly set as editable.
+ */
+static uint32_t
+EditableInclusiveDescendantCount(nsIContent* aContent)
+{
+ auto htmlElem = nsGenericHTMLElement::FromContent(aContent);
+ if (htmlElem) {
+ return htmlElem->EditableInclusiveDescendantCount();
+ }
+
+ return aContent->EditableDescendantCount();
+}
+
+nsresult
+Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!");
+ NS_PRECONDITION((NODE_FROM(aParent, aDocument)->OwnerDoc() == OwnerDoc()),
+ "Must have the same owner document");
+ NS_PRECONDITION(!aParent || aDocument == aParent->GetUncomposedDoc(),
+ "aDocument must be current doc of aParent");
+ NS_PRECONDITION(!GetUncomposedDoc(), "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.
+ NS_PRECONDITION(!GetParent() || aParent == GetParent(),
+ "Already have a parent. Unbind first!");
+ NS_PRECONDITION(!GetBindingParent() ||
+ aBindingParent == GetBindingParent() ||
+ (!aBindingParent && aParent &&
+ aParent->GetBindingParent() == GetBindingParent()),
+ "Already have a binding parent. Unbind first!");
+ NS_PRECONDITION(!aParent || !aDocument ||
+ !aParent->HasFlag(NODE_FORCE_XBL_BINDINGS),
+ "Parent in document but flagged as forcing XBL");
+ NS_PRECONDITION(aBindingParent != this,
+ "Content must not be its own binding parent");
+ NS_PRECONDITION(!IsRootOfNativeAnonymousSubtree() ||
+ aBindingParent == aParent,
+ "Native anonymous content must have its parent as its "
+ "own binding parent");
+ NS_PRECONDITION(aBindingParent || !aParent ||
+ aBindingParent == aParent->GetBindingParent(),
+ "We should be passed the right binding parent");
+
+#ifdef MOZ_XUL
+ // First set the binding parent
+ nsXULElement* xulElem = nsXULElement::FromContent(this);
+ if (xulElem) {
+ xulElem->SetXULBindingParent(aBindingParent);
+ }
+ else
+#endif
+ {
+ if (aBindingParent) {
+ nsDOMSlots *slots = DOMSlots();
+
+ slots->mBindingParent = aBindingParent; // Weak, so no addref happens.
+ }
+ }
+ NS_ASSERTION(!aBindingParent || IsRootOfNativeAnonymousSubtree() ||
+ !HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE) ||
+ (aParent && aParent->IsInNativeAnonymousSubtree()),
+ "Trying to re-bind content from native anonymous subtree to "
+ "non-native anonymous parent!");
+ if (aParent) {
+ if (aParent->IsInNativeAnonymousSubtree()) {
+ SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+ }
+ if (aParent->HasFlag(NODE_CHROME_ONLY_ACCESS)) {
+ SetFlags(NODE_CHROME_ONLY_ACCESS);
+ }
+ if (aParent->IsInShadowTree()) {
+ ClearSubtreeRootPointer();
+ SetFlags(NODE_IS_IN_SHADOW_TREE);
+ }
+ ShadowRoot* parentContainingShadow = aParent->GetContainingShadow();
+ if (parentContainingShadow) {
+ DOMSlots()->mContainingShadow = parentContainingShadow;
+ }
+ }
+
+ bool hadForceXBL = HasFlag(NODE_FORCE_XBL_BINDINGS);
+
+ bool hadParent = !!GetParentNode();
+
+ // Now set the parent and set the "Force attach xbl" flag if needed.
+ if (aParent) {
+ if (!GetParent()) {
+ NS_ADDREF(aParent);
+ }
+ mParent = aParent;
+
+ if (aParent->HasFlag(NODE_FORCE_XBL_BINDINGS)) {
+ SetFlags(NODE_FORCE_XBL_BINDINGS);
+ }
+ }
+ else {
+ mParent = aDocument;
+ }
+ SetParentIsContent(aParent);
+
+ // XXXbz sXBL/XBL2 issue!
+
+ // Finally, set the document
+ if (aDocument) {
+ // Notify XBL- & nsIAnonymousContentCreator-generated
+ // anonymous content that the document is changing.
+ // XXXbz ordering issues here? Probably not, since ChangeDocumentFor is
+ // just pretty broken anyway.... Need to get it working.
+ // XXXbz XBL doesn't handle this (asserts), and we don't really want
+ // to be doing this during parsing anyway... sort this out.
+ // aDocument->BindingManager()->ChangeDocumentFor(this, nullptr,
+ // aDocument);
+
+ // We no longer need to track the subtree pointer (and in fact we'll assert
+ // if we do this any later).
+ ClearSubtreeRootPointer();
+
+ // Being added to a document.
+ SetIsInDocument();
+
+ // Unset this flag since we now really are in a document.
+ UnsetFlags(NODE_FORCE_XBL_BINDINGS |
+ // And clear the lazy frame construction bits.
+ NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
+ // And the restyle bits
+ UnsetRestyleFlagsIfGecko();
+ } else if (IsInShadowTree()) {
+ // We're not in a document, but we did get inserted into a shadow tree.
+ // Since we won't have any restyle data in the document's restyle trackers,
+ // don't let us get inserted with restyle bits set incorrectly.
+ //
+ // Also clear all the other flags that are cleared above when we do get
+ // inserted into a document.
+ UnsetFlags(NODE_FORCE_XBL_BINDINGS |
+ NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
+ UnsetRestyleFlagsIfGecko();
+ } else {
+ // If we're not in the doc and not in a shadow tree,
+ // update our subtree pointer.
+ SetSubtreeRootPointer(aParent->SubtreeRoot());
+ }
+
+ nsIDocument* composedDoc = GetComposedDoc();
+ if (composedDoc) {
+ // Attached callback must be enqueued whenever custom element is inserted into a
+ // document and this document has a browsing context.
+ if (GetCustomElementData() && composedDoc->GetDocShell()) {
+ // Enqueue an attached callback for the custom element.
+ nsContentUtils::EnqueueLifecycleCallback(
+ composedDoc, nsIDocument::eAttached, this);
+ }
+ }
+
+ // Propagate scoped style sheet tracking bit.
+ if (mParent->IsContent()) {
+ nsIContent* parent;
+ ShadowRoot* shadowRootParent = ShadowRoot::FromNode(mParent);
+ if (shadowRootParent) {
+ parent = shadowRootParent->GetHost();
+ } else {
+ parent = mParent->AsContent();
+ }
+
+ bool inStyleScope = parent->IsElementInStyleScope();
+
+ SetIsElementInStyleScope(inStyleScope);
+ SetIsElementInStyleScopeFlagOnShadowTree(inStyleScope);
+ }
+
+ // 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, aParent);
+ }
+
+ uint32_t editableDescendantCount = 0;
+
+ // If NODE_FORCE_XBL_BINDINGS was set we might have anonymous children
+ // that also need to be told that they are moving.
+ nsresult rv;
+ if (hadForceXBL) {
+ nsBindingManager* bmgr = OwnerDoc()->BindingManager();
+
+ nsXBLBinding* contBinding = bmgr->GetBindingWithContent(this);
+ // First check if we have a binding...
+ if (contBinding) {
+ nsCOMPtr<nsIContent> anonRoot = contBinding->GetAnonymousContent();
+ bool allowScripts = contBinding->AllowScripts();
+ for (nsCOMPtr<nsIContent> child = anonRoot->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ rv = child->BindToTree(aDocument, this, this, allowScripts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ editableDescendantCount += EditableInclusiveDescendantCount(child);
+ }
+ }
+ }
+
+ UpdateEditableState(false);
+
+ // Now recurse into our kids
+ for (nsIContent* child = GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ rv = child->BindToTree(aDocument, this, aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ editableDescendantCount += EditableInclusiveDescendantCount(child);
+ }
+
+ if (aDocument) {
+ // Update our editable descendant count because we don't keep track of it
+ // for content that is not in the uncomposed document.
+ MOZ_ASSERT(EditableDescendantCount() == 0);
+ ChangeEditableDescendantCount(editableDescendantCount);
+
+ if (!hadParent) {
+ uint32_t editableDescendantChange = EditableInclusiveDescendantCount(this);
+ if (editableDescendantChange != 0) {
+ // If we are binding a subtree root to the document, we need to update
+ // the editable descendant count of all the ancestors.
+ nsIContent* parent = GetParent();
+ while (parent) {
+ parent->ChangeEditableDescendantCount(editableDescendantChange);
+ parent = parent->GetParent();
+ }
+ }
+ }
+ }
+
+ nsNodeUtils::ParentChainChanged(this);
+ if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
+ nsNodeUtils::NativeAnonymousChildListChange(this, false);
+ }
+
+ if (HasID()) {
+ AddToIdTable(DoGetID());
+ }
+
+ if (MayHaveStyle() && !IsXULElement()) {
+ // XXXbz if we already have a style attr parsed, this won't do
+ // anything... need to fix that.
+ // If MayHaveStyle() is true, we must be an nsStyledElement
+ static_cast<nsStyledElement*>(this)->ReparseStyleAttribute(false);
+ }
+
+ if (aDocument) {
+ // If we're in a document now, let our mapped attrs know what their new
+ // sheet is. This is safe to run for non-mapped-attribute elements too;
+ // it'll just do a small bit of unnecessary work. But most elements in
+ // practice are mapped-attribute elements.
+ nsHTMLStyleSheet* sheet = aDocument->GetAttributeStyleSheet();
+ if (sheet) {
+ mAttrsAndChildren.SetMappedAttrStyleSheet(sheet);
+ }
+ }
+
+ // Call BindToTree on shadow root children.
+ ShadowRoot* shadowRoot = GetShadowRoot();
+ if (shadowRoot) {
+ shadowRoot->SetIsComposedDocParticipant(IsInComposedDoc());
+ for (nsIContent* child = shadowRoot->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ rv = child->BindToTree(nullptr, shadowRoot,
+ shadowRoot->GetBindingParent(),
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // It would be cleanest to mark nodes as dirty when (a) they're created and
+ // (b) they're unbound from a tree. However, we can't easily do (a) right now,
+ // because IsStyledByServo() is not always easy to check at node creation time,
+ // and the bits have different meaning in the non-IsStyledByServo case.
+ //
+ // So for now, we just mark nodes as dirty when they're inserted into a
+ // document or shadow tree.
+ if (IsStyledByServo() && IsInComposedDoc()) {
+ MOZ_ASSERT(!HasServoData());
+ SetIsDirtyForServo();
+ }
+
+ // 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.
+ NS_POSTCONDITION(aDocument == GetUncomposedDoc(), "Bound to wrong document");
+ NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
+ NS_POSTCONDITION(aBindingParent == GetBindingParent(),
+ "Bound to wrong binding parent");
+
+ return NS_OK;
+}
+
+RemoveFromBindingManagerRunnable::RemoveFromBindingManagerRunnable(nsBindingManager* aManager,
+ nsIContent* aContent,
+ nsIDocument* aDoc):
+ mManager(aManager), mContent(aContent), mDoc(aDoc)
+{}
+
+RemoveFromBindingManagerRunnable::~RemoveFromBindingManagerRunnable() {}
+
+NS_IMETHODIMP
+RemoveFromBindingManagerRunnable::Run()
+{
+ // It may be the case that the element was removed from the
+ // DOM, causing this runnable to be created, then inserted back
+ // into the document before the this runnable had a chance to
+ // tear down the binding. Only tear down the binding if the element
+ // is still no longer in the DOM. nsXBLService::LoadBinding tears
+ // down the old binding if the element is inserted back into the
+ // DOM and loads a different binding.
+ if (!mContent->IsInComposedDoc()) {
+ mManager->RemovedFromDocumentInternal(mContent, mDoc,
+ nsBindingManager::eRunDtor);
+ }
+
+ return NS_OK;
+}
+
+
+void
+Element::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ NS_PRECONDITION(aDeep || (!GetUncomposedDoc() && !GetBindingParent()),
+ "Shallow unbind won't clear document and binding parent on "
+ "kids!");
+
+ RemoveFromIdTable();
+
+ // Make sure to unbind this node before doing the kids
+ nsIDocument* document =
+ HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetComposedDoc();
+
+ if (HasPointerLock()) {
+ nsIDocument::UnlockPointer();
+ }
+ if (mState.HasState(NS_EVENT_STATE_FULL_SCREEN)) {
+ // The element being removed is an ancestor of the full-screen element,
+ // exit full-screen state.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"), OwnerDoc(),
+ nsContentUtils::eDOM_PROPERTIES,
+ "RemovedFullscreenElement");
+ // Fully exit full-screen.
+ nsIDocument::ExitFullscreenInDocTree(OwnerDoc());
+ }
+ if (aNullParent) {
+ if (GetParent() && GetParent()->IsInUncomposedDoc()) {
+ // Update the editable descendant count in the ancestors before we
+ // lose the reference to the parent.
+ int32_t editableDescendantChange = -1 * EditableInclusiveDescendantCount(this);
+ if (editableDescendantChange != 0) {
+ nsIContent* parent = GetParent();
+ while (parent) {
+ parent->ChangeEditableDescendantCount(editableDescendantChange);
+ parent = parent->GetParent();
+ }
+ }
+ }
+
+ if (this->IsRootOfNativeAnonymousSubtree()) {
+ nsNodeUtils::NativeAnonymousChildListChange(this, true);
+ }
+
+ if (GetParent()) {
+ RefPtr<nsINode> p;
+ p.swap(mParent);
+ } else {
+ mParent = nullptr;
+ }
+ SetParentIsContent(false);
+ }
+
+ // 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).
+ // FIXME (Bug 522599): Need a test for this.
+ if (MayHaveAnimations()) {
+ DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty);
+ DeleteProperty(nsGkAtoms::transitionsOfAfterProperty);
+ DeleteProperty(nsGkAtoms::transitionsProperty);
+ DeleteProperty(nsGkAtoms::animationsOfBeforeProperty);
+ DeleteProperty(nsGkAtoms::animationsOfAfterProperty);
+ DeleteProperty(nsGkAtoms::animationsProperty);
+ }
+
+ ClearInDocument();
+
+ // Computed styled data isn't useful for detached nodes, and we'll need to
+ // recomputed it anyway if we ever insert the nodes back into a document.
+ if (IsStyledByServo()) {
+ ClearServoData();
+ } else {
+#ifdef MOZ_STYLO
+ MOZ_ASSERT(!HasServoData());
+#endif
+ }
+
+ // Editable descendant count only counts descendants that
+ // are in the uncomposed document.
+ ResetEditableDescendantCount();
+
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ // Begin keeping track of our subtree root.
+ SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+ }
+
+ if (document) {
+ // Notify XBL- & nsIAnonymousContentCreator-generated
+ // anonymous content that the document is changing.
+ // Unlike XBL, bindings for web components shadow DOM
+ // do not get uninstalled.
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) && !GetShadowRoot()) {
+ nsContentUtils::AddScriptRunner(
+ new RemoveFromBindingManagerRunnable(document->BindingManager(), this,
+ document));
+ }
+
+ document->ClearBoxObjectFor(this);
+
+ // Detached must be enqueued whenever custom element is removed from
+ // the document and this document has a browsing context.
+ if (GetCustomElementData() && document->GetDocShell()) {
+ // Enqueue a detached callback for the custom element.
+ nsContentUtils::EnqueueLifecycleCallback(
+ document, nsIDocument::eDetached, this);
+ }
+ }
+
+ // Unset this since that's what the old code effectively did.
+ UnsetFlags(NODE_FORCE_XBL_BINDINGS);
+ bool clearBindingParent = true;
+
+#ifdef MOZ_XUL
+ nsXULElement* xulElem = nsXULElement::FromContent(this);
+ if (xulElem) {
+ xulElem->SetXULBindingParent(nullptr);
+ clearBindingParent = false;
+ }
+#endif
+
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ if (slots) {
+ if (clearBindingParent) {
+ slots->mBindingParent = nullptr;
+ }
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ slots->mContainingShadow = nullptr;
+ }
+ }
+
+ // 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);
+ }
+
+ if (aDeep) {
+ // Do the kids. Don't call GetChildCount() here since that'll force
+ // XUL to generate template children, which there is no need for since
+ // all we're going to do is unbind them anyway.
+ uint32_t i, n = mAttrsAndChildren.ChildCount();
+
+ for (i = 0; i < n; ++i) {
+ // Note that we pass false for aNullParent here, since we don't want
+ // the kids to forget us. We _do_ want them to forget their binding
+ // parent, though, since this only walks non-anonymous kids.
+ mAttrsAndChildren.ChildAt(i)->UnbindFromTree(true, false);
+ }
+ }
+
+ nsNodeUtils::ParentChainChanged(this);
+
+ // Unbind children of shadow root.
+ ShadowRoot* shadowRoot = GetShadowRoot();
+ if (shadowRoot) {
+ for (nsIContent* child = shadowRoot->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ child->UnbindFromTree(true, false);
+ }
+
+ shadowRoot->SetIsComposedDocParticipant(false);
+ }
+}
+
+nsICSSDeclaration*
+Element::GetSMILOverrideStyle()
+{
+ Element::nsDOMSlots *slots = DOMSlots();
+
+ if (!slots->mSMILOverrideStyle) {
+ slots->mSMILOverrideStyle = new nsDOMCSSAttributeDeclaration(this, true);
+ }
+
+ return slots->mSMILOverrideStyle;
+}
+
+DeclarationBlock*
+Element::GetSMILOverrideStyleDeclaration()
+{
+ Element::nsDOMSlots *slots = GetExistingDOMSlots();
+ return slots ? slots->mSMILOverrideStyleDeclaration.get() : nullptr;
+}
+
+nsresult
+Element::SetSMILOverrideStyleDeclaration(DeclarationBlock* aDeclaration,
+ bool aNotify)
+{
+ Element::nsDOMSlots *slots = DOMSlots();
+
+ slots->mSMILOverrideStyleDeclaration = aDeclaration;
+
+ if (aNotify) {
+ nsIDocument* doc = GetComposedDoc();
+ // 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 (doc) {
+ nsCOMPtr<nsIPresShell> shell = doc->GetShell();
+ if (shell) {
+ // Pass both eRestyle_StyleAttribute and
+ // eRestyle_StyleAttribute_Animations since we don't know if
+ // this style represents only the ticking of an existing
+ // animation or whether it's a new or changed animation.
+ shell->RestyleForAnimation(this, eRestyle_StyleAttribute |
+ eRestyle_StyleAttribute_Animations);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+Element::IsLabelable() const
+{
+ return false;
+}
+
+bool
+Element::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return false;
+}
+
+DeclarationBlock*
+Element::GetInlineStyleDeclaration()
+{
+ return nullptr;
+}
+
+nsresult
+Element::SetInlineStyleDeclaration(DeclarationBlock* aDeclaration,
+ const nsAString* aSerialized,
+ bool aNotify)
+{
+ NS_NOTYETIMPLEMENTED("Element::SetInlineStyleDeclaration");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(bool)
+Element::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ return false;
+}
+
+nsChangeHint
+Element::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ return nsChangeHint(0);
+}
+
+bool
+Element::FindAttributeDependence(const nsIAtom* 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,
+ nsIDOMNode::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
+ nsIDocument* document = aContent->GetComposedDoc();
+ if (!document)
+ return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
+ if (!window)
+ return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedFrame;
+ nsIContent* contentToBlur =
+ nsFocusManager::GetFocusedDescendant(window, false, getter_AddRefs(focusedFrame));
+ if (contentToBlur == aContent)
+ return true;
+
+ // if focus on this element would get redirected, then check the redirected
+ // content as well when blurring.
+ return (contentToBlur && nsFocusManager::GetRedirectedFocus(aContent) == contentToBlur);
+}
+
+bool
+Element::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~eCONTENT);
+}
+
+/* static */
+nsresult
+Element::DispatchEvent(nsPresContext* aPresContext,
+ WidgetEvent* aEvent,
+ nsIContent* aTarget,
+ bool aFullDispatch,
+ nsEventStatus* aStatus)
+{
+ NS_PRECONDITION(aTarget, "Must have target");
+ NS_PRECONDITION(aEvent, "Must have source event");
+ NS_PRECONDITION(aStatus, "Null out param?");
+
+ if (!aPresContext) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell();
+ if (!shell) {
+ return NS_OK;
+ }
+
+ if (aFullDispatch) {
+ return shell->HandleEventWithTarget(aEvent, nullptr, aTarget, aStatus);
+ }
+
+ return shell->HandleDOMEventWithTarget(aTarget, aEvent, aStatus);
+}
+
+/* static */
+nsresult
+Element::DispatchClickEvent(nsPresContext* aPresContext,
+ WidgetInputEvent* aSourceEvent,
+ nsIContent* aTarget,
+ bool aFullDispatch,
+ const EventFlags* aExtraEventFlags,
+ nsEventStatus* aStatus)
+{
+ NS_PRECONDITION(aTarget, "Must have target");
+ NS_PRECONDITION(aSourceEvent, "Must have source event");
+ NS_PRECONDITION(aStatus, "Null out param?");
+
+ WidgetMouseEvent event(aSourceEvent->IsTrusted(), eMouseClick,
+ aSourceEvent->mWidget, WidgetMouseEvent::eReal);
+ event.mRefPoint = aSourceEvent->mRefPoint;
+ uint32_t clickCount = 1;
+ float pressure = 0;
+ uint16_t inputSource = 0;
+ WidgetMouseEvent* sourceMouseEvent = aSourceEvent->AsMouseEvent();
+ if (sourceMouseEvent) {
+ clickCount = sourceMouseEvent->mClickCount;
+ pressure = sourceMouseEvent->pressure;
+ inputSource = sourceMouseEvent->inputSource;
+ } else if (aSourceEvent->mClass == eKeyboardEventClass) {
+ inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
+ }
+ event.pressure = pressure;
+ event.mClickCount = clickCount;
+ event.inputSource = 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);
+}
+
+nsIFrame*
+Element::GetPrimaryFrame(mozFlushType aType)
+{
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ // Cause a flush, so we get up-to-date frame
+ // information
+ if (aType != Flush_None) {
+ doc->FlushPendingNotifications(aType);
+ }
+
+ return GetPrimaryFrame();
+}
+
+//----------------------------------------------------------------------
+nsresult
+Element::LeaveLink(nsPresContext* aPresContext)
+{
+ nsILinkHandler *handler = aPresContext->GetLinkHandler();
+ if (!handler) {
+ return NS_OK;
+ }
+
+ return handler->OnLeaveLink();
+}
+
+nsresult
+Element::SetEventHandler(nsIAtom* aEventName,
+ const nsAString& aValue,
+ bool aDefer)
+{
+ nsIDocument *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 NS_OK;
+ }
+
+ NS_PRECONDITION(aEventName, "Must have event name!");
+ bool defer = true;
+ EventListenerManager* manager =
+ GetEventListenerManagerForAttr(aEventName, &defer);
+ if (!manager) {
+ return NS_OK;
+ }
+
+ defer = defer && aDefer; // only defer if everyone agrees...
+ manager->SetEventHandler(aEventName, aValue,
+ defer, !nsContentUtils::IsChromeDoc(ownerDoc),
+ this);
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+
+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 = mAttrsAndChildren.GetExistingAttrNameFromQName(outStr);
+ if (val) {
+ outStr.Truncate();
+ }
+ } else {
+ val = mAttrsAndChildren.GetExistingAttrNameFromQName(aStr);
+ if (!val && aNameToUse) {
+ *aNameToUse = aStr;
+ }
+ }
+
+ return val;
+}
+
+bool
+Element::MaybeCheckSameAttrVal(int32_t aNamespaceID,
+ nsIAtom* aName,
+ nsIAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ bool aNotify,
+ nsAttrValue& aOldValue,
+ uint8_t* aModType,
+ bool* aHasListeners)
+{
+ bool modification = false;
+ *aHasListeners = aNotify &&
+ nsContentUtils::HasMutationListeners(this,
+ NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
+ this);
+
+ // 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);
+ }
+ bool valueMatches = aValue.EqualsAsStrings(*info.mValue);
+ if (valueMatches && aPrefix == info.mName->GetPrefix()) {
+ return true;
+ }
+ modification = true;
+ }
+ }
+ *aModType = modification ?
+ static_cast<uint8_t>(nsIDOMMutationEvent::MODIFICATION) :
+ static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION);
+ return false;
+}
+
+bool
+Element::OnlyNotifySameValueSet(int32_t aNamespaceID, nsIAtom* aName,
+ nsIAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ bool aNotify, nsAttrValue& aOldValue,
+ uint8_t* aModType, bool* aHasListeners)
+{
+ if (!MaybeCheckSameAttrVal(aNamespaceID, aName, aPrefix, aValue, aNotify,
+ aOldValue, aModType, aHasListeners)) {
+ return false;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ nsNodeUtils::AttributeSetToCurrentValue(this, aNamespaceID, aName);
+ return true;
+}
+
+nsresult
+Element::SetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ // Keep this in sync with SetParsedAttr below
+
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+ "Don't call SetAttr with unknown namespace");
+
+ if (!mAttrsAndChildren.CanFitMoreAttrs()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint8_t modType;
+ bool hasListeners;
+ nsAttrValueOrString value(aValue);
+ nsAttrValue oldValue;
+
+ if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
+ oldValue, &modType, &hasListeners)) {
+ return NS_OK;
+ }
+
+ nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAttrValue* preparsedAttrValue = value.GetStoredAttrValue();
+
+ if (aNotify) {
+ nsNodeUtils::AttributeWillChange(this, aNamespaceID, aName, modType,
+ preparsedAttrValue);
+ }
+
+ // Hold a script blocker while calling ParseAttribute since that can call
+ // out to id-observers
+ nsAutoScriptBlocker scriptBlocker;
+
+ nsAttrValue attrValue;
+ if (preparsedAttrValue) {
+ attrValue.SwapValueWith(*preparsedAttrValue);
+ }
+ // Even the value was pre-parsed in BeforeSetAttr, we still need to call
+ // ParseAttribute because it can have side effects.
+ if (!ParseAttribute(aNamespaceID, aName, aValue, attrValue)) {
+ attrValue.SetTo(aValue);
+ }
+
+ return SetAttrAndNotify(aNamespaceID, aName, aPrefix, oldValue,
+ attrValue, modType, hasListeners, aNotify,
+ kCallAfterSetAttr);
+}
+
+nsresult
+Element::SetParsedAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, nsAttrValue& aParsedValue,
+ bool aNotify)
+{
+ // Keep this in sync with SetAttr above
+
+ NS_ENSURE_ARG_POINTER(aName);
+ NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+ "Don't call SetAttr with unknown namespace");
+
+ if (!mAttrsAndChildren.CanFitMoreAttrs()) {
+ return NS_ERROR_FAILURE;
+ }
+
+
+ uint8_t modType;
+ bool hasListeners;
+ nsAttrValueOrString value(aParsedValue);
+ nsAttrValue oldValue;
+
+ if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
+ oldValue, &modType, &hasListeners)) {
+ return NS_OK;
+ }
+
+ nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNotify) {
+ nsNodeUtils::AttributeWillChange(this, aNamespaceID, aName, modType,
+ &aParsedValue);
+ }
+
+ return SetAttrAndNotify(aNamespaceID, aName, aPrefix, oldValue,
+ aParsedValue, modType, hasListeners, aNotify,
+ kCallAfterSetAttr);
+}
+
+nsresult
+Element::SetAttrAndNotify(int32_t aNamespaceID,
+ nsIAtom* aName,
+ nsIAtom* aPrefix,
+ const nsAttrValue& aOldValue,
+ nsAttrValue& aParsedValue,
+ uint8_t aModType,
+ bool aFireMutation,
+ bool aNotify,
+ bool aCallAfterSetAttr)
+{
+ nsresult rv;
+
+ nsIDocument* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
+
+ nsMutationGuard::DidMutate();
+
+ // Copy aParsedValue for later use since it will be lost when we call
+ // SetAndSwapMappedAttr below
+ nsAttrValue valueForAfterSetAttr;
+ if (aCallAfterSetAttr) {
+ valueForAfterSetAttr.SetTo(aParsedValue);
+ }
+
+ bool hadValidDir = false;
+ bool hadDirAuto = false;
+
+ 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) ||
+ !SetMappedAttribute(document, aName, aParsedValue, &rv)) {
+ rv = mAttrsAndChildren.SetAndSwapAttr(aName, aParsedValue);
+ }
+ }
+ else {
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ ni = mNodeInfo->NodeInfoManager()->GetNodeInfo(aName, aPrefix,
+ aNamespaceID,
+ nsIDOMNode::ATTRIBUTE_NODE);
+
+ rv = mAttrsAndChildren.SetAndSwapAttr(ni, aParsedValue);
+ }
+
+ // If the old value owns its own data, we know it is OK to keep using it.
+ const nsAttrValue* oldValue =
+ aParsedValue.StoresOwnData() ? &aParsedValue : &aOldValue;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (document || HasFlag(NODE_FORCE_XBL_BINDINGS)) {
+ RefPtr<nsXBLBinding> binding = GetXBLBinding();
+ if (binding) {
+ binding->AttributeChanged(aName, aNamespaceID, false, aNotify);
+ }
+ }
+
+ UpdateState(aNotify);
+
+ nsIDocument* ownerDoc = OwnerDoc();
+ if (ownerDoc && GetCustomElementData()) {
+ nsCOMPtr<nsIAtom> oldValueAtom = oldValue->GetAsAtom();
+ nsCOMPtr<nsIAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom();
+ LifecycleCallbackArgs args = {
+ nsDependentAtomString(aName),
+ aModType == nsIDOMMutationEvent::ADDITION ?
+ NullString() : nsDependentAtomString(oldValueAtom),
+ nsDependentAtomString(newValueAtom)
+ };
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ownerDoc, nsIDocument::eAttributeChanged, this, &args);
+ }
+
+ if (aCallAfterSetAttr) {
+ rv = AfterSetAttr(aNamespaceID, aName, &valueForAfterSetAttr, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
+ OnSetDirAttr(this, &valueForAfterSetAttr,
+ hadValidDir, hadDirAuto, 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.
+ nsNodeUtils::AttributeChanged(this, aNamespaceID, aName, aModType,
+ oldValue == &aParsedValue ? &aParsedValue : nullptr);
+ }
+
+ if (aFireMutation) {
+ InternalMutationEvent mutation(true, eLegacyAttrModified);
+
+ nsAutoString ns;
+ nsContentUtils::NameSpaceManager()->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->IsEmptyString()) {
+ mutation.mPrevAttrValue = oldValue->GetAsAtom();
+ }
+ mutation.mAttrChange = aModType;
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Element::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue, bool aNotify)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aName == nsGkAtoms::_class) {
+ // aValue->GetAttrValue will only be non-null here when this is called
+ // via Element::SetParsedAttr. This shouldn't happen for "class", but
+ // this will handle it.
+ if (aValue && !aValue->GetAttrValue()) {
+ nsAttrValue attr;
+ attr.ParseAtomArray(aValue->String());
+ aValue->TakeParsedValue(attr);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+bool
+Element::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::_class) {
+ SetFlags(NODE_MAY_HAVE_CLASS);
+ // Result should have been preparsed above.
+ 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.
+ RemoveFromIdTable();
+ if (aValue.IsEmpty()) {
+ ClearHasID();
+ return false;
+ }
+ aResult.ParseAtom(aValue);
+ SetHasID();
+ AddToIdTable(aResult.GetAtomValue());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+Element::SetMappedAttribute(nsIDocument* aDocument,
+ nsIAtom* aName,
+ nsAttrValue& aValue,
+ nsresult* aRetval)
+{
+ *aRetval = NS_OK;
+ return false;
+}
+
+EventListenerManager*
+Element::GetEventListenerManagerForAttr(nsIAtom* aAttrName,
+ bool* aDefer)
+{
+ *aDefer = true;
+ return GetOrCreateListenerManager();
+}
+
+BorrowedAttrInfo
+Element::GetAttrInfo(int32_t aNamespaceID, nsIAtom* aName) const
+{
+ NS_ASSERTION(nullptr != aName, "must have attribute name");
+ NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
+ "must have a real namespace ID!");
+
+ int32_t index = mAttrsAndChildren.IndexOfAttr(aName, aNamespaceID);
+ if (index < 0) {
+ return BorrowedAttrInfo(nullptr, nullptr);
+ }
+
+ return mAttrsAndChildren.AttrInfoAt(index);
+}
+
+BorrowedAttrInfo
+Element::GetAttrInfoAt(uint32_t aIndex) const
+{
+ if (aIndex >= mAttrsAndChildren.AttrCount()) {
+ return BorrowedAttrInfo(nullptr, nullptr);
+ }
+
+ return mAttrsAndChildren.AttrInfoAt(aIndex);
+}
+
+bool
+Element::GetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAString& aResult) const
+{
+ DOMString str;
+ bool haveAttr = GetAttr(aNameSpaceID, aName, str);
+ str.ToString(aResult);
+ return haveAttr;
+}
+
+int32_t
+Element::FindAttrValueIn(int32_t aNameSpaceID,
+ nsIAtom* 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 = mAttrsAndChildren.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;
+}
+
+nsresult
+Element::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ bool aNotify)
+{
+ NS_ASSERTION(nullptr != aName, "must have attribute name");
+
+ int32_t index = mAttrsAndChildren.IndexOfAttr(aName, aNameSpaceID);
+ if (index < 0) {
+ return NS_OK;
+ }
+
+ nsresult rv = BeforeSetAttr(aNameSpaceID, aName, nullptr, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIDocument *document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
+
+ if (aNotify) {
+ nsNodeUtils::AttributeWillChange(this, aNameSpaceID, aName,
+ nsIDOMMutationEvent::REMOVAL,
+ nullptr);
+ }
+
+ bool hasMutationListeners = aNotify &&
+ nsContentUtils::HasMutationListeners(this,
+ NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
+ this);
+
+ // Grab the attr node if needed before we remove it from the attr map
+ RefPtr<Attr> attrNode;
+ if (hasMutationListeners) {
+ nsAutoString ns;
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, ns);
+ attrNode = GetAttributeNodeNSInternal(ns, nsDependentAtomString(aName));
+ }
+
+ // Clear binding to nsIDOMMozNamedAttrMap
+ 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();
+
+ if (aName == nsGkAtoms::id && aNameSpaceID == kNameSpaceID_None) {
+ // Have to do this before clearing flag. See RemoveFromIdTable
+ RemoveFromIdTable();
+ ClearHasID();
+ }
+
+ 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;
+ rv = mAttrsAndChildren.RemoveAttrAt(index, oldValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (document || HasFlag(NODE_FORCE_XBL_BINDINGS)) {
+ RefPtr<nsXBLBinding> binding = GetXBLBinding();
+ if (binding) {
+ binding->AttributeChanged(aName, aNameSpaceID, true, aNotify);
+ }
+ }
+
+ UpdateState(aNotify);
+
+ nsIDocument* ownerDoc = OwnerDoc();
+ if (ownerDoc && GetCustomElementData()) {
+ nsCOMPtr<nsIAtom> oldValueAtom = oldValue.GetAsAtom();
+ LifecycleCallbackArgs args = {
+ nsDependentAtomString(aName),
+ nsDependentAtomString(oldValueAtom),
+ NullString()
+ };
+
+ nsContentUtils::EnqueueLifecycleCallback(
+ ownerDoc, nsIDocument::eAttributeChanged, this, &args);
+ }
+
+ if (aNotify) {
+ // We can always pass oldValue here since there is no new value which could
+ // have corrupted it.
+ nsNodeUtils::AttributeChanged(this, aNameSpaceID, aName,
+ nsIDOMMutationEvent::REMOVAL, &oldValue);
+ }
+
+ rv = AfterSetAttr(aNameSpaceID, aName, nullptr, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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 = nsIDOMMutationEvent::REMOVAL;
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe();
+ }
+
+ return NS_OK;
+}
+
+const nsAttrName*
+Element::GetAttrNameAt(uint32_t aIndex) const
+{
+ return mAttrsAndChildren.GetSafeAttrNameAt(aIndex);
+}
+
+uint32_t
+Element::GetAttrCount() const
+{
+ return mAttrsAndChildren.AttrCount();
+}
+
+void
+Element::DescribeAttribute(uint32_t index, nsAString& aOutDescription) const
+{
+ // name
+ mAttrsAndChildren.AttrNameAt(index)->GetQualifiedName(aOutDescription);
+
+ // value
+ aOutDescription.AppendLiteral("=\"");
+ nsAutoString value;
+ mAttrsAndChildren.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 DEBUG
+void
+Element::ListAttributes(FILE* out) const
+{
+ uint32_t index, count = mAttrsAndChildren.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 (IsCommonAncestorForRangeInSelection()) {
+ nsRange::RangeHashTable* ranges =
+ static_cast<nsRange::RangeHashTable*>(GetProperty(nsGkAtoms::range));
+ fprintf(out, " ranges:%d", ranges ? ranges->Count() : 0);
+ }
+ 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);
+
+ Element* nonConstThis = const_cast<Element*>(this);
+
+ // XXX sXBL/XBL2 issue! Owner or current document?
+ nsIDocument *document = OwnerDoc();
+
+ // Note: not listing nsIAnonymousContentCreator-created content...
+
+ nsBindingManager* bindingManager = document->BindingManager();
+ nsCOMPtr<nsIDOMNodeList> anonymousChildren;
+ bindingManager->GetAnonymousNodesFor(nonConstThis,
+ getter_AddRefs(anonymousChildren));
+
+ if (anonymousChildren) {
+ uint32_t length = 0;
+ anonymousChildren->GetLength(&length);
+
+ for (indent = aIndent; --indent >= 0; ) fputs(" ", out);
+ fputs("anonymous-children<\n", out);
+
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIDOMNode> node;
+ anonymousChildren->Item(i, getter_AddRefs(node));
+ nsCOMPtr<nsIContent> child = do_QueryInterface(node);
+ child->List(out, aIndent + 1);
+ }
+
+ for (indent = aIndent; --indent >= 0; ) fputs(" ", out);
+ fputs(">\n", out);
+
+ bool outHeader = false;
+ ExplicitChildIterator iter(nonConstThis);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (!outHeader) {
+ outHeader = true;
+
+ for (indent = aIndent; --indent >= 0; ) fputs(" ", out);
+ fputs("content-list<\n", out);
+ }
+
+ child->List(out, aIndent + 1);
+ }
+
+ if (outHeader) {
+ 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) const
+{
+ aOutDescription.Append(mNodeInfo->QualifiedName());
+ aOutDescription.AppendPrintf("@%p", (void *)this);
+
+ uint32_t index, count = mAttrsAndChildren.AttrCount();
+ for (index = 0; index < count; index++) {
+ aOutDescription.Append(' ');
+ nsAutoString attributeDescription;
+ DescribeAttribute(index, attributeDescription);
+ aOutDescription.Append(attributeDescription);
+ }
+}
+
+bool
+Element::CheckHandleEventForLinksPrecondition(EventChainVisitor& aVisitor,
+ nsIURI** aURI) const
+{
+ if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault ||
+ (!aVisitor.mEvent->IsTrusted() &&
+ (aVisitor.mEvent->mMessage != eMouseClick) &&
+ (aVisitor.mEvent->mMessage != eKeyPress) &&
+ (aVisitor.mEvent->mMessage != eLegacyDOMActivate)) ||
+ !aVisitor.mPresContext ||
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented) {
+ return false;
+ }
+
+ // Make sure we actually are a link
+ return IsLink(aURI);
+}
+
+nsresult
+Element::PreHandleEventForLinks(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 NS_OK;
+ }
+
+ // Make sure we meet the preconditions before continuing
+ nsCOMPtr<nsIURI> absURI;
+ if (!CheckHandleEventForLinksPrecondition(aVisitor, getter_AddRefs(absURI))) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ // We do the status bar updates in PreHandleEvent 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;
+ MOZ_FALLTHROUGH;
+ case eFocus: {
+ InternalFocusEvent* focusEvent = aVisitor.mEvent->AsFocusEvent();
+ if (!focusEvent || !focusEvent->mIsRefocus) {
+ nsAutoString target;
+ GetLinkTarget(target);
+ nsContentUtils::TriggerLink(this, aVisitor.mPresContext, absURI, target,
+ false, true, true);
+ // Make sure any ancestor links don't also TriggerLink
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+ break;
+ }
+ case eMouseOut:
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ MOZ_FALLTHROUGH;
+ case eBlur:
+ 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
+ NS_NOTREACHED("switch statements not in sync");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return rv;
+}
+
+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 eLegacyDOMActivate:
+ case eKeyPress:
+ break;
+ default:
+ return NS_OK;
+ }
+
+ // Make sure we meet the preconditions before continuing
+ nsCOMPtr<nsIURI> absURI;
+ if (!CheckHandleEventForLinksPrecondition(aVisitor, getter_AddRefs(absURI))) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseDown:
+ {
+ if (aVisitor.mEvent->AsMouseEvent()->button ==
+ WidgetMouseEvent::eLeftButton) {
+ // don't make the link grab the focus if there is no link handler
+ nsILinkHandler *handler = aVisitor.mPresContext->GetLinkHandler();
+ nsIDocument *document = GetComposedDoc();
+ if (handler && document) {
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(this);
+ fm->SetFocus(elem, nsIFocusManager::FLAG_BYMOUSE |
+ nsIFocusManager::FLAG_NOSCROLL);
+ }
+
+ EventStateManager::SetActiveManager(
+ aVisitor.mPresContext->EventStateManager(), this);
+ }
+ }
+ }
+ break;
+
+ case eMouseClick: {
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent->IsLeftClickEvent()) {
+ if (mouseEvent->IsControl() || mouseEvent->IsMeta() ||
+ mouseEvent->IsAlt() ||mouseEvent->IsShift()) {
+ break;
+ }
+
+ // The default action is simply to dispatch DOMActivate
+ nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
+ if (shell) {
+ // single-click
+ nsEventStatus status = nsEventStatus_eIgnore;
+ // DOMActive 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 = shell->HandleDOMEventWithTarget(this, &actEvent, &status);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ }
+ break;
+ }
+ case eLegacyDOMActivate:
+ {
+ if (aVisitor.mEvent->mOriginalTarget == this) {
+ nsAutoString target;
+ GetLinkTarget(target);
+ const InternalUIEvent* activeEvent = aVisitor.mEvent->AsUIEvent();
+ MOZ_ASSERT(activeEvent);
+ nsContentUtils::TriggerLink(this, aVisitor.mPresContext, absURI, target,
+ true, 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
+ NS_NOTREACHED("switch statements not in sync");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return rv;
+}
+
+void
+Element::GetLinkTarget(nsAString& aTarget)
+{
+ aTarget.Truncate();
+}
+
+static void
+nsDOMTokenListPropertyDestructor(void *aObject, nsIAtom *aProperty,
+ void *aPropertyValue, void *aData)
+{
+ nsDOMTokenList* list =
+ static_cast<nsDOMTokenList*>(aPropertyValue);
+ NS_RELEASE(list);
+}
+
+static nsIAtom** sPropertiesToTraverseAndUnlink[] =
+ {
+ &nsGkAtoms::sandbox,
+ &nsGkAtoms::sizes,
+ &nsGkAtoms::dirAutoSetBy,
+ nullptr
+ };
+
+// static
+nsIAtom***
+Element::HTMLSVGPropertiesToTraverseAndUnlink()
+{
+ return sPropertiesToTraverseAndUnlink;
+}
+
+nsDOMTokenList*
+Element::GetTokenList(nsIAtom* aAtom,
+ const DOMTokenListSupportedTokenArray aSupportedTokens)
+{
+#ifdef DEBUG
+ nsIAtom*** props =
+ HTMLSVGPropertiesToTraverseAndUnlink();
+ bool found = false;
+ for (uint32_t i = 0; props[i]; ++i) {
+ if (*props[i] == aAtom) {
+ found = true;
+ break;
+ }
+ }
+ MOZ_ASSERT(found, "Trying to use an unknown tokenlist!");
+#endif
+
+ nsDOMTokenList* list = nullptr;
+ if (HasProperties()) {
+ list = static_cast<nsDOMTokenList*>(GetProperty(aAtom));
+ }
+ if (!list) {
+ list = new nsDOMTokenList(this, aAtom, aSupportedTokens);
+ NS_ADDREF(list);
+ SetProperty(aAtom, list, nsDOMTokenListPropertyDestructor);
+ }
+ return list;
+}
+
+Element*
+Element::Closest(const nsAString& aSelector, ErrorResult& aResult)
+{
+ nsCSSSelectorList* selectorList = ParseSelectorList(aSelector, aResult);
+ if (!selectorList) {
+ // Either we failed (and aResult already has the exception), or this
+ // is a pseudo-element-only selector that matches nothing.
+ return nullptr;
+ }
+ OwnerDoc()->FlushPendingLinkUpdates();
+ TreeMatchContext matchingContext(false,
+ nsRuleWalker::eRelevantLinkUnvisited,
+ OwnerDoc(),
+ TreeMatchContext::eNeverMatchVisited);
+ matchingContext.SetHasSpecifiedScope();
+ matchingContext.AddScopeElement(this);
+ for (nsINode* node = this; node; node = node->GetParentNode()) {
+ if (node->IsElement() &&
+ nsCSSRuleProcessor::SelectorListMatches(node->AsElement(),
+ matchingContext,
+ selectorList)) {
+ return node->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+bool
+Element::Matches(const nsAString& aSelector, ErrorResult& aError)
+{
+ nsCSSSelectorList* selectorList = ParseSelectorList(aSelector, aError);
+ if (!selectorList) {
+ // Either we failed (and aError already has the exception), or this
+ // is a pseudo-element-only selector that matches nothing.
+ return false;
+ }
+
+ OwnerDoc()->FlushPendingLinkUpdates();
+ TreeMatchContext matchingContext(false,
+ nsRuleWalker::eRelevantLinkUnvisited,
+ OwnerDoc(),
+ TreeMatchContext::eNeverMatchVisited);
+ matchingContext.SetHasSpecifiedScope();
+ matchingContext.AddScopeElement(this);
+ return nsCSSRuleProcessor::SelectorListMatches(this, matchingContext,
+ selectorList);
+}
+
+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());
+}
+
+static const char*
+GetFullScreenError(nsIDocument* aDoc)
+{
+ if (aDoc->NodePrincipal()->GetAppStatus() >= nsIPrincipal::APP_STATUS_INSTALLED) {
+ // Request is in a web app and in the same origin as the web app.
+ // Don't enforce as strict security checks for web apps, the user
+ // is supposed to have trust in them. However documents cross-origin
+ // to the web app must still confirm to the normal security checks.
+ return nullptr;
+ }
+
+ if (!nsContentUtils::IsRequestFullScreenAllowed()) {
+ return "FullscreenDeniedNotInputDriven";
+ }
+
+ return nullptr;
+}
+
+void
+Element::RequestFullscreen(ErrorResult& aError)
+{
+ // Only grant full-screen 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 full-screen from being abused similar to the popups of old,
+ // and it also makes it harder for bad guys' script to go full-screen 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(OwnerDoc())) {
+ OwnerDoc()->DispatchFullscreenError(error);
+ return;
+ }
+
+ auto request = MakeUnique<FullscreenRequest>(this);
+ request->mIsCallerChrome = nsContentUtils::IsCallerChrome();
+
+ OwnerDoc()->AsyncRequestFullScreen(Move(request));
+}
+
+void
+Element::RequestPointerLock()
+{
+ OwnerDoc()->RequestPointerLock(this);
+}
+
+void
+Element::GetGridFragments(nsTArray<RefPtr<Grid>>& aResult)
+{
+ nsGridContainerFrame* frame =
+ nsGridContainerFrame::GetGridFrameWithComputedInfo(GetPrimaryFrame());
+
+ // If we get a nsGridContainerFrame from the prior call,
+ // all the next-in-flow frames will also be nsGridContainerFrames.
+ while (frame) {
+ aResult.AppendElement(
+ new Grid(this, frame)
+ );
+ frame = static_cast<nsGridContainerFrame*>(frame->GetNextInFlow());
+ }
+}
+
+already_AddRefed<Animation>
+Element::Animate(JSContext* aContext,
+ JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aError)
+{
+ Nullable<ElementOrCSSPseudoElement> target;
+ target.SetValue().SetAsElement() = this;
+ return Animate(target, aContext, aKeyframes, aOptions, aError);
+}
+
+/* static */ already_AddRefed<Animation>
+Element::Animate(const Nullable<ElementOrCSSPseudoElement>& aTarget,
+ JSContext* aContext,
+ JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(!aTarget.IsNull() &&
+ (aTarget.Value().IsElement() ||
+ aTarget.Value().IsCSSPseudoElement()),
+ "aTarget should be initialized");
+
+ RefPtr<Element> referenceElement;
+ if (aTarget.Value().IsElement()) {
+ referenceElement = &aTarget.Value().GetAsElement();
+ } else {
+ referenceElement = aTarget.Value().GetAsCSSPseudoElement().ParentElement();
+ }
+
+ nsCOMPtr<nsIGlobalObject> ownerGlobal = referenceElement->GetOwnerGlobal();
+ if (!ownerGlobal) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ GlobalObject global(aContext, ownerGlobal->GetGlobalJSObject());
+ MOZ_ASSERT(!global.Failed());
+
+ // Wrap the aKeyframes object for the cross-compartment case.
+ JS::Rooted<JSObject*> keyframes(aContext);
+ keyframes = aKeyframes;
+ Maybe<JSAutoCompartment> ac;
+ if (js::GetContextCompartment(aContext) !=
+ js::GetObjectCompartment(ownerGlobal->GetGlobalJSObject())) {
+ ac.emplace(aContext, ownerGlobal->GetGlobalJSObject());
+ if (!JS_WrapObject(aContext, &keyframes)) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<KeyframeEffect> effect =
+ KeyframeEffect::Constructor(global, aTarget, keyframes, aOptions, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ AnimationTimeline* timeline = referenceElement->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 AnimationFilter& filter,
+ nsTArray<RefPtr<Animation>>& aAnimations)
+{
+ nsIDocument* doc = GetComposedDoc();
+ if (doc) {
+ doc->FlushPendingNotifications(Flush_Style);
+ }
+
+ Element* elem = this;
+ CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
+ // For animations on generated-content elements, the animations are stored
+ // on the parent element.
+ if (IsGeneratedContentContainerForBefore()) {
+ elem = GetParentElement();
+ pseudoType = CSSPseudoElementType::before;
+ } else if (IsGeneratedContentContainerForAfter()) {
+ elem = GetParentElement();
+ pseudoType = CSSPseudoElementType::after;
+ }
+
+ if (!elem) {
+ return;
+ }
+
+ if (!filter.mSubtree ||
+ pseudoType == CSSPseudoElementType::before ||
+ pseudoType == CSSPseudoElementType::after) {
+ 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, CSSPseudoElementType::NotPseudo,
+ aAnimations);
+ Element::GetAnimationsUnsorted(element, CSSPseudoElementType::before,
+ aAnimations);
+ Element::GetAnimationsUnsorted(element, CSSPseudoElementType::after,
+ aAnimations);
+ }
+ }
+ aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>());
+}
+
+/* static */ void
+Element::GetAnimationsUnsorted(Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ nsTArray<RefPtr<Animation>>& aAnimations)
+{
+ MOZ_ASSERT(aPseudoType == CSSPseudoElementType::NotPseudo ||
+ aPseudoType == CSSPseudoElementType::after ||
+ aPseudoType == CSSPseudoElementType::before,
+ "Unsupported pseudo type");
+ MOZ_ASSERT(aElement, "Null element");
+
+ EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
+ if (!effects) {
+ return;
+ }
+
+ for (KeyframeEffectReadOnly* 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);
+ }
+}
+
+NS_IMETHODIMP
+Element::GetInnerHTML(nsAString& aInnerHTML)
+{
+ GetMarkup(false, aInnerHTML);
+ return NS_OK;
+}
+
+void
+Element::SetInnerHTML(const nsAString& aInnerHTML, 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() == nsIDOMNode::DOCUMENT_NODE) {
+ aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
+ return;
+ }
+
+ if (OwnerDoc()->IsHTMLDocument()) {
+ nsIAtom* localName;
+ int32_t namespaceID;
+ if (parent->IsElement()) {
+ localName = parent->NodeInfo()->NameAtom();
+ namespaceID = parent->NodeInfo()->NamespaceID();
+ } else {
+ NS_ASSERTION(parent->NodeType() == nsIDOMNode::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 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() == nsIDOMNode::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,
+ nsIDOMNode::ELEMENT_NODE);
+ context = NS_NewHTMLBodyElement(info.forget(), FROM_PARSER_FRAGMENT);
+ }
+
+ nsCOMPtr<nsIDOMDocumentFragment> df;
+ aError = nsContentUtils::CreateContextualFragment(context,
+ aOuterHTML,
+ true,
+ getter_AddRefs(df));
+ if (aError.Failed()) {
+ return;
+ }
+ nsCOMPtr<nsINode> fragment = do_QueryInterface(df);
+ 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;
+ }
+
+ nsIDocument* doc = OwnerDoc();
+
+ // Needed when insertAdjacentHTML is used in combination with contenteditable
+ mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, 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();
+ nsIAtom* 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
+ nsCOMPtr<nsIDOMDocumentFragment> df;
+ aError = nsContentUtils::CreateContextualFragment(destination,
+ aText,
+ true,
+ getter_AddRefs(df));
+ if (aError.Failed()) {
+ return;
+ }
+
+ nsCOMPtr<nsINode> fragment = do_QueryInterface(df);
+
+ // 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;
+
+ nsAutoMutationBatch mb(destination, true, false);
+ 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);
+}
+
+nsIEditor*
+Element::GetEditorInternal()
+{
+ nsCOMPtr<nsITextControlElement> textCtrl = do_QueryInterface(this);
+ return textCtrl ? textCtrl->GetTextEditor() : nullptr;
+}
+
+nsresult
+Element::SetBoolAttr(nsIAtom* aAttr, bool aValue)
+{
+ if (aValue) {
+ return SetAttr(kNameSpaceID_None, aAttr, EmptyString(), true);
+ }
+
+ return UnsetAttr(kNameSpaceID_None, aAttr, true);
+}
+
+void
+Element::GetEnumAttr(nsIAtom* aAttr,
+ const char* aDefault,
+ nsAString& aResult) const
+{
+ GetEnumAttr(aAttr, aDefault, aDefault, aResult);
+}
+
+void
+Element::GetEnumAttr(nsIAtom* aAttr,
+ const char* aDefaultMissing,
+ const char* aDefaultInvalid,
+ nsAString& aResult) const
+{
+ const nsAttrValue* attrVal = mAttrsAndChildren.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(nsIAtom* aName, const nsAString& aValue,
+ ErrorResult& aError)
+{
+ if (DOMStringIsNull(aValue)) {
+ UnsetAttr(aName, aError);
+ } else {
+ SetAttr(aName, aValue, aError);
+ }
+}
+
+Directionality
+Element::GetComputedDirectionality() const
+{
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ return frame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_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;
+}
+
+net::ReferrerPolicy
+Element::GetReferrerPolicyAsEnum()
+{
+ if (Preferences::GetBool("network.http.enablePerElementReferrer", true) &&
+ IsHTMLElement()) {
+ const nsAttrValue* referrerValue = GetParsedAttr(nsGkAtoms::referrerpolicy);
+ if (referrerValue && referrerValue->Type() == nsAttrValue::eEnum) {
+ return net::ReferrerPolicy(referrerValue->GetEnumValue());
+ }
+ }
+ return net::RP_Unset;
+}
+
+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;
+}
+
+nsTArray<Element::nsDOMSlots::IntersectionObserverRegistration>*
+Element::RegisteredIntersectionObservers()
+{
+ nsDOMSlots* slots = DOMSlots();
+ return &slots->mRegisteredIntersectionObservers;
+}
+
+void
+Element::RegisterIntersectionObserver(DOMIntersectionObserver* aObserver)
+{
+ RegisteredIntersectionObservers()->AppendElement(
+ nsDOMSlots::IntersectionObserverRegistration { aObserver, -1 });
+}
+
+void
+Element::UnregisterIntersectionObserver(DOMIntersectionObserver* aObserver)
+{
+ nsTArray<nsDOMSlots::IntersectionObserverRegistration>* observers =
+ RegisteredIntersectionObservers();
+ for (uint32_t i = 0; i < observers->Length(); ++i) {
+ nsDOMSlots::IntersectionObserverRegistration reg = observers->ElementAt(i);
+ if (reg.observer == aObserver) {
+ observers->RemoveElementAt(i);
+ break;
+ }
+ }
+}
+
+bool
+Element::UpdateIntersectionObservation(DOMIntersectionObserver* aObserver, int32_t aThreshold)
+{
+ nsTArray<nsDOMSlots::IntersectionObserverRegistration>* observers =
+ RegisteredIntersectionObservers();
+ for (auto& reg : *observers) {
+ if (reg.observer == aObserver && reg.previousThreshold != aThreshold) {
+ reg.previousThreshold = aThreshold;
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/dom/base/Element.h b/dom/base/Element.h
new file mode 100644
index 000000000..5d878df60
--- /dev/null
+++ b/dom/base/Element.h
@@ -0,0 +1,1907 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMElement, implements nsIContent, provides
+ * utility methods for subclasses, and so forth.
+ */
+
+#ifndef mozilla_dom_Element_h__
+#define mozilla_dom_Element_h__
+
+#include "mozilla/dom/FragmentOrElement.h" // for base class
+#include "nsChangeHint.h" // for enum
+#include "mozilla/EventStates.h" // for member
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsIDOMElement.h"
+#include "nsILinkHandler.h"
+#include "nsINodeList.h"
+#include "nsNodeUtils.h"
+#include "nsAttrAndChildArray.h"
+#include "mozFlushType.h"
+#include "nsDOMAttributeMap.h"
+#include "nsPresContext.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/Attributes.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/dom/Attr.h"
+#include "nsISMILAttr.h"
+#include "mozilla/dom/DOMRect.h"
+#include "nsAttrValue.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMTokenListSupportedTokens.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "Units.h"
+#include "DOMIntersectionObserver.h"
+
+class nsIFrame;
+class nsIDOMMozNamedAttrMap;
+class nsIURI;
+class nsIScrollableFrame;
+class nsAttrValueOrString;
+class nsContentList;
+class nsDOMTokenList;
+struct nsRect;
+class nsFocusManager;
+class nsGlobalWindow;
+class nsICSSDeclaration;
+class nsISMILAttr;
+class nsDocument;
+class nsDOMStringMap;
+
+namespace mozilla {
+class DeclarationBlock;
+namespace dom {
+ struct AnimationFilter;
+ struct ScrollIntoViewOptions;
+ struct ScrollToOptions;
+ class DOMIntersectionObserver;
+ class ElementOrCSSPseudoElement;
+ class UnrestrictedDoubleOrKeyframeAnimationOptions;
+} // namespace dom
+} // namespace mozilla
+
+
+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 {
+ // Set if the element has a pending style change.
+ ELEMENT_HAS_PENDING_RESTYLE = NODE_SHARED_RESTYLE_BIT_1,
+
+ // Set if the element is a potential restyle root (that is, has a style
+ // change pending _and_ that style change will attempt to restyle
+ // descendants).
+ ELEMENT_IS_POTENTIAL_RESTYLE_ROOT = NODE_SHARED_RESTYLE_BIT_2,
+
+ // Set if the element has a pending animation-only style change as
+ // part of an animation-only style update (where we update styles from
+ // animation to the current refresh tick, but leave everything else as
+ // it was).
+ ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE = ELEMENT_FLAG_BIT(0),
+
+ // Set if the element is a potential animation-only restyle root (that
+ // is, has an animation-only style change pending _and_ that style
+ // change will attempt to restyle descendants).
+ ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT = ELEMENT_FLAG_BIT(1),
+
+ // Set if this element has a pending restyle with an eRestyle_SomeDescendants
+ // restyle hint.
+ ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR = ELEMENT_FLAG_BIT(2),
+
+ // Just the HAS_PENDING bits, for convenience
+ ELEMENT_PENDING_RESTYLE_FLAGS =
+ ELEMENT_HAS_PENDING_RESTYLE |
+ ELEMENT_HAS_PENDING_ANIMATION_ONLY_RESTYLE,
+
+ // Just the IS_POTENTIAL bits, for convenience
+ ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS =
+ ELEMENT_IS_POTENTIAL_RESTYLE_ROOT |
+ ELEMENT_IS_POTENTIAL_ANIMATION_ONLY_RESTYLE_ROOT,
+
+ // All of the restyle bits together, for convenience.
+ ELEMENT_ALL_RESTYLE_FLAGS = ELEMENT_PENDING_RESTYLE_FLAGS |
+ ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS |
+ ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR,
+
+ // Set if this element is marked as 'scrollgrab' (see bug 912666)
+ ELEMENT_HAS_SCROLLGRAB = ELEMENT_FLAG_BIT(3),
+
+ // Remaining bits are for subclasses
+ ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 4
+};
+
+#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 CSSPseudoElementType : uint8_t;
+class EventChainPostVisitor;
+class EventChainPreVisitor;
+class EventChainVisitor;
+class EventListenerManager;
+class EventStateManager;
+
+namespace dom {
+
+class Animation;
+class CustomElementRegistry;
+class Link;
+class DOMRect;
+class DOMRectList;
+class DestinationInsertionPointList;
+class Grid;
+
+// IID for the dom::Element interface
+#define NS_ELEMENT_IID \
+{ 0xc67ed254, 0xfd3b, 0x4b10, \
+ { 0x96, 0xa2, 0xc5, 0x8b, 0x7b, 0x64, 0x97, 0xd1 } }
+
+class Element : public FragmentOrElement
+{
+public:
+#ifdef MOZILLA_INTERNAL_API
+ explicit Element(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) :
+ FragmentOrElement(aNodeInfo),
+ mState(NS_EVENT_STATE_MOZ_READONLY)
+ {
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::ELEMENT_NODE,
+ "Bad NodeType in aNodeInfo");
+ SetIsElement();
+ }
+#endif // MOZILLA_INTERNAL_API
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ELEMENT_IID)
+
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+
+ /**
+ * Method to get the full state of this element. See mozilla/EventStates.h
+ * for the possible bits that could be set here.
+ */
+ EventStates 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(EventStates aState);
+
+ virtual int32_t TabIndexDefault()
+ {
+ return -1;
+ }
+
+ /**
+ * Get tabIndex of this element. If not found, return TabIndexDefault.
+ */
+ int32_t TabIndex();
+
+ /**
+ * Set tabIndex value to this element.
+ */
+ void SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError);
+
+ /**
+ * Make focus on this element.
+ */
+ virtual void Focus(mozilla::ErrorResult& aError);
+
+ /**
+ * Show blur and clear focus.
+ */
+ 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.
+ */
+ EventStates StyleState() const
+ {
+ if (!HasLockedStyleStates()) {
+ return mState;
+ }
+ return StyleStateFromLocks();
+ }
+
+ /**
+ * The style state locks applied to this element.
+ */
+ EventStates LockedStyleStates() const;
+
+ /**
+ * Add a style state lock on this element.
+ */
+ void LockStyleStates(EventStates aStates);
+
+ /**
+ * Remove a style state lock on this element.
+ */
+ void UnlockStyleStates(EventStates aStates);
+
+ /**
+ * Clear all style state locks on this element.
+ */
+ void ClearStyleStateLocks();
+
+ /**
+ * Get the inline style declaration, if any, for this element.
+ */
+ virtual DeclarationBlock* GetInlineStyleDeclaration();
+
+ /**
+ * Set the inline style declaration for this element. This will send
+ * an appropriate AttributeChanged notification if aNotify is true.
+ */
+ virtual nsresult SetInlineStyleDeclaration(DeclarationBlock* aDeclaration,
+ const nsAString* aSerialized,
+ bool aNotify);
+
+ /**
+ * Get the SMIL override style declaration for this element. If the
+ * rule hasn't been created, this method simply returns null.
+ */
+ virtual DeclarationBlock* GetSMILOverrideStyleDeclaration();
+
+ /**
+ * Set the SMIL override style declaration for this element. If
+ * aNotify is true, this method will notify the document's pres
+ * context, so that the style changes will be noticed.
+ */
+ virtual nsresult SetSMILOverrideStyleDeclaration(
+ DeclarationBlock* aDeclaration, bool aNotify);
+
+ /**
+ * Returns a new nsISMILAttr that allows the caller to animate the given
+ * attribute on this element.
+ *
+ * The CALLER OWNS the result and is responsible for deleting it.
+ */
+ virtual nsISMILAttr* GetAnimatedAttr(int32_t aNamespaceID, nsIAtom* aName)
+ {
+ return nullptr;
+ }
+
+ /**
+ * 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.
+ */
+ virtual nsICSSDeclaration* GetSMILOverrideStyle();
+
+ /**
+ * 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(bool aIgnoreTabindex) const;
+
+ /**
+ * 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 nsIAtom* 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 nsIAtom* 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(DIRECTION_STATES);
+ }
+
+ switch (aDir) {
+ case (eDir_RTL):
+ SetFlags(NODE_HAS_DIRECTION_RTL);
+ if (!aNotify) {
+ AddStatesSilently(NS_EVENT_STATE_RTL);
+ }
+ break;
+
+ case(eDir_LTR):
+ SetFlags(NODE_HAS_DIRECTION_LTR);
+ if (!aNotify) {
+ AddStatesSilently(NS_EVENT_STATE_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);
+ }
+ }
+
+ bool GetBindingURL(nsIDocument *aDocument, css::URLValue **aResult);
+
+ // The bdi element defaults to dir=auto if it has no dir attribute set.
+ // Other elements will only have dir=auto if they have an explicit dir=auto,
+ // which will mean that HasValidDir() returns true but HasFixedDir() returns
+ // false
+ inline bool HasDirAuto() const {
+ return (!HasFixedDir() &&
+ (HasValidDir() || IsHTMLElement(nsGkAtoms::bdi)));
+ }
+
+ Directionality GetComputedDirectionality() const;
+
+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
+ * content state, use State(). See mozilla/EventStates.h for
+ * the possible bits that could be set here.
+ */
+ virtual EventStates 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(EventStates 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(EventStates aStates)
+ {
+ mState &= ~aStates;
+ }
+
+private:
+ // Need to allow the ESM, nsGlobalWindow, and the focus manager to
+ // set our state
+ friend class mozilla::EventStateManager;
+ friend class ::nsGlobalWindow;
+ friend class ::nsFocusManager;
+
+ // Allow CusomtElementRegistry to call AddStates.
+ friend class CustomElementRegistry;
+
+ // Also need to allow Link to call UpdateLinkState.
+ friend class Link;
+
+ void NotifyStateChange(EventStates aStates);
+
+ void NotifyStyleStateChange(EventStates aStates);
+
+ // Style state computed from element's state and style locks.
+ EventStates StyleStateFromLocks() const;
+
+protected:
+ // Methods for the ESM 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.
+ virtual void AddStates(EventStates aStates)
+ {
+ NS_PRECONDITION(!aStates.HasAtLeastOneOfStates(INTRINSIC_STATES),
+ "Should only be adding ESM-managed states here");
+ AddStatesSilently(aStates);
+ NotifyStateChange(aStates);
+ }
+ virtual void RemoveStates(EventStates aStates)
+ {
+ NS_PRECONDITION(!aStates.HasAtLeastOneOfStates(INTRINSIC_STATES),
+ "Should only be removing ESM-managed states here");
+ RemoveStatesSilently(aStates);
+ NotifyStateChange(aStates);
+ }
+public:
+ virtual void UpdateEditableState(bool aNotify) override;
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ 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;
+
+ nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAString& aValue, bool aNotify)
+ {
+ return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
+ }
+
+ /**
+ * 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 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 Set to nsIDOMMutationEvent::MODIFICATION or to
+ * nsIDOMMutationEvent::ADDITION, but only if this helper returns true
+ * @param aHasListeners Set to true if there are mutation event listeners
+ * listening for NS_EVENT_BITS_MUTATION_ATTRMODIFIED
+ */
+ bool MaybeCheckSameAttrVal(int32_t aNamespaceID, nsIAtom* aName,
+ nsIAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ bool aNotify, nsAttrValue& aOldValue,
+ uint8_t* aModType, bool* aHasListeners);
+
+ bool OnlyNotifySameValueSet(int32_t aNamespaceID, nsIAtom* aName,
+ nsIAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ bool aNotify, nsAttrValue& aOldValue,
+ uint8_t* aModType, bool* aHasListeners);
+
+ virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName, nsIAtom* aPrefix,
+ const nsAString& aValue, bool aNotify) override;
+ // 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, nsIAtom* aName, nsIAtom* aPrefix,
+ nsAttrValue& aParsedValue, bool aNotify);
+ // GetAttr is not inlined on purpose, to keep down codesize from all
+ // the inlined nsAttrValue bits for C++ callers.
+ bool GetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAString& aResult) const;
+ inline bool HasAttr(int32_t aNameSpaceID, nsIAtom* aName) const;
+ // aCaseSensitive == eIgnoreCaase means ASCII case-insensitive matching.
+ inline bool AttrValueIs(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const;
+ inline bool AttrValueIs(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aValue,
+ nsCaseTreatment aCaseSensitive) const;
+ virtual int32_t FindAttrValueIn(int32_t aNameSpaceID,
+ nsIAtom* aName,
+ AttrValuesArray* aValues,
+ nsCaseTreatment aCaseSensitive) const override;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+ virtual const nsAttrName* GetAttrNameAt(uint32_t aIndex) const override;
+ virtual BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const override;
+ virtual uint32_t GetAttrCount() const override;
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override
+ {
+ List(out, aIndent, EmptyCString());
+ }
+ 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
+
+ void Describe(nsAString& aOutDescription) const override;
+
+ /*
+ * Attribute Mapping Helpers
+ */
+ struct MappedAttributeEntry {
+ nsIAtom** 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 nsIAtom* aAttribute,
+ const MappedAttributeEntry* const (&aMaps)[N])
+ {
+ return FindAttributeDependence(aAttribute, aMaps, N);
+ }
+
+ static nsIAtom*** HTMLSVGPropertiesToTraverseAndUnlink();
+
+private:
+ void DescribeAttribute(uint32_t index, nsAString& aOutDescription) const;
+
+ static bool
+ FindAttributeDependence(const nsIAtom* aAttribute,
+ const MappedAttributeEntry* const aMaps[],
+ uint32_t aMapCount);
+
+protected:
+ inline bool GetAttr(int32_t aNameSpaceID, nsIAtom* 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.HasStringBuffer() && aResult.StringBufferLength() == 0,
+ "Should have empty string coming in");
+ const nsAttrValue* val = mAttrsAndChildren.GetAttr(aName, aNameSpaceID);
+ if (val) {
+ val->ToString(aResult);
+ return true;
+ }
+ // else DOMString comes pre-emptied.
+ return false;
+ }
+
+public:
+ bool HasAttrs() const { return mAttrsAndChildren.HasAttrs(); }
+
+ inline bool GetAttr(const nsAString& aName, DOMString& aResult) const
+ {
+ MOZ_ASSERT(aResult.HasStringBuffer() && aResult.StringBufferLength() == 0,
+ "Should have empty string coming in");
+ const nsAttrValue* val = mAttrsAndChildren.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();
+ nsDOMAttributeMap* Attributes()
+ {
+ nsDOMSlots* slots = DOMSlots();
+ if (!slots->mAttributeMap) {
+ slots->mAttributeMap = new nsDOMAttributeMap(this);
+ }
+
+ return slots->mAttributeMap;
+ }
+
+ void GetAttributeNames(nsTArray<nsString>& aResult);
+
+ void GetAttribute(const nsAString& aName, nsString& 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);
+ void SetAttribute(const nsAString& aName, const nsAString& aValue,
+ ErrorResult& aError);
+ void SetAttributeNS(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 nsAString& aSelector,
+ ErrorResult& aResult);
+ bool Matches(const nsAString& 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);
+
+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)
+ {
+ bool activeState = false;
+ if (!nsIPresShell::GetPointerInfo(aPointerId, activeState)) {
+ aError.Throw(NS_ERROR_DOM_INVALID_POINTER_ERR);
+ return;
+ }
+ if (!IsInUncomposedDoc()) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ if (!activeState) {
+ return;
+ }
+ nsIPresShell::SetPointerCapturingContent(aPointerId, this);
+ }
+ void ReleasePointerCapture(int32_t aPointerId, ErrorResult& aError)
+ {
+ bool activeState = false;
+ if (!nsIPresShell::GetPointerInfo(aPointerId, activeState)) {
+ aError.Throw(NS_ERROR_DOM_INVALID_POINTER_ERR);
+ return;
+ }
+ if (HasPointerCapture(aPointerId)) {
+ nsIPresShell::ReleasePointerCapturingContent(aPointerId);
+ }
+ }
+ bool HasPointerCapture(long aPointerId)
+ {
+ nsIPresShell::PointerCaptureInfo* pointerCaptureInfo =
+ nsIPresShell::GetPointerCaptureInfo(aPointerId);
+ if (pointerCaptureInfo && pointerCaptureInfo->mPendingContent == this) {
+ return true;
+ }
+ return false;
+ }
+ void 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 (!nsIPresShell::GetCapturingContent()) {
+ nsIPresShell::SetCapturingContent(this, CAPTURE_PREVENTDRAG |
+ (aRetargetToElement ? CAPTURE_RETARGETTOELEMENT : 0));
+ }
+ }
+ void ReleaseCapture()
+ {
+ if (nsIPresShell::GetCapturingContent() == this) {
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+ }
+ }
+
+ void RequestFullscreen(ErrorResult& aError);
+ void RequestPointerLock();
+ 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);
+
+ already_AddRefed<DOMRectList> GetClientRects();
+ already_AddRefed<DOMRect> GetBoundingClientRect();
+
+ already_AddRefed<ShadowRoot> CreateShadowRoot(ErrorResult& aError);
+ already_AddRefed<DestinationInsertionPointList> GetDestinationInsertionPoints();
+
+ ShadowRoot *FastGetShadowRoot() const
+ {
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ return slots ? slots->mShadowRoot.get() : nullptr;
+ }
+
+ void ScrollIntoView();
+ void ScrollIntoView(bool aTop);
+ void ScrollIntoView(const ScrollIntoViewOptions &aOptions);
+ void Scroll(double aXScroll, double aYScroll);
+ void Scroll(const ScrollToOptions& aOptions);
+ void ScrollTo(double aXScroll, double aYScroll);
+ void ScrollTo(const ScrollToOptions& aOptions);
+ void ScrollBy(double aXScrollDif, double aYScrollDif);
+ void ScrollBy(const ScrollToOptions& aOptions);
+ /* Scrolls without flushing the layout.
+ * aDx is the x offset, aDy the y offset in CSS pixels.
+ * Returns true if we actually scrolled.
+ */
+ bool ScrollByNoFlush(int32_t aDx, int32_t aDy);
+ int32_t ScrollTop();
+ void SetScrollTop(int32_t aScrollTop);
+ int32_t ScrollLeft();
+ void SetScrollLeft(int32_t aScrollLeft);
+ int32_t ScrollWidth();
+ int32_t ScrollHeight();
+ void MozScrollSnap();
+ int32_t ClientTop()
+ {
+ return nsPresContext::AppUnitsToIntCSSPixels(GetClientAreaRect().y);
+ }
+ int32_t ClientLeft()
+ {
+ return nsPresContext::AppUnitsToIntCSSPixels(GetClientAreaRect().x);
+ }
+ int32_t ClientWidth()
+ {
+ return nsPresContext::AppUnitsToIntCSSPixels(GetClientAreaRect().width);
+ }
+ int32_t ClientHeight()
+ {
+ return nsPresContext::AppUnitsToIntCSSPixels(GetClientAreaRect().height);
+ }
+ int32_t ScrollTopMin()
+ {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ?
+ nsPresContext::AppUnitsToIntCSSPixels(sf->GetScrollRange().y) : 0;
+ }
+ int32_t ScrollTopMax()
+ {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ?
+ nsPresContext::AppUnitsToIntCSSPixels(sf->GetScrollRange().YMost()) :
+ 0;
+ }
+ int32_t ScrollLeftMin()
+ {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ?
+ nsPresContext::AppUnitsToIntCSSPixels(sf->GetScrollRange().x) : 0;
+ }
+ int32_t ScrollLeftMax()
+ {
+ nsIScrollableFrame* sf = GetScrollFrame();
+ return sf ?
+ nsPresContext::AppUnitsToIntCSSPixels(sf->GetScrollRange().XMost()) :
+ 0;
+ }
+
+ void GetGridFragments(nsTArray<RefPtr<Grid>>& aResult);
+
+ already_AddRefed<Animation>
+ Animate(JSContext* aContext,
+ JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aError);
+
+ // A helper method that factors out the common functionality needed by
+ // Element::Animate and CSSPseudoElement::Animate
+ static already_AddRefed<Animation>
+ Animate(const Nullable<ElementOrCSSPseudoElement>& aTarget,
+ JSContext* aContext,
+ JS::Handle<JSObject*> aKeyframes,
+ const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
+ ErrorResult& aError);
+
+ // Note: GetAnimations will flush style while GetAnimationsUnsorted won't.
+ // Callers must keep this element alive because flushing style may destroy
+ // this element.
+ void GetAnimations(const AnimationFilter& filter,
+ nsTArray<RefPtr<Animation>>& aAnimations);
+ static void GetAnimationsUnsorted(Element* aElement,
+ CSSPseudoElementType aPseudoType,
+ nsTArray<RefPtr<Animation>>& aAnimations);
+
+ NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML);
+ virtual void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
+ void GetOuterHTML(nsAString& aOuterHTML);
+ void SetOuterHTML(const nsAString& aOuterHTML, ErrorResult& aError);
+ void InsertAdjacentHTML(const nsAString& aPosition, const nsAString& aText,
+ 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
+ */
+ nsresult SetEventHandler(nsIAtom* 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.
+ */
+ 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;
+ static nsresult DispatchEvent(nsPresContext* aPresContext,
+ WidgetEvent* aEvent,
+ nsIContent* aTarget,
+ bool aFullDispatch,
+ nsEventStatus* aStatus);
+
+ /**
+ * Get the primary frame for this content with flushing
+ *
+ * @param aType the kind of flush to do, typically Flush_Frames or
+ * Flush_Layout
+ * @return the primary frame
+ */
+ nsIFrame* GetPrimaryFrame(mozFlushType aType);
+ // Work around silly C++ name hiding stuff
+ nsIFrame* GetPrimaryFrame() const { return nsIContent::GetPrimaryFrame(); }
+
+ const nsAttrValue* GetParsedAttr(nsIAtom* aAttr) const
+ {
+ return mAttrsAndChildren.GetAttr(aAttr);
+ }
+
+ const nsAttrValue* GetParsedAttr(nsIAtom* aAttr, int32_t aNameSpaceID) const
+ {
+ return mAttrsAndChildren.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;
+ }
+
+ virtual 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 mAttrsAndChildren.
+ */
+ virtual BorrowedAttrInfo GetAttrInfo(int32_t aNamespaceID, nsIAtom* aName) const;
+
+ virtual void NodeInfoChanged()
+ {
+ }
+
+ /**
+ * 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);
+
+ // These are just used to implement nsIDOMElement using
+ // NS_FORWARD_NSIDOMELEMENT_TO_GENERIC and for quickstubs.
+ void
+ GetElementsByTagName(const nsAString& aQualifiedName,
+ nsIDOMHTMLCollection** aResult);
+ nsresult
+ GetElementsByTagNameNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ nsIDOMHTMLCollection** aResult);
+ nsresult
+ GetElementsByClassName(const nsAString& aClassNames,
+ nsIDOMHTMLCollection** aResult);
+ void GetClassList(nsISupports** aClassList);
+
+ virtual JSObject* WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) final override;
+
+ nsINode* GetScopeChainParent() const override;
+
+ /**
+ * Locate an nsIEditor rooted at this content node, if there is one.
+ */
+ nsIEditor* GetEditorInternal();
+
+ /**
+ * Helper method for NS_IMPL_BOOL_ATTR macro.
+ * 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(nsIAtom* aAttr) const
+ {
+ return HasAttr(kNameSpaceID_None, aAttr);
+ }
+
+ /**
+ * Helper method for NS_IMPL_BOOL_ATTR macro.
+ * 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(nsIAtom* aAttr, bool aValue);
+
+ /**
+ * Helper method for NS_IMPL_ENUM_ATTR_DEFAULT_VALUE.
+ * 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(nsIAtom* aAttr,
+ const char* aDefault,
+ nsAString& aResult) const;
+
+ /**
+ * Helper method for NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES.
+ * 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(nsIAtom* aAttr,
+ const char* aDefaultMissing,
+ const char* aDefaultInvalid,
+ nsAString& aResult) const;
+
+ /**
+ * Unset an attribute.
+ */
+ void UnsetAttr(nsIAtom* aAttr, ErrorResult& aError)
+ {
+ aError = UnsetAttr(kNameSpaceID_None, aAttr, true);
+ }
+
+ /**
+ * Set an attribute in the simplest way possible.
+ */
+ void SetAttr(nsIAtom* aAttr, const nsAString& aValue, ErrorResult& aError)
+ {
+ aError = SetAttr(kNameSpaceID_None, aAttr, aValue, 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(nsIAtom* 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();
+
+ net::ReferrerPolicy GetReferrerPolicyAsEnum();
+
+ /*
+ * 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);
+ bool UpdateIntersectionObservation(DOMIntersectionObserver* aObserver, int32_t threshold);
+
+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.
+ * @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 aModType nsIDOMMutationEvent::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?
+ */
+ nsresult SetAttrAndNotify(int32_t aNamespaceID,
+ nsIAtom* aName,
+ nsIAtom* aPrefix,
+ const nsAttrValue& aOldValue,
+ nsAttrValue& aParsedValue,
+ uint8_t aModType,
+ bool aFireMutation,
+ bool aNotify,
+ bool aCallAfterSetAttr);
+
+ /**
+ * 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
+ */
+ 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 aResult the nsAttrValue [OUT]
+ * @return true if the parsing was successful, false otherwise
+ */
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ 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 aDocument the current document of this node (an optimization)
+ * @param aName the name of the attribute
+ * @param aValue the nsAttrValue to set
+ * @param [out] aRetval the nsresult status of the operation, if any.
+ * @return true if the setting was attempted, false otherwise.
+ */
+ virtual bool SetMappedAttribute(nsIDocument* aDocument,
+ nsIAtom* aName,
+ nsAttrValue& aValue,
+ 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. BeforeSetAttr is allowed to modify aValue by parsing
+ * the string to an nsAttrValue (to avoid having to reparse it in
+ * ParseAttribute).
+ * @param aNotify Whether we plan to notify document observers.
+ */
+ // Note that this is inlined so that when subclasses call it it gets
+ // inlined. Those calls don't go through a vtable.
+ virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsAttrValueOrString* 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
+ * SetAndTakeAttr and AttributeChanged (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 aNotify Whether we plan to notify document observers.
+ */
+ // Note that this is inlined so that when subclasses call it it gets
+ // inlined. Those calls don't go through a vtable.
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+ {
+ return NS_OK;
+ }
+
+ /**
+ * Hook to allow subclasses to produce a different EventListenerManager if
+ * needed for attachment of attribute-defined handlers
+ */
+ virtual EventListenerManager*
+ GetEventListenerManagerForAttr(nsIAtom* 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;
+
+ nsIFrame* GetStyledFrame();
+
+ 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(nsIAtom* 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
+ * @param aURI the uri of the link, set only if the return value is true [OUT]
+ * @return true if we can handle the link event, false otherwise
+ */
+ bool CheckHandleEventForLinksPrecondition(EventChainVisitor& aVisitor,
+ nsIURI** aURI) const;
+
+ /**
+ * Handle status bar updates before they can be cancelled.
+ */
+ nsresult PreHandleEventForLinks(EventChainPreVisitor& aVisitor);
+
+ /**
+ * Handle default actions for link event if the event isn't consumed yet.
+ */
+ nsresult PostHandleEventForLinks(EventChainPostVisitor& aVisitor);
+
+ /**
+ * 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);
+
+ nsDOMTokenList* GetTokenList(nsIAtom* aAtom,
+ const DOMTokenListSupportedTokenArray aSupportedTokens = nullptr);
+
+ nsTArray<nsDOMSlots::IntersectionObserverRegistration>* RegisteredIntersectionObservers();
+
+private:
+ /**
+ * Get this element's client area rect in app units.
+ * @return the frame's client area
+ */
+ nsRect GetClientAreaRect();
+
+ nsIScrollableFrame* GetScrollFrame(nsIFrame **aStyledFrame = nullptr,
+ bool aFlushLayout = true);
+
+ // Data members
+ EventStates mState;
+};
+
+class RemoveFromBindingManagerRunnable : public mozilla::Runnable
+{
+public:
+ RemoveFromBindingManagerRunnable(nsBindingManager* aManager,
+ nsIContent* aContent,
+ nsIDocument* aDoc);
+
+ NS_IMETHOD Run() override;
+private:
+ virtual ~RemoveFromBindingManagerRunnable();
+ RefPtr<nsBindingManager> mManager;
+ RefPtr<nsIContent> mContent;
+ nsCOMPtr<nsIDocument> mDoc;
+};
+
+class DestinationInsertionPointList : public nsINodeList
+{
+public:
+ explicit DestinationInsertionPointList(Element* aElement);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DestinationInsertionPointList)
+
+ // nsIDOMNodeList
+ NS_DECL_NSIDOMNODELIST
+
+ // nsINodeList
+ virtual nsIContent* Item(uint32_t aIndex) override;
+ virtual int32_t IndexOf(nsIContent* aContent) override;
+ virtual nsINode* GetParentObject() override { return mParent; }
+ virtual uint32_t Length() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+protected:
+ virtual ~DestinationInsertionPointList();
+
+ RefPtr<Element> mParent;
+ nsCOMArray<nsIContent> mDestinationPoints;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Element, NS_ELEMENT_IID)
+
+inline bool
+Element::HasAttr(int32_t aNameSpaceID, nsIAtom* aName) const
+{
+ NS_ASSERTION(nullptr != aName, "must have attribute name");
+ NS_ASSERTION(aNameSpaceID != kNameSpaceID_Unknown,
+ "must have a real namespace ID!");
+
+ return mAttrsAndChildren.IndexOfAttr(aName, aNameSpaceID) >= 0;
+}
+
+inline bool
+Element::AttrValueIs(int32_t aNameSpaceID,
+ nsIAtom* 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 = mAttrsAndChildren.GetAttr(aName, aNameSpaceID);
+ return val && val->Equals(aValue, aCaseSensitive);
+}
+
+inline bool
+Element::AttrValueIs(int32_t aNameSpaceID,
+ nsIAtom* aName,
+ nsIAtom* 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 = mAttrsAndChildren.GetAttr(aName, aNameSpaceID);
+ return val && val->Equals(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 void nsINode::UnsetRestyleFlagsIfGecko()
+{
+ if (IsElement() && !IsStyledByServo()) {
+ UnsetFlags(ELEMENT_ALL_RESTYLE_FLAGS);
+ }
+}
+
+/**
+ * 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; \
+ already_AddRefed<mozilla::dom::NodeInfo> ni = \
+ RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget(); \
+ _elementName *it = new _elementName(ni); \
+ if (!it) { \
+ return NS_ERROR_OUT_OF_MEMORY; \
+ } \
+ \
+ nsCOMPtr<nsINode> kungFuDeathGrip = it; \
+ nsresult rv = const_cast<_elementName*>(this)->CopyInnerTo(it); \
+ if (NS_SUCCEEDED(rv)) { \
+ kungFuDeathGrip.swap(*aResult); \
+ } \
+ \
+ return rv; \
+}
+
+#define NS_IMPL_ELEMENT_CLONE_WITH_INIT(_elementName) \
+nsresult \
+_elementName::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const \
+{ \
+ *aResult = nullptr; \
+ already_AddRefed<mozilla::dom::NodeInfo> ni = \
+ RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget(); \
+ _elementName *it = new _elementName(ni); \
+ if (!it) { \
+ return NS_ERROR_OUT_OF_MEMORY; \
+ } \
+ \
+ nsCOMPtr<nsINode> kungFuDeathGrip = it; \
+ nsresult rv = it->Init(); \
+ nsresult rv2 = const_cast<_elementName*>(this)->CopyInnerTo(it); \
+ if (NS_FAILED(rv2)) { \
+ rv = rv2; \
+ } \
+ if (NS_SUCCEEDED(rv)) { \
+ kungFuDeathGrip.swap(*aResult); \
+ } \
+ \
+ return rv; \
+}
+
+/**
+ * A macro to implement the getter and setter for a given string
+ * valued content property. The method uses the generic GetAttr and
+ * SetAttr methods. We use the 5-argument form of SetAttr, because
+ * some consumers only implement that one, hiding superclass
+ * 4-argument forms.
+ */
+#define NS_IMPL_STRING_ATTR(_class, _method, _atom) \
+ NS_IMETHODIMP \
+ _class::Get##_method(nsAString& aValue) \
+ { \
+ GetAttr(kNameSpaceID_None, nsGkAtoms::_atom, aValue); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(const nsAString& aValue) \
+ { \
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::_atom, nullptr, aValue, true); \
+ }
+
+/**
+ * A macro to implement the getter and setter for a given boolean
+ * valued content property. The method uses the GetBoolAttr and
+ * SetBoolAttr methods.
+ */
+#define NS_IMPL_BOOL_ATTR(_class, _method, _atom) \
+ NS_IMETHODIMP \
+ _class::Get##_method(bool* aValue) \
+ { \
+ *aValue = GetBoolAttr(nsGkAtoms::_atom); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ _class::Set##_method(bool aValue) \
+ { \
+ return SetBoolAttr(nsGkAtoms::_atom, aValue); \
+ }
+
+#define NS_FORWARD_NSIDOMELEMENT_TO_GENERIC \
+typedef mozilla::dom::Element Element; \
+NS_IMETHOD GetTagName(nsAString& aTagName) final override \
+{ \
+ Element::GetTagName(aTagName); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetId(nsAString& aId) final override \
+{ \
+ Element::GetId(aId); \
+ return NS_OK; \
+} \
+NS_IMETHOD SetId(const nsAString& aId) final override \
+{ \
+ Element::SetId(aId); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetClassName(nsAString& aClassName) final override \
+{ \
+ Element::GetClassName(aClassName); \
+ return NS_OK; \
+} \
+NS_IMETHOD SetClassName(const nsAString& aClassName) final override \
+{ \
+ Element::SetClassName(aClassName); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetClassList(nsISupports** aClassList) final override \
+{ \
+ Element::GetClassList(aClassList); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetAttributes(nsIDOMMozNamedAttrMap** aAttributes) final override \
+{ \
+ NS_ADDREF(*aAttributes = Attributes()); \
+ return NS_OK; \
+} \
+using Element::GetAttribute; \
+NS_IMETHOD GetAttribute(const nsAString& name, nsAString& _retval) final \
+ override \
+{ \
+ nsString attr; \
+ GetAttribute(name, attr); \
+ _retval = attr; \
+ return NS_OK; \
+} \
+NS_IMETHOD GetAttributeNS(const nsAString& namespaceURI, \
+ const nsAString& localName, \
+ nsAString& _retval) final override \
+{ \
+ Element::GetAttributeNS(namespaceURI, localName, _retval); \
+ return NS_OK; \
+} \
+NS_IMETHOD SetAttribute(const nsAString& name, \
+ const nsAString& value) override \
+{ \
+ mozilla::ErrorResult rv; \
+ Element::SetAttribute(name, value, rv); \
+ return rv.StealNSResult(); \
+} \
+NS_IMETHOD SetAttributeNS(const nsAString& namespaceURI, \
+ const nsAString& qualifiedName, \
+ const nsAString& value) final override \
+{ \
+ mozilla::ErrorResult rv; \
+ Element::SetAttributeNS(namespaceURI, qualifiedName, value, rv); \
+ return rv.StealNSResult(); \
+} \
+using Element::RemoveAttribute; \
+NS_IMETHOD RemoveAttribute(const nsAString& name) final override \
+{ \
+ mozilla::ErrorResult rv; \
+ RemoveAttribute(name, rv); \
+ return rv.StealNSResult(); \
+} \
+NS_IMETHOD RemoveAttributeNS(const nsAString& namespaceURI, \
+ const nsAString& localName) final override \
+{ \
+ mozilla::ErrorResult rv; \
+ Element::RemoveAttributeNS(namespaceURI, localName, rv); \
+ return rv.StealNSResult(); \
+} \
+using Element::HasAttribute; \
+NS_IMETHOD HasAttribute(const nsAString& name, \
+ bool* _retval) final override \
+{ \
+ *_retval = HasAttribute(name); \
+ return NS_OK; \
+} \
+NS_IMETHOD HasAttributeNS(const nsAString& namespaceURI, \
+ const nsAString& localName, \
+ bool* _retval) final override \
+{ \
+ *_retval = Element::HasAttributeNS(namespaceURI, localName); \
+ return NS_OK; \
+} \
+NS_IMETHOD HasAttributes(bool* _retval) final override \
+{ \
+ *_retval = Element::HasAttributes(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetAttributeNode(const nsAString& name, \
+ nsIDOMAttr** _retval) final override \
+{ \
+ NS_IF_ADDREF(*_retval = Element::GetAttributeNode(name)); \
+ return NS_OK; \
+} \
+NS_IMETHOD SetAttributeNode(nsIDOMAttr* newAttr, \
+ nsIDOMAttr** _retval) final override \
+{ \
+ if (!newAttr) { \
+ return NS_ERROR_INVALID_POINTER; \
+ } \
+ mozilla::ErrorResult rv; \
+ mozilla::dom::Attr* attr = static_cast<mozilla::dom::Attr*>(newAttr); \
+ *_retval = Element::SetAttributeNode(*attr, rv).take(); \
+ return rv.StealNSResult(); \
+} \
+NS_IMETHOD RemoveAttributeNode(nsIDOMAttr* oldAttr, \
+ nsIDOMAttr** _retval) final override \
+{ \
+ if (!oldAttr) { \
+ return NS_ERROR_INVALID_POINTER; \
+ } \
+ mozilla::ErrorResult rv; \
+ mozilla::dom::Attr* attr = static_cast<mozilla::dom::Attr*>(oldAttr); \
+ *_retval = Element::RemoveAttributeNode(*attr, rv).take(); \
+ return rv.StealNSResult(); \
+} \
+NS_IMETHOD GetAttributeNodeNS(const nsAString& namespaceURI, \
+ const nsAString& localName, \
+ nsIDOMAttr** _retval) final override \
+{ \
+ NS_IF_ADDREF(*_retval = Element::GetAttributeNodeNS(namespaceURI, \
+ localName)); \
+ return NS_OK; \
+} \
+NS_IMETHOD SetAttributeNodeNS(nsIDOMAttr* newAttr, \
+ nsIDOMAttr** _retval) final override \
+{ \
+ mozilla::ErrorResult rv; \
+ mozilla::dom::Attr* attr = static_cast<mozilla::dom::Attr*>(newAttr); \
+ *_retval = Element::SetAttributeNodeNS(*attr, rv).take(); \
+ return rv.StealNSResult(); \
+} \
+NS_IMETHOD GetElementsByTagName(const nsAString& name, \
+ nsIDOMHTMLCollection** _retval) final \
+ override \
+{ \
+ Element::GetElementsByTagName(name, _retval); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetElementsByTagNameNS(const nsAString& namespaceURI, \
+ const nsAString& localName, \
+ nsIDOMHTMLCollection** _retval) final \
+ override \
+{ \
+ return Element::GetElementsByTagNameNS(namespaceURI, localName, \
+ _retval); \
+} \
+NS_IMETHOD GetElementsByClassName(const nsAString& classes, \
+ nsIDOMHTMLCollection** _retval) final \
+ override \
+{ \
+ return Element::GetElementsByClassName(classes, _retval); \
+} \
+NS_IMETHOD GetChildElements(nsIDOMNodeList** aChildElements) final override \
+{ \
+ nsIHTMLCollection* list = FragmentOrElement::Children(); \
+ return CallQueryInterface(list, aChildElements); \
+} \
+NS_IMETHOD GetFirstElementChild(nsIDOMElement** aFirstElementChild) final \
+ override \
+{ \
+ Element* element = Element::GetFirstElementChild(); \
+ if (!element) { \
+ *aFirstElementChild = nullptr; \
+ return NS_OK; \
+ } \
+ return CallQueryInterface(element, aFirstElementChild); \
+} \
+NS_IMETHOD GetLastElementChild(nsIDOMElement** aLastElementChild) final \
+ override \
+{ \
+ Element* element = Element::GetLastElementChild(); \
+ if (!element) { \
+ *aLastElementChild = nullptr; \
+ return NS_OK; \
+ } \
+ return CallQueryInterface(element, aLastElementChild); \
+} \
+NS_IMETHOD GetPreviousElementSibling(nsIDOMElement** aPreviousElementSibling) \
+ final override \
+{ \
+ Element* element = Element::GetPreviousElementSibling(); \
+ if (!element) { \
+ *aPreviousElementSibling = nullptr; \
+ return NS_OK; \
+ } \
+ return CallQueryInterface(element, aPreviousElementSibling); \
+} \
+NS_IMETHOD GetNextElementSibling(nsIDOMElement** aNextElementSibling) \
+ final override \
+{ \
+ Element* element = Element::GetNextElementSibling(); \
+ if (!element) { \
+ *aNextElementSibling = nullptr; \
+ return NS_OK; \
+ } \
+ return CallQueryInterface(element, aNextElementSibling); \
+} \
+NS_IMETHOD GetChildElementCount(uint32_t* aChildElementCount) final override \
+{ \
+ *aChildElementCount = Element::ChildElementCount(); \
+ return NS_OK; \
+} \
+NS_IMETHOD MozRemove() final override \
+{ \
+ nsINode::Remove(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetClientRects(nsIDOMClientRectList** _retval) final override \
+{ \
+ *_retval = Element::GetClientRects().take(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetBoundingClientRect(nsIDOMClientRect** _retval) final override \
+{ \
+ *_retval = Element::GetBoundingClientRect().take(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetScrollTop(int32_t* aScrollTop) final override \
+{ \
+ *aScrollTop = Element::ScrollTop(); \
+ return NS_OK; \
+} \
+NS_IMETHOD SetScrollTop(int32_t aScrollTop) final override \
+{ \
+ Element::SetScrollTop(aScrollTop); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetScrollLeft(int32_t* aScrollLeft) final override \
+{ \
+ *aScrollLeft = Element::ScrollLeft(); \
+ return NS_OK; \
+} \
+NS_IMETHOD SetScrollLeft(int32_t aScrollLeft) final override \
+{ \
+ Element::SetScrollLeft(aScrollLeft); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetScrollWidth(int32_t* aScrollWidth) final override \
+{ \
+ *aScrollWidth = Element::ScrollWidth(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetScrollHeight(int32_t* aScrollHeight) final override \
+{ \
+ *aScrollHeight = Element::ScrollHeight(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetClientTop(int32_t* aClientTop) final override \
+{ \
+ *aClientTop = Element::ClientTop(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetClientLeft(int32_t* aClientLeft) final override \
+{ \
+ *aClientLeft = Element::ClientLeft(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetClientWidth(int32_t* aClientWidth) final override \
+{ \
+ *aClientWidth = Element::ClientWidth(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetClientHeight(int32_t* aClientHeight) final override \
+{ \
+ *aClientHeight = Element::ClientHeight(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetScrollLeftMax(int32_t* aScrollLeftMax) final override \
+{ \
+ *aScrollLeftMax = Element::ScrollLeftMax(); \
+ return NS_OK; \
+} \
+NS_IMETHOD GetScrollTopMax(int32_t* aScrollTopMax) final override \
+{ \
+ *aScrollTopMax = Element::ScrollTopMax(); \
+ return NS_OK; \
+} \
+NS_IMETHOD MozMatchesSelector(const nsAString& selector, \
+ bool* _retval) final override \
+{ \
+ mozilla::ErrorResult rv; \
+ *_retval = Element::Matches(selector, rv); \
+ return rv.StealNSResult(); \
+} \
+NS_IMETHOD SetCapture(bool retargetToElement) final override \
+{ \
+ Element::SetCapture(retargetToElement); \
+ return NS_OK; \
+} \
+NS_IMETHOD ReleaseCapture(void) final override \
+{ \
+ Element::ReleaseCapture(); \
+ return NS_OK; \
+} \
+NS_IMETHOD MozRequestFullScreen(void) final override \
+{ \
+ mozilla::ErrorResult rv; \
+ Element::RequestFullscreen(rv); \
+ return rv.StealNSResult(); \
+} \
+NS_IMETHOD MozRequestPointerLock(void) final override \
+{ \
+ Element::RequestPointerLock(); \
+ return NS_OK; \
+} \
+using nsINode::QuerySelector; \
+NS_IMETHOD QuerySelector(const nsAString& aSelector, \
+ nsIDOMElement **aReturn) final override \
+{ \
+ return nsINode::QuerySelector(aSelector, aReturn); \
+} \
+using nsINode::QuerySelectorAll; \
+NS_IMETHOD QuerySelectorAll(const nsAString& aSelector, \
+ nsIDOMNodeList **aReturn) final override \
+{ \
+ return nsINode::QuerySelectorAll(aSelector, aReturn); \
+}
+
+#endif // mozilla_dom_Element_h__
diff --git a/dom/base/ElementInlines.h b/dom/base/ElementInlines.h
new file mode 100644
index 000000000..c68bd012e
--- /dev/null
+++ b/dom/base/ElementInlines.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_ElementInlines_h
+#define mozilla_dom_ElementInlines_h
+
+#include "mozilla/dom/Element.h"
+#include "nsIDocument.h"
+
+namespace mozilla {
+namespace dom {
+
+inline void
+Element::RegisterActivityObserver()
+{
+ OwnerDoc()->RegisterActivityObserver(this);
+}
+
+inline void
+Element::UnregisterActivityObserver()
+{
+ OwnerDoc()->UnregisterActivityObserver(this);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ElementInlines_h
diff --git a/dom/base/EventSource.cpp b/dom/base/EventSource.cpp
new file mode 100644
index 000000000..754a2d13b
--- /dev/null
+++ b/dom/base/EventSource.cpp
@@ -0,0 +1,1356 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/EventSource.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/EventSourceBinding.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "nsAutoPtr.h"
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIInputStream.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsMimeTypes.h"
+#include "nsIPromptFactory.h"
+#include "nsIWindowWatcher.h"
+#include "nsPresContext.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIConsoleService.h"
+#include "nsIObserverService.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsJSUtils.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIScriptError.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsContentUtils.h"
+#include "mozilla/Preferences.h"
+#include "xpcpublic.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/Attributes.h"
+#include "nsError.h"
+
+namespace mozilla {
+namespace dom {
+
+#define REPLACEMENT_CHAR (char16_t)0xFFFD
+#define BOM_CHAR (char16_t)0xFEFF
+#define SPACE_CHAR (char16_t)0x0020
+#define CR_CHAR (char16_t)0x000D
+#define LF_CHAR (char16_t)0x000A
+#define COLON_CHAR (char16_t)0x003A
+
+#define DEFAULT_BUFFER_SIZE 4096
+
+// 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)
+
+EventSource::EventSource(nsPIDOMWindowInner* aOwnerWindow) :
+ DOMEventTargetHelper(aOwnerWindow),
+ mStatus(PARSE_STATE_OFF),
+ mFrozen(false),
+ mErrorLoadOnRedirect(false),
+ mGoingToDispatchAllMessages(false),
+ mWithCredentials(false),
+ mWaitingForOnStopRequest(false),
+ mLastConvertionResult(NS_OK),
+ mReadyState(CONNECTING),
+ mScriptLine(0),
+ mScriptColumn(0),
+ mInnerWindowID(0)
+{
+}
+
+EventSource::~EventSource()
+{
+ Close();
+}
+
+//-----------------------------------------------------------------------------
+// EventSource::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(EventSource)
+ bool isBlack = tmp->IsBlack();
+ if (isBlack || tmp->mWaitingForOnStopRequest) {
+ if (tmp->mListenerManager) {
+ tmp->mListenerManager->MarkForCC();
+ }
+ if (!isBlack && tmp->PreservingWrapper()) {
+ // This marks the wrapper black.
+ tmp->GetWrapper();
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(EventSource)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(EventSource)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(EventSource,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrc)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadGroup)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHttpChannel)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnicodeDecoder)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
+ DOMEventTargetHelper)
+ tmp->Close();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(EventSource)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
+
+void
+EventSource::DisconnectFromOwner()
+{
+ DOMEventTargetHelper::DisconnectFromOwner();
+ Close();
+}
+
+void
+EventSource::Close()
+{
+ if (mReadyState == CLOSED) {
+ return;
+ }
+
+ 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);
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ ResetConnection();
+
+ ClearFields();
+
+ while (mMessagesToDispatch.GetSize() != 0) {
+ delete static_cast<Message*>(mMessagesToDispatch.PopFront());
+ }
+
+ mSrc = nullptr;
+ mFrozen = false;
+
+ mUnicodeDecoder = nullptr;
+
+ mReadyState = CLOSED;
+}
+
+nsresult
+EventSource::Init(nsISupports* aOwner,
+ const nsAString& aURL,
+ bool aWithCredentials)
+{
+ if (mReadyState != CONNECTING) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
+ NS_ENSURE_STATE(sgo);
+ // XXXbz why are we checking this? This doesn't match anything in the spec.
+ nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
+ NS_ENSURE_STATE(scriptContext);
+
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
+ do_QueryInterface(aOwner);
+ NS_ENSURE_STATE(scriptPrincipal);
+ nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
+ NS_ENSURE_STATE(principal);
+
+ mPrincipal = principal;
+ mWithCredentials = aWithCredentials;
+
+ // The conditional here is historical and not necessarily sane.
+ if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
+ nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
+ &mScriptColumn);
+ mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
+ }
+
+ // Get the load group for the page. When requesting we'll add ourselves to it.
+ // This way any pending requests will be automatically aborted if the user
+ // leaves the page.
+ nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+ if (doc) {
+ mLoadGroup = doc->GetDocumentLoadGroup();
+ }
+ // 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);
+
+ // we observe when the window freezes and thaws
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ 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);
+
+ nsAutoString origin;
+ rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = srcURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOriginalURL = NS_ConvertUTF8toUTF16(spec);
+ mSrc = srcURI;
+ mOrigin = origin;
+
+ mReconnectionTime =
+ Preferences::GetInt("dom.server-events.default-reconnection-time",
+ DEFAULT_RECONNECTION_TIME_VALUE);
+
+ mUnicodeDecoder = EncodingUtils::DecoderForEncoding("UTF-8");
+
+ // the constructor should throw a SYNTAX_ERROR only if it fails resolving the
+ // url parameter, so we don't care about the InitChannelAndRequestEventSource
+ // result.
+ InitChannelAndRequestEventSource();
+
+ return NS_OK;
+}
+
+/* virtual */ JSObject*
+EventSource::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return EventSourceBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<EventSource>
+EventSource::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aURL,
+ const EventSourceInit& aEventSourceInitDict,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!ownerWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ MOZ_ASSERT(ownerWindow->IsInnerWindow());
+
+ RefPtr<EventSource> eventSource = new EventSource(ownerWindow);
+ aRv = eventSource->Init(aGlobal.GetAsSupports(), aURL,
+ aEventSourceInitDict.mWithCredentials);
+ return eventSource.forget();
+}
+
+//-----------------------------------------------------------------------------
+// EventSource::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EventSource::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (mReadyState == CLOSED) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
+ if (!GetOwner() || window != GetOwner()) {
+ return NS_OK;
+ }
+
+ DebugOnly<nsresult> rv;
+ if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
+ rv = Freeze();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Freeze() failed");
+ } else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
+ rv = Thaw();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Thaw() failed");
+ } else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EventSource::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EventSource::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *ctxt)
+{
+ 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;
+ }
+
+ rv = NS_DispatchToMainThread(NewRunnableMethod(this, &EventSource::AnnounceConnection));
+ 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
+EventSource::StreamReaderFunc(nsIInputStream *aInputStream,
+ void *aClosure,
+ const char *aFromRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ EventSource* thisObject = static_cast<EventSource*>(aClosure);
+ if (!thisObject || !aWriteCount) {
+ NS_WARNING("EventSource cannot read from stream: no aClosure or aWriteCount");
+ return NS_ERROR_FAILURE;
+ }
+
+ *aWriteCount = 0;
+
+ int32_t srcCount, outCount;
+ char16_t out[2];
+ nsresult rv;
+
+ const char *p = aFromRawSegment,
+ *end = aFromRawSegment + aCount;
+
+ do {
+ srcCount = aCount - (p - aFromRawSegment);
+ outCount = 2;
+
+ thisObject->mLastConvertionResult =
+ thisObject->mUnicodeDecoder->Convert(p, &srcCount, out, &outCount);
+ MOZ_ASSERT(thisObject->mLastConvertionResult != NS_ERROR_ILLEGAL_INPUT);
+
+ for (int32_t i = 0; i < outCount; ++i) {
+ rv = thisObject->ParseCharacter(out[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ p = p + srcCount;
+ } while (p < end &&
+ thisObject->mLastConvertionResult != NS_PARTIAL_MORE_INPUT &&
+ thisObject->mLastConvertionResult != NS_OK);
+
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+EventSource::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ NS_ENSURE_ARG_POINTER(aInputStream);
+
+ nsresult rv = CheckHealthOfRequestCallback(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t totalRead;
+ return aInputStream->ReadSegments(EventSource::StreamReaderFunc, this,
+ aCount, &totalRead);
+}
+
+NS_IMETHODIMP
+EventSource::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ mWaitingForOnStopRequest = false;
+
+ if (mReadyState == CLOSED) {
+ return NS_ERROR_ABORT;
+ }
+
+ // "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.
+
+ 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_PROXY_CONNECTION_REFUSED &&
+ aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
+ DispatchFailConnection();
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult rv = CheckHealthOfRequestCallback(aRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ClearFields();
+
+ rv = NS_DispatchToMainThread(NewRunnableMethod(this, &EventSource::ReestablishConnection));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// EventSource::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EventSource::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *aCallback)
+{
+ nsCOMPtr<nsIRequest> aOldRequest = do_QueryInterface(aOldChannel);
+ NS_PRECONDITION(aOldRequest, "Redirect from a null request?");
+
+ nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
+
+ nsCOMPtr<nsIURI> newURI;
+ rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isValidScheme =
+ (NS_SUCCEEDED(newURI->SchemeIs("http", &isValidScheme)) && isValidScheme) ||
+ (NS_SUCCEEDED(newURI->SchemeIs("https", &isValidScheme)) && isValidScheme);
+
+ rv = CheckInnerWindowCorrectness();
+ 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 and referrer policy on
+ // redirects, so we don't need to SetupReferrerPolicy().
+
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// EventSource::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+EventSource::GetInterface(const nsIID & aIID,
+ void **aResult)
+{
+ 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 = CheckInnerWindowCorrectness();
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the an auth prompter for our window so that the parenting
+ // of the dialogs works as it should when using tabs.
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (GetOwner()) {
+ window = GetOwner()->GetOuterWindow();
+ }
+
+ return wwatch->GetPrompt(window, aIID, aResult);
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+nsresult
+EventSource::GetBaseURI(nsIURI **aBaseURI)
+{
+ NS_ENSURE_ARG_POINTER(aBaseURI);
+
+ *aBaseURI = nullptr;
+
+ nsCOMPtr<nsIURI> baseURI;
+
+ // first we try from document->GetBaseURI()
+ nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+ if (doc) {
+ baseURI = doc->GetBaseURI();
+ }
+
+ // otherwise we get from the doc's principal
+ if (!baseURI) {
+ nsresult rv = mPrincipal->GetURI(getter_AddRefs(baseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ENSURE_STATE(baseURI);
+
+ baseURI.forget(aBaseURI);
+ return NS_OK;
+}
+
+void
+EventSource::SetupHttpChannel()
+{
+ mHttpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET"));
+
+ /* set the http request headers */
+
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ NS_LITERAL_CSTRING(TEXT_EVENT_STREAM), false);
+
+ // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
+
+ if (!mLastEventID.IsEmpty()) {
+ mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Last-Event-ID"),
+ NS_ConvertUTF16toUTF8(mLastEventID), false);
+ }
+}
+
+nsresult
+EventSource::SetupReferrerPolicy()
+{
+ nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+ if (doc) {
+ nsresult rv = mHttpChannel->SetReferrerWithPolicy(doc->GetDocumentURI(),
+ doc->GetReferrerPolicy());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+EventSource::InitChannelAndRequestEventSource()
+{
+ if (mReadyState == CLOSED) {
+ return NS_ERROR_ABORT;
+ }
+
+ bool isValidScheme =
+ (NS_SUCCEEDED(mSrc->SchemeIs("http", &isValidScheme)) && isValidScheme) ||
+ (NS_SUCCEEDED(mSrc->SchemeIs("https", &isValidScheme)) && isValidScheme);
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv) || !isValidScheme) {
+ DispatchFailConnection();
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsLoadFlags loadFlags;
+ loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE;
+
+ nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+
+ nsSecurityFlags securityFlags =
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+
+ if (mWithCredentials) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ // If we have the document, use it
+ if (doc) {
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ mSrc,
+ doc,
+ securityFlags,
+ nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
+ mLoadGroup, // loadGroup
+ nullptr, // aCallbacks
+ loadFlags); // aLoadFlags
+ } else {
+ // otherwise use the principal
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ mSrc,
+ mPrincipal,
+ securityFlags,
+ nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
+ mLoadGroup, // loadGroup
+ nullptr, // aCallbacks
+ loadFlags); // aLoadFlags
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHttpChannel = do_QueryInterface(channel);
+ NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
+
+ SetupHttpChannel();
+ rv = SetupReferrerPolicy();
+ 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->AsyncOpen2(this);
+ if (NS_FAILED(rv)) {
+ DispatchFailConnection();
+ return rv;
+ }
+ mWaitingForOnStopRequest = true;
+ return rv;
+}
+
+void
+EventSource::AnnounceConnection()
+{
+ if (mReadyState == CLOSED) {
+ return;
+ }
+
+ if (mReadyState != CONNECTING) {
+ NS_WARNING("Unexpected mReadyState!!!");
+ return;
+ }
+
+ // 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.
+
+ mReadyState = OPEN;
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+
+ // it doesn't bubble, and it isn't cancelable
+ event->InitEvent(NS_LITERAL_STRING("open"), false, false);
+ event->SetTrusted(true);
+
+ rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the open event!!!");
+ return;
+ }
+}
+
+nsresult
+EventSource::ResetConnection()
+{
+ if (mHttpChannel) {
+ mHttpChannel->Cancel(NS_ERROR_ABORT);
+ }
+
+ if (mUnicodeDecoder) {
+ mUnicodeDecoder->Reset();
+ }
+ mLastConvertionResult = NS_OK;
+
+ mHttpChannel = nullptr;
+ mStatus = PARSE_STATE_OFF;
+
+ mReadyState = CONNECTING;
+
+ return NS_OK;
+}
+
+void
+EventSource::ReestablishConnection()
+{
+ if (mReadyState == CLOSED) {
+ return;
+ }
+
+ nsresult rv = ResetConnection();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to reset the connection!!!");
+ return;
+ }
+
+ rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+
+ // it doesn't bubble, and it isn't cancelable
+ event->InitEvent(NS_LITERAL_STRING("error"), false, false);
+ event->SetTrusted(true);
+
+ rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event!!!");
+ return;
+ }
+
+ rv = SetReconnectionTimeout();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to set the timeout for reestablishing the connection!!!");
+ return;
+ }
+}
+
+nsresult
+EventSource::SetReconnectionTimeout()
+{
+ if (mReadyState == CLOSED) {
+ return NS_ERROR_ABORT;
+ }
+
+ // the timer will be used whenever the requests are going finished.
+ if (!mTimer) {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ NS_ENSURE_STATE(mTimer);
+ }
+
+ nsresult rv = mTimer->InitWithFuncCallback(TimerCallback, this,
+ mReconnectionTime,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+EventSource::PrintErrorOnConsole(const char *aBundleURI,
+ const char16_t *aError,
+ const char16_t **aFormatStrings,
+ uint32_t aFormatStringsLen)
+{
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ 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
+ nsXPIDLString message;
+ if (aFormatStrings) {
+ rv = strBundle->FormatStringFromName(aError, aFormatStrings,
+ aFormatStringsLen,
+ getter_Copies(message));
+ } else {
+ rv = strBundle->GetStringFromName(aError, getter_Copies(message));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = errObj->InitWithWindowID(message,
+ mScriptFile,
+ EmptyString(),
+ 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
+EventSource::ConsoleError()
+{
+ nsAutoCString targetSpec;
+ nsresult rv = mSrc->GetSpec(targetSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
+ const char16_t *formatStrings[] = { specUTF16.get() };
+
+ if (mReadyState == CONNECTING) {
+ rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ u"connectionFailure",
+ formatStrings, ArrayLength(formatStrings));
+ } else {
+ rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ u"netInterrupt",
+ formatStrings, ArrayLength(formatStrings));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+EventSource::DispatchFailConnection()
+{
+
+ return NS_DispatchToMainThread(NewRunnableMethod(this, &EventSource::FailConnection));
+}
+
+void
+EventSource::FailConnection()
+{
+ if (mReadyState == CLOSED) {
+ return;
+ }
+
+ nsresult rv = ConsoleError();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to print to the console error");
+ }
+
+ // 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.
+
+ Close(); // it sets mReadyState to CLOSED
+
+ rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+
+ // it doesn't bubble, and it isn't cancelable
+ event->InitEvent(NS_LITERAL_STRING("error"), false, false);
+ event->SetTrusted(true);
+
+ rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event!!!");
+ return;
+ }
+}
+
+// static
+void
+EventSource::TimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ RefPtr<EventSource> thisObject = static_cast<EventSource*>(aClosure);
+
+ if (thisObject->mReadyState == CLOSED) {
+ return;
+ }
+
+ NS_PRECONDITION(!thisObject->mHttpChannel,
+ "the channel hasn't been cancelled!!");
+
+ if (!thisObject->mFrozen) {
+ nsresult rv = thisObject->InitChannelAndRequestEventSource();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("thisObject->InitChannelAndRequestEventSource() failed");
+ return;
+ }
+ }
+}
+
+nsresult
+EventSource::Thaw()
+{
+ if (mReadyState == CLOSED || !mFrozen) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mHttpChannel, "the connection hasn't been closed!!!");
+
+ mFrozen = false;
+ nsresult rv;
+ if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &EventSource::DispatchAllMessageEvents);
+ NS_ENSURE_STATE(event);
+
+ mGoingToDispatchAllMessages = true;
+
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = InitChannelAndRequestEventSource();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+EventSource::Freeze()
+{
+ if (mReadyState == CLOSED || mFrozen) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mHttpChannel, "the connection hasn't been closed!!!");
+ mFrozen = true;
+ return NS_OK;
+}
+
+nsresult
+EventSource::DispatchCurrentMessageEvent()
+{
+ nsAutoPtr<Message> message(new Message());
+ *message = mCurrentMessage;
+
+ ClearFields();
+
+ if (message->mData.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // removes the trailing LF from mData
+ NS_ASSERTION(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");
+ }
+
+ if (message->mLastEventID.IsEmpty() && !mLastEventID.IsEmpty()) {
+ message->mLastEventID.Assign(mLastEventID);
+ }
+
+ size_t sizeBefore = mMessagesToDispatch.GetSize();
+ mMessagesToDispatch.Push(message.forget());
+ NS_ENSURE_TRUE(mMessagesToDispatch.GetSize() == sizeBefore + 1,
+ NS_ERROR_OUT_OF_MEMORY);
+
+
+ if (!mGoingToDispatchAllMessages) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &EventSource::DispatchAllMessageEvents);
+ NS_ENSURE_STATE(event);
+
+ mGoingToDispatchAllMessages = true;
+
+ return NS_DispatchToMainThread(event);
+ }
+
+ return NS_OK;
+}
+
+void
+EventSource::DispatchAllMessageEvents()
+{
+ if (mReadyState == CLOSED || mFrozen) {
+ return;
+ }
+
+ mGoingToDispatchAllMessages = false;
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ while (mMessagesToDispatch.GetSize() > 0) {
+ nsAutoPtr<Message>
+ message(static_cast<Message*>(mMessagesToDispatch.PopFront()));
+
+ // 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<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
+
+ event->InitMessageEvent(nullptr, message->mEventName, false, false, jsData,
+ mOrigin, message->mLastEventID, nullptr,
+ Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ rv = DispatchDOMEvent(nullptr, static_cast<Event*>(event), nullptr,
+ nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the message event!!!");
+ return;
+ }
+
+ mLastEventID.Assign(message->mLastEventID);
+ }
+}
+
+nsresult
+EventSource::ClearFields()
+{
+ // mLastEventID and mReconnectionTime must be cached
+
+ mCurrentMessage.mEventName.Truncate();
+ mCurrentMessage.mLastEventID.Truncate();
+ mCurrentMessage.mData.Truncate();
+
+ mLastFieldName.Truncate();
+ mLastFieldValue.Truncate();
+
+ return NS_OK;
+}
+
+nsresult
+EventSource::SetFieldAndClear()
+{
+ if (mLastFieldName.IsEmpty()) {
+ mLastFieldValue.Truncate();
+ return NS_OK;
+ }
+
+ char16_t first_char;
+ first_char = mLastFieldName.CharAt(0);
+
+ switch (first_char) // with no case folding performed
+ {
+ 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.Assign(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
+EventSource::CheckHealthOfRequestCallback(nsIRequest *aRequestCallback)
+{
+ // check if we have been closed or if the request has been canceled
+ // or if we have been frozen
+ if (mReadyState == CLOSED || !mHttpChannel ||
+ mFrozen || mErrorLoadOnRedirect) {
+ 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
+EventSource::ParseCharacter(char16_t aChr)
+{
+ nsresult rv;
+
+ if (mReadyState == CLOSED) {
+ 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 == BOM_CHAR) {
+ mStatus = PARSE_STATE_BOM_WAS_READ; // ignore it
+ } else 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_BOM_WAS_READ:
+ 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 {
+ mLastFieldValue += aChr;
+ }
+
+ 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 {
+ mLastFieldName += aChr;
+ mStatus = PARSE_STATE_FIELD_NAME;
+ }
+
+ break;
+ }
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/EventSource.h b/dom/base/EventSource.h
new file mode 100644
index 000000000..a1573c656
--- /dev/null
+++ b/dom/base/EventSource.h
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+#include "nsIHttpChannel.h"
+#include "nsWeakReference.h"
+#include "nsDeque.h"
+#include "nsIUnicodeDecoder.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+struct EventSourceInit;
+
+class EventSource final : public DOMEventTargetHelper
+ , public nsIObserver
+ , public nsIStreamListener
+ , public nsIChannelEventSink
+ , public nsIInterfaceRequestor
+ , public nsSupportsWeakReference
+{
+public:
+ explicit EventSource(nsPIDOMWindowInner* aOwnerWindow);
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(
+ EventSource, DOMEventTargetHelper)
+
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // nsWrapperCache
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL
+ nsPIDOMWindowInner*
+ GetParentObject() const
+ {
+ return GetOwner();
+ }
+ static already_AddRefed<EventSource>
+ Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const EventSourceInit& aEventSourceInitDict,
+ ErrorResult& aRv);
+
+ void GetUrl(nsAString& aURL) const
+ {
+ aURL = mOriginalURL;
+ }
+ bool WithCredentials() const
+ {
+ return mWithCredentials;
+ }
+
+ enum {
+ CONNECTING = 0U,
+ OPEN = 1U,
+ CLOSED = 2U
+ };
+ uint16_t ReadyState() const
+ {
+ return mReadyState;
+ }
+
+ IMPL_EVENT_HANDLER(open)
+ IMPL_EVENT_HANDLER(message)
+ IMPL_EVENT_HANDLER(error)
+ void Close();
+
+ virtual void DisconnectFromOwner() override;
+
+protected:
+ virtual ~EventSource();
+
+ MOZ_IS_CLASS_INIT
+ nsresult Init(nsISupports* aOwner,
+ const nsAString& aURL,
+ bool aWithCredentials);
+
+ nsresult GetBaseURI(nsIURI **aBaseURI);
+
+ void SetupHttpChannel();
+ nsresult SetupReferrerPolicy();
+ nsresult InitChannelAndRequestEventSource();
+ nsresult ResetConnection();
+ nsresult DispatchFailConnection();
+ nsresult SetReconnectionTimeout();
+
+ void AnnounceConnection();
+ void DispatchAllMessageEvents();
+ void ReestablishConnection();
+ void FailConnection();
+
+ nsresult Thaw();
+ nsresult Freeze();
+
+ static void TimerCallback(nsITimer *aTimer, void *aClosure);
+
+ nsresult PrintErrorOnConsole(const char *aBundleURI,
+ const char16_t *aError,
+ const char16_t **aFormatStrings,
+ uint32_t aFormatStringsLen);
+ nsresult ConsoleError();
+
+ static nsresult StreamReaderFunc(nsIInputStream *aInputStream,
+ void *aClosure,
+ const char *aFromRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount);
+ nsresult SetFieldAndClear();
+ nsresult ClearFields();
+ nsresult ResetEvent();
+ nsresult DispatchCurrentMessageEvent();
+ nsresult ParseCharacter(char16_t aChr);
+ nsresult CheckHealthOfRequestCallback(nsIRequest *aRequestCallback);
+ nsresult OnRedirectVerifyCallback(nsresult result);
+
+ nsCOMPtr<nsIURI> mSrc;
+
+ nsString mLastEventID;
+ uint32_t mReconnectionTime; // in ms
+
+ struct Message {
+ nsString mEventName;
+ nsString mLastEventID;
+ nsString mData;
+ };
+ nsDeque mMessagesToDispatch;
+ Message mCurrentMessage;
+
+ /**
+ * 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_BOM_WAS_READ |
+ * PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME
+ *
+ * PARSE_STATE_BOM_WAS_READ -> 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,
+ PARSE_STATE_BEGIN_OF_STREAM,
+ PARSE_STATE_BOM_WAS_READ,
+ PARSE_STATE_CR_CHAR,
+ PARSE_STATE_COMMENT,
+ PARSE_STATE_FIELD_NAME,
+ PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
+ PARSE_STATE_FIELD_VALUE,
+ PARSE_STATE_BEGIN_OF_LINE
+ };
+ ParserStatus mStatus;
+
+ bool mFrozen;
+ bool mErrorLoadOnRedirect;
+ bool mGoingToDispatchAllMessages;
+ bool mWithCredentials;
+ bool mWaitingForOnStopRequest;
+
+ // used while reading the input streams
+ nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
+ nsresult mLastConvertionResult;
+ nsString mLastFieldName;
+ nsString mLastFieldValue;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ uint16_t mReadyState;
+ nsString mOriginalURL;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsString mOrigin;
+
+ // 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.
+ nsString mScriptFile;
+ uint32_t mScriptLine;
+ uint32_t mScriptColumn;
+ uint64_t mInnerWindowID;
+
+private:
+ EventSource(const EventSource& x); // prevent bad usage
+ EventSource& operator=(const EventSource& x);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_EventSource_h
diff --git a/dom/base/FeedWriterEnabled.h b/dom/base/FeedWriterEnabled.h
new file mode 100644
index 000000000..1bf39eba9
--- /dev/null
+++ b/dom/base/FeedWriterEnabled.h
@@ -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 "js/TypeDecls.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+struct FeedWriterEnabled {
+ static bool IsEnabled(JSContext* cx, JSObject* aGlobal)
+ {
+ return nsContentUtils::IsSpecificAboutPage(aGlobal, "about:feeds");
+ }
+};
+
+}
diff --git a/dom/base/File.cpp b/dom/base/File.cpp
new file mode 100644
index 000000000..46b37b976
--- /dev/null
+++ b/dom/base/File.cpp
@@ -0,0 +1,1367 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/File.h"
+
+#include "MultipartBlobImpl.h"
+#include "nsCExternalHandlerService.h"
+#include "nsContentCID.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsICharsetDetector.h"
+#include "nsIConverterInputStream.h"
+#include "nsIDocument.h"
+#include "nsIFileStreams.h"
+#include "nsIInputStream.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsIMIMEService.h"
+#include "nsISeekableStream.h"
+#include "nsIUnicharInputStream.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsIRemoteBlob.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIUUIDGenerator.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsStringStream.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "nsThreadUtils.h"
+#include "nsStreamUtils.h"
+#include "SlicedInputStream.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+// XXXkhuey the input stream that we pass out of a File
+// can outlive the actual File object. Thus, we must
+// ensure that the buffer underlying the stream we get
+// from NS_NewByteInputStream is held alive as long as the
+// stream is. We do that by passing back this class instead.
+class DataOwnerAdapter final : public nsIInputStream,
+ public nsISeekableStream,
+ public nsIIPCSerializableInputStream
+{
+ typedef BlobImplMemory::DataOwner DataOwner;
+public:
+ static nsresult Create(DataOwner* aDataOwner,
+ uint32_t aStart,
+ uint32_t aLength,
+ nsIInputStream** _retval);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // These are mandatory.
+ NS_FORWARD_NSIINPUTSTREAM(mStream->)
+ NS_FORWARD_NSISEEKABLESTREAM(mSeekableStream->)
+
+ // This is optional. We use a conditional QI to keep it from being called
+ // if the underlying stream doesn't support it.
+ NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(mSerializableInputStream->)
+
+private:
+ ~DataOwnerAdapter() {}
+
+ DataOwnerAdapter(DataOwner* aDataOwner,
+ nsIInputStream* aStream)
+ : mDataOwner(aDataOwner), mStream(aStream),
+ mSeekableStream(do_QueryInterface(aStream)),
+ mSerializableInputStream(do_QueryInterface(aStream))
+ {
+ NS_ASSERTION(mSeekableStream, "Somebody gave us the wrong stream!");
+ }
+
+ RefPtr<DataOwner> mDataOwner;
+ nsCOMPtr<nsIInputStream> mStream;
+ nsCOMPtr<nsISeekableStream> mSeekableStream;
+ nsCOMPtr<nsIIPCSerializableInputStream> mSerializableInputStream;
+};
+
+NS_IMPL_ADDREF(DataOwnerAdapter)
+NS_IMPL_RELEASE(DataOwnerAdapter)
+
+NS_INTERFACE_MAP_BEGIN(DataOwnerAdapter)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
+ mSerializableInputStream)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+nsresult DataOwnerAdapter::Create(DataOwner* aDataOwner,
+ uint32_t aStart,
+ uint32_t aLength,
+ nsIInputStream** _retval)
+{
+ nsresult rv;
+ NS_ASSERTION(aDataOwner, "Uh ...");
+
+ nsCOMPtr<nsIInputStream> stream;
+
+ rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ static_cast<const char*>(aDataOwner->mData) +
+ aStart,
+ (int32_t)aLength,
+ NS_ASSIGNMENT_DEPEND);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*_retval = new DataOwnerAdapter(aDataOwner, stream));
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// mozilla::dom::Blob implementation
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Blob)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Blob)
+ 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(Blob)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Blob)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Blob)
+ // This class should not receive any nsIRemoteBlob QI!
+ MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsIRemoteBlob)));
+
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMBlob)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMBlob)
+ NS_INTERFACE_MAP_ENTRY(nsIXHRSendable)
+ NS_INTERFACE_MAP_ENTRY(nsIMutable)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Blob)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Blob)
+
+/* static */ Blob*
+Blob::Create(nsISupports* aParent, BlobImpl* aImpl)
+{
+ MOZ_ASSERT(aImpl);
+
+ return aImpl->IsFile() ? new File(aParent, aImpl)
+ : new Blob(aParent, aImpl);
+}
+
+/* static */ already_AddRefed<Blob>
+Blob::Create(nsISupports* aParent, const nsAString& aContentType,
+ uint64_t aLength)
+{
+ RefPtr<Blob> blob = Blob::Create(aParent,
+ new BlobImplBase(aContentType, aLength));
+ MOZ_ASSERT(!blob->mImpl->IsFile());
+ return blob.forget();
+}
+
+/* static */ already_AddRefed<Blob>
+Blob::Create(nsISupports* aParent, const nsAString& aContentType,
+ uint64_t aStart, uint64_t aLength)
+{
+ RefPtr<Blob> blob = Blob::Create(aParent,
+ new BlobImplBase(aContentType, aStart, aLength));
+ MOZ_ASSERT(!blob->mImpl->IsFile());
+ return blob.forget();
+}
+
+/* static */ already_AddRefed<Blob>
+Blob::CreateStringBlob(nsISupports* aParent, const nsACString& aData,
+ const nsAString& aContentType)
+{
+ RefPtr<BlobImpl> blobImpl = BlobImplString::Create(aData, aContentType);
+ RefPtr<Blob> blob = Blob::Create(aParent, blobImpl);
+ MOZ_ASSERT(!blob->mImpl->IsFile());
+ return blob.forget();
+}
+
+/* static */ already_AddRefed<Blob>
+Blob::CreateMemoryBlob(nsISupports* aParent, void* aMemoryBuffer,
+ uint64_t aLength, const nsAString& aContentType)
+{
+ RefPtr<Blob> blob = Blob::Create(aParent,
+ new BlobImplMemory(aMemoryBuffer, aLength, aContentType));
+ MOZ_ASSERT(!blob->mImpl->IsFile());
+ return blob.forget();
+}
+
+/* static */ already_AddRefed<Blob>
+Blob::CreateTemporaryBlob(nsISupports* aParent, PRFileDesc* aFD,
+ uint64_t aStartPos, uint64_t aLength,
+ const nsAString& aContentType)
+{
+ RefPtr<Blob> blob = Blob::Create(aParent,
+ new BlobImplTemporaryBlob(aFD, aStartPos, aLength, aContentType));
+ MOZ_ASSERT(!blob->mImpl->IsFile());
+ return blob.forget();
+}
+
+Blob::Blob(nsISupports* aParent, BlobImpl* aImpl)
+ : mImpl(aImpl)
+ , mParent(aParent)
+{
+ MOZ_ASSERT(mImpl);
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aParent);
+ if (win) {
+ MOZ_ASSERT(win->IsInnerWindow());
+ }
+ }
+#endif
+}
+
+bool
+Blob::IsFile() const
+{
+ return mImpl->IsFile();
+}
+
+const nsTArray<RefPtr<BlobImpl>>*
+Blob::GetSubBlobImpls() const
+{
+ return mImpl->GetSubBlobImpls();
+}
+
+already_AddRefed<File>
+Blob::ToFile()
+{
+ if (!mImpl->IsFile()) {
+ return nullptr;
+ }
+
+ RefPtr<File> file;
+ if (HasFileInterface()) {
+ file = static_cast<File*>(this);
+ } else {
+ file = new File(mParent, mImpl);
+ }
+
+ return file.forget();
+}
+
+already_AddRefed<File>
+Blob::ToFile(const nsAString& aName, ErrorResult& aRv) const
+{
+ AutoTArray<RefPtr<BlobImpl>, 1> blobImpls({mImpl});
+
+ nsAutoString contentType;
+ mImpl->GetType(contentType);
+
+ RefPtr<MultipartBlobImpl> impl =
+ MultipartBlobImpl::Create(Move(blobImpls), aName, contentType, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<File> file = new File(mParent, impl);
+ return file.forget();
+}
+
+already_AddRefed<Blob>
+Blob::CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ RefPtr<BlobImpl> impl = mImpl->CreateSlice(aStart, aLength,
+ aContentType, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<Blob> blob = Blob::Create(mParent, impl);
+ return blob.forget();
+}
+
+uint64_t
+Blob::GetSize(ErrorResult& aRv)
+{
+ return mImpl->GetSize(aRv);
+}
+
+void
+Blob::GetType(nsAString &aType)
+{
+ mImpl->GetType(aType);
+}
+
+already_AddRefed<Blob>
+Blob::Slice(const Optional<int64_t>& aStart,
+ const Optional<int64_t>& aEnd,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ RefPtr<BlobImpl> impl =
+ mImpl->Slice(aStart, aEnd, aContentType, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<Blob> blob = Blob::Create(mParent, impl);
+ return blob.forget();
+}
+
+NS_IMETHODIMP
+Blob::GetSendInfo(nsIInputStream** aBody,
+ uint64_t* aContentLength,
+ nsACString& aContentType,
+ nsACString& aCharset)
+{
+ return mImpl->GetSendInfo(aBody, aContentLength, aContentType, aCharset);
+}
+
+NS_IMETHODIMP
+Blob::GetMutable(bool* aMutable)
+{
+ return mImpl->GetMutable(aMutable);
+}
+
+NS_IMETHODIMP
+Blob::SetMutable(bool aMutable)
+{
+ return mImpl->SetMutable(aMutable);
+}
+
+JSObject*
+Blob::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return BlobBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<Blob>
+Blob::Constructor(const GlobalObject& aGlobal,
+ const Optional<Sequence<BlobPart>>& aData,
+ const BlobPropertyBag& aBag,
+ ErrorResult& aRv)
+{
+ RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl();
+
+ if (aData.WasPassed()) {
+ impl->InitializeBlob(aGlobal.Context(), aData.Value(), aBag.mType,
+ aBag.mEndings == EndingTypes::Native, aRv);
+ } else {
+ impl->InitializeBlob(aRv);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!impl->IsFile());
+
+ RefPtr<Blob> blob = Blob::Create(aGlobal.GetAsSupports(), impl);
+ return blob.forget();
+}
+
+int64_t
+Blob::GetFileId()
+{
+ return mImpl->GetFileId();
+}
+
+bool
+Blob::IsMemoryFile() const
+{
+ return mImpl->IsMemoryFile();
+}
+
+void
+Blob::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv)
+{
+ mImpl->GetInternalStream(aStream, aRv);
+}
+
+////////////////////////////////////////////////////////////////////////////
+// mozilla::dom::File implementation
+
+File::File(nsISupports* aParent, BlobImpl* aImpl)
+ : Blob(aParent, aImpl)
+{
+ MOZ_ASSERT(aImpl->IsFile());
+}
+
+/* static */ File*
+File::Create(nsISupports* aParent, BlobImpl* aImpl)
+{
+ MOZ_ASSERT(aImpl);
+ MOZ_ASSERT(aImpl->IsFile());
+
+ return new File(aParent, aImpl);
+}
+
+/* static */ already_AddRefed<File>
+File::Create(nsISupports* aParent, const nsAString& aName,
+ const nsAString& aContentType, uint64_t aLength,
+ int64_t aLastModifiedDate)
+{
+ RefPtr<File> file = new File(aParent,
+ new BlobImplBase(aName, aContentType, aLength, aLastModifiedDate));
+ return file.forget();
+}
+
+/* static */ already_AddRefed<File>
+File::CreateMemoryFile(nsISupports* aParent, void* aMemoryBuffer,
+ uint64_t aLength, const nsAString& aName,
+ const nsAString& aContentType,
+ int64_t aLastModifiedDate)
+{
+ RefPtr<File> file = new File(aParent,
+ new BlobImplMemory(aMemoryBuffer, aLength, aName,
+ aContentType, aLastModifiedDate));
+ return file.forget();
+}
+
+/* static */ already_AddRefed<File>
+File::CreateFromFile(nsISupports* aParent, nsIFile* aFile, bool aTemporary)
+{
+ RefPtr<File> file = new File(aParent, new BlobImplFile(aFile, aTemporary));
+ return file.forget();
+}
+
+/* static */ already_AddRefed<File>
+File::CreateFromFile(nsISupports* aParent, nsIFile* aFile,
+ const nsAString& aName, const nsAString& aContentType)
+{
+ RefPtr<File> file = new File(aParent,
+ new BlobImplFile(aFile, aName, aContentType));
+ return file.forget();
+}
+
+JSObject*
+File::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FileBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+File::GetName(nsAString& aFileName) const
+{
+ mImpl->GetName(aFileName);
+}
+
+void
+File::GetRelativePath(nsAString& aPath) const
+{
+ aPath.Truncate();
+
+ nsAutoString path;
+ mImpl->GetDOMPath(path);
+
+ // WebkitRelativePath doesn't start with '/'
+ if (!path.IsEmpty()) {
+ MOZ_ASSERT(path[0] == FILESYSTEM_DOM_PATH_SEPARATOR_CHAR);
+ aPath.Assign(Substring(path, 1));
+ }
+}
+
+Date
+File::GetLastModifiedDate(ErrorResult& aRv)
+{
+ int64_t value = GetLastModified(aRv);
+ if (aRv.Failed()) {
+ return Date();
+ }
+
+ return Date(JS::TimeClip(value));
+}
+
+int64_t
+File::GetLastModified(ErrorResult& aRv)
+{
+ return mImpl->GetLastModified(aRv);
+}
+
+void
+File::GetMozFullPath(nsAString& aFilename, ErrorResult& aRv) const
+{
+ mImpl->GetMozFullPath(aFilename, aRv);
+}
+
+void
+File::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
+{
+ mImpl->GetMozFullPathInternal(aFileName, aRv);
+}
+
+// Makes sure that aStart and aEnd is less then or equal to aSize and greater
+// than 0
+static void
+ParseSize(int64_t aSize, int64_t& aStart, int64_t& aEnd)
+{
+ CheckedInt64 newStartOffset = aStart;
+ if (aStart < -aSize) {
+ newStartOffset = 0;
+ }
+ else if (aStart < 0) {
+ newStartOffset += aSize;
+ }
+ else if (aStart > aSize) {
+ newStartOffset = aSize;
+ }
+
+ CheckedInt64 newEndOffset = aEnd;
+ if (aEnd < -aSize) {
+ newEndOffset = 0;
+ }
+ else if (aEnd < 0) {
+ newEndOffset += aSize;
+ }
+ else if (aEnd > aSize) {
+ newEndOffset = aSize;
+ }
+
+ if (!newStartOffset.isValid() || !newEndOffset.isValid() ||
+ newStartOffset.value() >= newEndOffset.value()) {
+ aStart = aEnd = 0;
+ }
+ else {
+ aStart = newStartOffset.value();
+ aEnd = newEndOffset.value();
+ }
+}
+
+/* static */ already_AddRefed<File>
+File::Constructor(const GlobalObject& aGlobal,
+ const Sequence<BlobPart>& aData,
+ const nsAString& aName,
+ const FilePropertyBag& aBag,
+ ErrorResult& aRv)
+{
+ RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(aName);
+
+ impl->InitializeBlob(aGlobal.Context(), aData, aBag.mType, false, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(impl->IsFile());
+
+ if (aBag.mLastModified.WasPassed()) {
+ impl->SetLastModified(aBag.mLastModified.Value());
+ }
+
+ RefPtr<File> file = new File(aGlobal.GetAsSupports(), impl);
+ return file.forget();
+}
+
+/* static */ already_AddRefed<File>
+File::CreateFromNsIFile(const GlobalObject& aGlobal,
+ nsIFile* aData,
+ const ChromeFilePropertyBag& aBag,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!nsContentUtils::IsCallerChrome()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
+
+ RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(EmptyString());
+ impl->InitializeChromeFile(window, aData, aBag, true, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(impl->IsFile());
+
+ if (aBag.mLastModified.WasPassed()) {
+ impl->SetLastModified(aBag.mLastModified.Value());
+ }
+
+ RefPtr<File> domFile = new File(aGlobal.GetAsSupports(), impl);
+ return domFile.forget();
+}
+
+/* static */ already_AddRefed<File>
+File::CreateFromFileName(const GlobalObject& aGlobal,
+ const nsAString& aData,
+ const ChromeFilePropertyBag& aBag,
+ ErrorResult& aRv)
+{
+ if (!nsContentUtils::ThreadsafeIsCallerChrome()) {
+ aRv.ThrowTypeError<MSG_MISSING_ARGUMENTS>(NS_LITERAL_STRING("File"));
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
+
+ RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(EmptyString());
+ impl->InitializeChromeFile(window, aData, aBag, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(impl->IsFile());
+
+ if (aBag.mLastModified.WasPassed()) {
+ impl->SetLastModified(aBag.mLastModified.Value());
+ }
+
+ RefPtr<File> domFile = new File(aGlobal.GetAsSupports(), impl);
+ return domFile.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////
+// mozilla::dom::BlobImpl implementation
+
+already_AddRefed<BlobImpl>
+BlobImpl::Slice(const Optional<int64_t>& aStart,
+ const Optional<int64_t>& aEnd,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ // Truncate aStart and aEnd so that we stay within this file.
+ uint64_t thisLength = GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ int64_t start = aStart.WasPassed() ? aStart.Value() : 0;
+ int64_t end = aEnd.WasPassed() ? aEnd.Value() : (int64_t)thisLength;
+
+ ParseSize((int64_t)thisLength, start, end);
+
+ return CreateSlice((uint64_t)start, (uint64_t)(end - start),
+ aContentType, aRv);
+}
+
+////////////////////////////////////////////////////////////////////////////
+// BlobImpl implementation
+
+NS_IMPL_ISUPPORTS(BlobImpl, BlobImpl)
+
+////////////////////////////////////////////////////////////////////////////
+// BlobImplFile implementation
+
+NS_IMPL_ISUPPORTS_INHERITED0(BlobImplFile, BlobImpl)
+
+void
+BlobImplBase::GetName(nsAString& aName) const
+{
+ NS_ASSERTION(mIsFile, "Should only be called on files");
+ aName = mName;
+}
+
+void
+BlobImplBase::GetDOMPath(nsAString& aPath) const
+{
+ NS_ASSERTION(mIsFile, "Should only be called on files");
+ aPath = mPath;
+}
+
+void
+BlobImplBase::SetDOMPath(const nsAString& aPath)
+{
+ NS_ASSERTION(mIsFile, "Should only be called on files");
+ mPath = aPath;
+}
+
+void
+BlobImplBase::GetMozFullPath(nsAString& aFileName, ErrorResult& aRv) const
+{
+ NS_ASSERTION(mIsFile, "Should only be called on files");
+
+ aFileName.Truncate();
+
+ if (NS_IsMainThread()) {
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ GetMozFullPathInternal(aFileName, aRv);
+ }
+
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ if (workerPrivate->UsesSystemPrincipal()) {
+ GetMozFullPathInternal(aFileName, aRv);
+ }
+}
+
+void
+BlobImplBase::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
+{
+ if (!mIsFile) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aFileName.Truncate();
+}
+
+void
+BlobImplBase::GetType(nsAString& aType)
+{
+ aType = mContentType;
+}
+
+int64_t
+BlobImplBase::GetLastModified(ErrorResult& aRv)
+{
+ NS_ASSERTION(mIsFile, "Should only be called on files");
+ if (IsDateUnknown()) {
+ mLastModificationDate = PR_Now();
+ }
+
+ return mLastModificationDate / PR_USEC_PER_MSEC;
+}
+
+void
+BlobImplBase::SetLastModified(int64_t aLastModified)
+{
+ mLastModificationDate = aLastModified * PR_USEC_PER_MSEC;
+}
+
+int64_t
+BlobImplBase::GetFileId()
+{
+ return -1;
+}
+
+nsresult
+BlobImplBase::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset)
+{
+ MOZ_ASSERT(aContentLength);
+
+ ErrorResult rv;
+
+ nsCOMPtr<nsIInputStream> stream;
+ GetInternalStream(getter_AddRefs(stream), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ *aContentLength = GetSize(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ nsAutoString contentType;
+ GetType(contentType);
+
+ if (contentType.IsEmpty()) {
+ aContentType.SetIsVoid(true);
+ } else {
+ CopyUTF16toUTF8(contentType, aContentType);
+ }
+
+ aCharset.Truncate();
+
+ stream.forget(aBody);
+ return NS_OK;
+}
+
+nsresult
+BlobImplBase::GetMutable(bool* aMutable) const
+{
+ *aMutable = !mImmutable;
+ return NS_OK;
+}
+
+nsresult
+BlobImplBase::SetMutable(bool aMutable)
+{
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_ARG(!mImmutable || !aMutable);
+
+ if (!mImmutable && !aMutable) {
+ // Force the content type and size to be cached
+ nsAutoString dummyString;
+ GetType(dummyString);
+
+ ErrorResult error;
+ GetSize(error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+ }
+
+ mImmutable = !aMutable;
+ return rv;
+}
+
+/* static */ uint64_t
+BlobImplBase::NextSerialNumber()
+{
+ static Atomic<uint64_t> nextSerialNumber;
+ return nextSerialNumber++;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// BlobImplFile implementation
+
+already_AddRefed<BlobImpl>
+BlobImplFile::CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ RefPtr<BlobImpl> impl =
+ new BlobImplFile(this, aStart, aLength, aContentType);
+ return impl.forget();
+}
+
+void
+BlobImplFile::GetMozFullPathInternal(nsAString& aFilename, ErrorResult& aRv) const
+{
+ NS_ASSERTION(mIsFile, "Should only be called on files");
+ aRv = mFile->GetPath(aFilename);
+}
+
+uint64_t
+BlobImplFile::GetSize(ErrorResult& aRv)
+{
+ if (BlobImplBase::IsSizeUnknown()) {
+ NS_ASSERTION(mWholeFile,
+ "Should only use lazy size when using the whole file");
+ int64_t fileSize;
+ aRv = mFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return 0;
+ }
+
+ if (fileSize < 0) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+
+ mLength = fileSize;
+ }
+
+ return mLength;
+}
+
+namespace {
+
+class GetTypeRunnable final : public WorkerMainThreadRunnable
+{
+public:
+ GetTypeRunnable(WorkerPrivate* aWorkerPrivate,
+ BlobImpl* aBlobImpl)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("BlobImplFile :: GetType"))
+ , mBlobImpl(aBlobImpl)
+ {
+ MOZ_ASSERT(aBlobImpl);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool
+ MainThreadRun() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoString type;
+ mBlobImpl->GetType(type);
+ return true;
+ }
+
+private:
+ ~GetTypeRunnable()
+ {}
+
+ RefPtr<BlobImpl> mBlobImpl;
+};
+
+} // anonymous namespace
+
+void
+BlobImplFile::GetType(nsAString& aType)
+{
+ aType.Truncate();
+
+ if (mContentType.IsVoid()) {
+ NS_ASSERTION(mWholeFile,
+ "Should only use lazy ContentType when using the whole file");
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (!workerPrivate) {
+ // I have no idea in which thread this method is called. We cannot
+ // return any valid value.
+ return;
+ }
+
+ RefPtr<GetTypeRunnable> runnable =
+ new GetTypeRunnable(workerPrivate, this);
+
+ ErrorResult rv;
+ runnable->Dispatch(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEService> mimeService =
+ do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsAutoCString mimeType;
+ rv = mimeService->GetTypeFromFile(mFile, mimeType);
+ if (NS_FAILED(rv)) {
+ mimeType.Truncate();
+ }
+
+ AppendUTF8toUTF16(mimeType, mContentType);
+ mContentType.SetIsVoid(false);
+ }
+
+ aType = mContentType;
+}
+
+int64_t
+BlobImplFile::GetLastModified(ErrorResult& aRv)
+{
+ NS_ASSERTION(mIsFile, "Should only be called on files");
+ if (BlobImplBase::IsDateUnknown()) {
+ PRTime msecs;
+ aRv = mFile->GetLastModifiedTime(&msecs);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return 0;
+ }
+
+ mLastModificationDate = msecs;
+ }
+
+ return mLastModificationDate;
+}
+
+void
+BlobImplFile::SetLastModified(int64_t aLastModified)
+{
+ MOZ_CRASH("SetLastModified of a real file is not allowed!");
+}
+
+const uint32_t sFileStreamFlags =
+ nsIFileInputStream::CLOSE_ON_EOF |
+ nsIFileInputStream::REOPEN_ON_REWIND |
+ nsIFileInputStream::DEFER_OPEN |
+ nsIFileInputStream::SHARE_DELETE;
+
+void
+BlobImplFile::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv)
+{
+ if (mWholeFile) {
+ aRv = NS_NewLocalFileInputStream(aStream, mFile, -1, -1, sFileStreamFlags);
+ return;
+ }
+
+ aRv = NS_NewPartialLocalFileInputStream(aStream, mFile, mStart, mLength,
+ -1, -1, sFileStreamFlags);
+}
+
+bool
+BlobImplFile::IsDirectory() const
+{
+ bool isDirectory = false;
+ if (mFile) {
+ mFile->IsDirectory(&isDirectory);
+ }
+ return isDirectory;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// EmptyBlobImpl implementation
+
+NS_IMPL_ISUPPORTS_INHERITED0(EmptyBlobImpl, BlobImpl)
+
+already_AddRefed<BlobImpl>
+EmptyBlobImpl::CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(!aStart && !aLength);
+ RefPtr<BlobImpl> impl = new EmptyBlobImpl(aContentType);
+
+ DebugOnly<bool> isMutable;
+ MOZ_ASSERT(NS_SUCCEEDED(impl->GetMutable(&isMutable)));
+ MOZ_ASSERT(!isMutable);
+
+ return impl.forget();
+}
+
+void
+EmptyBlobImpl::GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv)
+{
+ if (NS_WARN_IF(!aStream)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsresult rv = NS_NewCStringInputStream(aStream, EmptyCString());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+// BlobImplString implementation
+
+NS_IMPL_ISUPPORTS_INHERITED(BlobImplString, BlobImpl, nsIMemoryReporter)
+
+/* static */ already_AddRefed<BlobImplString>
+BlobImplString::Create(const nsACString& aData, const nsAString& aContentType)
+{
+ RefPtr<BlobImplString> blobImpl = new BlobImplString(aData, aContentType);
+ RegisterWeakMemoryReporter(blobImpl);
+ return blobImpl.forget();
+}
+
+BlobImplString::BlobImplString(const nsACString& aData,
+ const nsAString& aContentType)
+ : BlobImplBase(aContentType, aData.Length())
+ , mData(aData)
+{
+}
+
+BlobImplString::~BlobImplString()
+{
+ UnregisterWeakMemoryReporter(this);
+}
+
+already_AddRefed<BlobImpl>
+BlobImplString::CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ RefPtr<BlobImpl> impl =
+ new BlobImplString(Substring(mData, aStart, aLength),
+ aContentType);
+ return impl.forget();
+}
+
+void
+BlobImplString::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv)
+{
+ aRv = NS_NewCStringInputStream(aStream, mData);
+}
+
+NS_IMETHODIMP
+BlobImplString::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/dom/memory-file-data/string", KIND_HEAP, UNITS_BYTES,
+ mData.SizeOfExcludingThisIfUnshared(MallocSizeOf),
+ "Memory used to back a File/Blob based on a string.");
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// BlobImplMemory implementation
+
+NS_IMPL_ISUPPORTS_INHERITED0(BlobImplMemory, BlobImpl)
+
+already_AddRefed<BlobImpl>
+BlobImplMemory::CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ RefPtr<BlobImpl> impl =
+ new BlobImplMemory(this, aStart, aLength, aContentType);
+ return impl.forget();
+}
+
+void
+BlobImplMemory::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv)
+{
+ if (mLength > INT32_MAX) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aRv = DataOwnerAdapter::Create(mDataOwner, mStart, mLength, aStream);
+}
+
+/* static */ StaticMutex
+BlobImplMemory::DataOwner::sDataOwnerMutex;
+
+/* static */ StaticAutoPtr<LinkedList<BlobImplMemory::DataOwner>>
+BlobImplMemory::DataOwner::sDataOwners;
+
+/* static */ bool
+BlobImplMemory::DataOwner::sMemoryReporterRegistered = false;
+
+MOZ_DEFINE_MALLOC_SIZE_OF(MemoryFileDataOwnerMallocSizeOf)
+
+class BlobImplMemoryDataOwnerMemoryReporter final
+ : public nsIMemoryReporter
+{
+ ~BlobImplMemoryDataOwnerMemoryReporter() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override
+ {
+ typedef BlobImplMemory::DataOwner DataOwner;
+
+ StaticMutexAutoLock lock(DataOwner::sDataOwnerMutex);
+
+ if (!DataOwner::sDataOwners) {
+ return NS_OK;
+ }
+
+ const size_t LARGE_OBJECT_MIN_SIZE = 8 * 1024;
+ size_t smallObjectsTotal = 0;
+
+ for (DataOwner *owner = DataOwner::sDataOwners->getFirst();
+ owner; owner = owner->getNext()) {
+
+ size_t size = MemoryFileDataOwnerMallocSizeOf(owner->mData);
+
+ if (size < LARGE_OBJECT_MIN_SIZE) {
+ smallObjectsTotal += size;
+ } else {
+ SHA1Sum sha1;
+ sha1.update(owner->mData, owner->mLength);
+ uint8_t digest[SHA1Sum::kHashSize]; // SHA1 digests are 20 bytes long.
+ sha1.finish(digest);
+
+ nsAutoCString digestString;
+ for (size_t i = 0; i < sizeof(digest); i++) {
+ digestString.AppendPrintf("%02x", digest[i]);
+ }
+
+ aHandleReport->Callback(
+ /* process */ NS_LITERAL_CSTRING(""),
+ nsPrintfCString(
+ "explicit/dom/memory-file-data/large/file(length=%llu, sha1=%s)",
+ owner->mLength, aAnonymize ? "<anonymized>" : digestString.get()),
+ KIND_HEAP, UNITS_BYTES, size,
+ nsPrintfCString(
+ "Memory used to back a memory file of length %llu bytes. The file "
+ "has a sha1 of %s.\n\n"
+ "Note that the allocator may round up a memory file's length -- "
+ "that is, an N-byte memory file may take up more than N bytes of "
+ "memory.",
+ owner->mLength, digestString.get()),
+ aData);
+ }
+ }
+
+ if (smallObjectsTotal > 0) {
+ aHandleReport->Callback(
+ /* process */ NS_LITERAL_CSTRING(""),
+ NS_LITERAL_CSTRING("explicit/dom/memory-file-data/small"),
+ KIND_HEAP, UNITS_BYTES, smallObjectsTotal,
+ nsPrintfCString(
+ "Memory used to back small memory files (i.e. those taking up less "
+ "than %zu bytes of memory each).\n\n"
+ "Note that the allocator may round up a memory file's length -- "
+ "that is, an N-byte memory file may take up more than N bytes of "
+ "memory.", LARGE_OBJECT_MIN_SIZE),
+ aData);
+ }
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(BlobImplMemoryDataOwnerMemoryReporter, nsIMemoryReporter)
+
+/* static */ void
+BlobImplMemory::DataOwner::EnsureMemoryReporterRegistered()
+{
+ sDataOwnerMutex.AssertCurrentThreadOwns();
+ if (sMemoryReporterRegistered) {
+ return;
+ }
+
+ RegisterStrongMemoryReporter(new BlobImplMemoryDataOwnerMemoryReporter());
+
+ sMemoryReporterRegistered = true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// BlobImplTemporaryBlob implementation
+
+NS_IMPL_ISUPPORTS_INHERITED0(BlobImplTemporaryBlob, BlobImpl)
+
+already_AddRefed<BlobImpl>
+BlobImplTemporaryBlob::CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ if (aStart + aLength > mLength) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ RefPtr<BlobImpl> impl =
+ new BlobImplTemporaryBlob(this, aStart + mStartPos,
+ aLength, aContentType);
+ return impl.forget();
+}
+
+void
+BlobImplTemporaryBlob::GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIInputStream> stream =
+ new nsTemporaryFileInputStream(mFileDescOwner, mStartPos, mStartPos + mLength);
+ stream.forget(aStream);
+}
+
+////////////////////////////////////////////////////////////////////////////
+// BlobImplStream implementation
+
+NS_IMPL_ISUPPORTS_INHERITED(BlobImplStream, BlobImpl, nsIMemoryReporter)
+
+/* static */ already_AddRefed<BlobImplStream>
+BlobImplStream::Create(nsIInputStream* aInputStream,
+ const nsAString& aContentType,
+ uint64_t aLength)
+{
+ RefPtr<BlobImplStream> blobImplStream =
+ new BlobImplStream(aInputStream, aContentType, aLength);
+ blobImplStream->MaybeRegisterMemoryReporter();
+ return blobImplStream.forget();
+}
+
+/* static */ already_AddRefed<BlobImplStream>
+BlobImplStream::Create(nsIInputStream* aInputStream,
+ const nsAString& aName,
+ const nsAString& aContentType,
+ int64_t aLastModifiedDate,
+ uint64_t aLength)
+{
+ RefPtr<BlobImplStream> blobImplStream =
+ new BlobImplStream(aInputStream, aName, aContentType, aLastModifiedDate,
+ aLength);
+ blobImplStream->MaybeRegisterMemoryReporter();
+ return blobImplStream.forget();
+}
+
+BlobImplStream::BlobImplStream(nsIInputStream* aInputStream,
+ const nsAString& aContentType,
+ uint64_t aLength)
+ : BlobImplBase(aContentType, aLength)
+ , mInputStream(aInputStream)
+{
+ mImmutable = true;
+}
+
+BlobImplStream::BlobImplStream(BlobImplStream* aOther,
+ const nsAString& aContentType,
+ uint64_t aStart, uint64_t aLength)
+ : BlobImplBase(aContentType, aOther->mStart + aStart, aLength)
+ , mInputStream(new SlicedInputStream(aOther->mInputStream, aStart, aLength))
+{
+ mImmutable = true;
+}
+
+BlobImplStream::BlobImplStream(nsIInputStream* aInputStream,
+ const nsAString& aName,
+ const nsAString& aContentType,
+ int64_t aLastModifiedDate,
+ uint64_t aLength)
+ : BlobImplBase(aName, aContentType, aLength, aLastModifiedDate)
+ , mInputStream(aInputStream)
+{
+ mImmutable = true;
+}
+
+BlobImplStream::~BlobImplStream()
+{
+ UnregisterWeakMemoryReporter(this);
+}
+
+void
+BlobImplStream::GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsCOMPtr<nsIInputStream> replacementStream;
+
+ aRv = NS_CloneInputStream(mInputStream, getter_AddRefs(clonedStream),
+ getter_AddRefs(replacementStream));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (replacementStream) {
+ mInputStream = replacementStream.forget();
+ }
+
+ clonedStream.forget(aStream);
+}
+
+already_AddRefed<BlobImpl>
+BlobImplStream::CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv)
+{
+ if (!aLength) {
+ RefPtr<BlobImpl> impl = new EmptyBlobImpl(aContentType);
+ return impl.forget();
+ }
+
+ RefPtr<BlobImpl> impl =
+ new BlobImplStream(this, aContentType, aStart, aLength);
+ return impl.forget();
+}
+
+void
+BlobImplStream::MaybeRegisterMemoryReporter()
+{
+ // We report only stringInputStream.
+ nsCOMPtr<nsIStringInputStream> stringInputStream =
+ do_QueryInterface(mInputStream);
+ if (!stringInputStream) {
+ return;
+ }
+
+ RegisterWeakMemoryReporter(this);
+}
+
+NS_IMETHODIMP
+BlobImplStream::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ nsCOMPtr<nsIStringInputStream> stringInputStream =
+ do_QueryInterface(mInputStream);
+ if (!stringInputStream) {
+ return NS_OK;
+ }
+
+ MOZ_COLLECT_REPORT(
+ "explicit/dom/memory-file-data/stream", KIND_HEAP, UNITS_BYTES,
+ stringInputStream->SizeOfIncludingThis(MallocSizeOf),
+ "Memory used to back a File/Blob based on an input stream.");
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/File.h b/dom/base/File.h
new file mode 100644
index 000000000..aed1f68c3
--- /dev/null
+++ b/dom/base/File.h
@@ -0,0 +1,878 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_File_h
+#define mozilla_dom_File_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/GuardObjects.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Date.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMBlob.h"
+#include "nsIFile.h"
+#include "nsIMemoryReporter.h"
+#include "nsIMutable.h"
+#include "nsIXMLHttpRequest.h"
+#include "nsString.h"
+#include "nsTemporaryFileInputStream.h"
+#include "nsWrapperCache.h"
+#include "nsWeakReference.h"
+
+class nsIFile;
+class nsIInputStream;
+
+#define BLOBIMPL_IID \
+ { 0xbccb3275, 0x6778, 0x4ac5, \
+ { 0xaf, 0x03, 0x90, 0xed, 0x37, 0xad, 0xdf, 0x5d } }
+
+namespace mozilla {
+namespace dom {
+
+struct BlobPropertyBag;
+struct ChromeFilePropertyBag;
+struct FilePropertyBag;
+class BlobImpl;
+class File;
+class OwningArrayBufferOrArrayBufferViewOrBlobOrUSVString;
+
+class Blob : public nsIDOMBlob
+ , public nsIXHRSendable
+ , public nsIMutable
+ , public nsSupportsWeakReference
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_NSIDOMBLOB
+ NS_DECL_NSIXHRSENDABLE
+ NS_DECL_NSIMUTABLE
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Blob, nsIDOMBlob)
+
+ typedef OwningArrayBufferOrArrayBufferViewOrBlobOrUSVString BlobPart;
+
+ // This creates a Blob or a File based on the type of BlobImpl.
+ static Blob*
+ Create(nsISupports* aParent, BlobImpl* aImpl);
+
+ static already_AddRefed<Blob>
+ Create(nsISupports* aParent, const nsAString& aContentType,
+ uint64_t aLength);
+
+ static already_AddRefed<Blob>
+ Create(nsISupports* aParent, const nsAString& aContentType, uint64_t aStart,
+ uint64_t aLength);
+
+ static already_AddRefed<Blob>
+ CreateStringBlob(nsISupports* aParent, const nsACString& aData,
+ const nsAString& aContentType);
+
+ // The returned Blob takes ownership of aMemoryBuffer. aMemoryBuffer will be
+ // freed by free so it must be allocated by malloc or something
+ // compatible with it.
+ static already_AddRefed<Blob>
+ CreateMemoryBlob(nsISupports* aParent, void* aMemoryBuffer, uint64_t aLength,
+ const nsAString& aContentType);
+
+ static already_AddRefed<Blob>
+ CreateTemporaryBlob(nsISupports* aParent, PRFileDesc* aFD,
+ uint64_t aStartPos, uint64_t aLength,
+ const nsAString& aContentType);
+
+ BlobImpl* Impl() const
+ {
+ return mImpl;
+ }
+
+ bool IsFile() const;
+
+ const nsTArray<RefPtr<BlobImpl>>* GetSubBlobImpls() const;
+
+ // This method returns null if this Blob is not a File; it returns
+ // the same object in case this Blob already implements the File interface;
+ // otherwise it returns a new File object with the same BlobImpl.
+ already_AddRefed<File> ToFile();
+
+ // This method creates a new File object with the given name and the same
+ // BlobImpl.
+ already_AddRefed<File> ToFile(const nsAString& aName,
+ ErrorResult& aRv) const;
+
+ already_AddRefed<Blob>
+ CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType,
+ ErrorResult& aRv);
+
+ void
+ GetInternalStream(nsIInputStream** aStream, ErrorResult& aRv);
+
+ int64_t
+ GetFileId();
+
+ // WebIDL methods
+ nsISupports* GetParentObject() const
+ {
+ return mParent;
+ }
+
+ bool
+ IsMemoryFile() const;
+
+ // Blob constructor
+ static already_AddRefed<Blob>
+ Constructor(const GlobalObject& aGlobal,
+ const Optional<Sequence<BlobPart>>& aData,
+ const BlobPropertyBag& aBag,
+ ErrorResult& aRv);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ uint64_t GetSize(ErrorResult& aRv);
+
+ void GetType(nsAString& aType);
+
+ already_AddRefed<Blob> Slice(const Optional<int64_t>& aStart,
+ const Optional<int64_t>& aEnd,
+ const nsAString& aContentType,
+ ErrorResult& aRv);
+
+protected:
+ // File constructor should never be used directly. Use Blob::Create instead.
+ Blob(nsISupports* aParent, BlobImpl* aImpl);
+ virtual ~Blob() {};
+
+ virtual bool HasFileInterface() const { return false; }
+
+ // The member is the real backend implementation of this File/Blob.
+ // It's thread-safe and not CC-able and it's the only element that is moved
+ // between threads.
+ // Note: we should not store any other state in this class!
+ RefPtr<BlobImpl> mImpl;
+
+private:
+ nsCOMPtr<nsISupports> mParent;
+};
+
+class File final : public Blob
+{
+ friend class Blob;
+
+public:
+ // Note: BlobImpl must be a File in order to use this method.
+ // Check impl->IsFile().
+ static File*
+ Create(nsISupports* aParent, BlobImpl* aImpl);
+
+ static already_AddRefed<File>
+ Create(nsISupports* aParent, const nsAString& aName,
+ const nsAString& aContentType, uint64_t aLength,
+ int64_t aLastModifiedDate);
+
+ // The returned File takes ownership of aMemoryBuffer. aMemoryBuffer will be
+ // freed by free so it must be allocated by malloc or something
+ // compatible with it.
+ static already_AddRefed<File>
+ CreateMemoryFile(nsISupports* aParent, void* aMemoryBuffer, uint64_t aLength,
+ const nsAString& aName, const nsAString& aContentType,
+ int64_t aLastModifiedDate);
+
+ // This method creates a BlobFileImpl for the new File object. This is
+ // thread-safe, cross-process, cross-thread as any other BlobImpl, but, when
+ // GetType() is called, it must dispatch a runnable to the main-thread in
+ // order to use nsIMIMEService.
+ // Would be nice if we try to avoid to use this method outside the
+ // main-thread to avoid extra runnables.
+ static already_AddRefed<File>
+ CreateFromFile(nsISupports* aParent, nsIFile* aFile, bool aTemporary = false);
+
+ static already_AddRefed<File>
+ CreateFromFile(nsISupports* aParent, nsIFile* aFile, const nsAString& aName,
+ const nsAString& aContentType);
+
+ // WebIDL methods
+
+ virtual JSObject* WrapObject(JSContext *cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // File constructor
+ static already_AddRefed<File>
+ Constructor(const GlobalObject& aGlobal,
+ const Sequence<BlobPart>& aData,
+ const nsAString& aName,
+ const FilePropertyBag& aBag,
+ ErrorResult& aRv);
+
+ // ChromeOnly
+ static already_AddRefed<File>
+ CreateFromFileName(const GlobalObject& aGlobal,
+ const nsAString& aData,
+ const ChromeFilePropertyBag& aBag,
+ ErrorResult& aRv);
+
+ // ChromeOnly
+ static already_AddRefed<File>
+ CreateFromNsIFile(const GlobalObject& aGlobal,
+ nsIFile* aData,
+ const ChromeFilePropertyBag& aBag,
+ ErrorResult& aRv);
+
+ void GetName(nsAString& aName) const;
+
+ int64_t GetLastModified(ErrorResult& aRv);
+
+ Date GetLastModifiedDate(ErrorResult& aRv);
+
+ void GetRelativePath(nsAString& aPath) const;
+
+ void GetMozFullPath(nsAString& aFilename, ErrorResult& aRv) const;
+
+ void GetMozFullPathInternal(nsAString& aName, ErrorResult& aRv) const;
+
+protected:
+ virtual bool HasFileInterface() const override { return true; }
+
+private:
+ // File constructor should never be used directly. Use Blob::Create or
+ // File::Create.
+ File(nsISupports* aParent, BlobImpl* aImpl);
+ ~File() {};
+};
+
+// This is the abstract class for any File backend. It must be nsISupports
+// because this class must be ref-counted and it has to work with IPC.
+class BlobImpl : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(BLOBIMPL_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ BlobImpl() {}
+
+ virtual void GetName(nsAString& aName) const = 0;
+
+ virtual void GetDOMPath(nsAString& aName) const = 0;
+
+ virtual void SetDOMPath(const nsAString& aName) = 0;
+
+ virtual int64_t GetLastModified(ErrorResult& aRv) = 0;
+
+ virtual void SetLastModified(int64_t aLastModified) = 0;
+
+ virtual void GetMozFullPath(nsAString& aName, ErrorResult& aRv) const = 0;
+
+ virtual void GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const = 0;
+
+ virtual uint64_t GetSize(ErrorResult& aRv) = 0;
+
+ virtual void GetType(nsAString& aType) = 0;
+
+ /**
+ * An effectively-unique serial number identifying this instance of FileImpl.
+ *
+ * Implementations should obtain a serial number from
+ * FileImplBase::NextSerialNumber().
+ */
+ virtual uint64_t GetSerialNumber() const = 0;
+
+ already_AddRefed<BlobImpl>
+ Slice(const Optional<int64_t>& aStart, const Optional<int64_t>& aEnd,
+ const nsAString& aContentType, ErrorResult& aRv);
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv) = 0;
+
+ virtual const nsTArray<RefPtr<BlobImpl>>*
+ GetSubBlobImpls() const = 0;
+
+ virtual void GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv) = 0;
+
+ virtual int64_t GetFileId() = 0;
+
+ virtual nsresult GetSendInfo(nsIInputStream** aBody,
+ uint64_t* aContentLength,
+ nsACString& aContentType,
+ nsACString& aCharset) = 0;
+
+ virtual nsresult GetMutable(bool* aMutable) const = 0;
+
+ virtual nsresult SetMutable(bool aMutable) = 0;
+
+ virtual void SetLazyData(const nsAString& aName,
+ const nsAString& aContentType,
+ uint64_t aLength,
+ int64_t aLastModifiedDate) = 0;
+
+ virtual bool IsMemoryFile() const = 0;
+
+ virtual bool IsSizeUnknown() const = 0;
+
+ virtual bool IsDateUnknown() const = 0;
+
+ virtual bool IsFile() const = 0;
+
+ // Returns true if the BlobImpl is backed by an nsIFile and the underlying
+ // file is a directory.
+ virtual bool IsDirectory() const
+ {
+ return false;
+ }
+
+ // True if this implementation can be sent to other threads.
+ virtual bool MayBeClonedToOtherThreads() const
+ {
+ return true;
+ }
+
+protected:
+ virtual ~BlobImpl() {}
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(BlobImpl, BLOBIMPL_IID)
+
+class BlobImplBase : public BlobImpl
+{
+public:
+ BlobImplBase(const nsAString& aName, const nsAString& aContentType,
+ uint64_t aLength, int64_t aLastModifiedDate)
+ : mIsFile(true)
+ , mImmutable(false)
+ , mContentType(aContentType)
+ , mName(aName)
+ , mStart(0)
+ , mLength(aLength)
+ , mLastModificationDate(aLastModifiedDate)
+ , mSerialNumber(NextSerialNumber())
+ {
+ // Ensure non-null mContentType by default
+ mContentType.SetIsVoid(false);
+ }
+
+ BlobImplBase(const nsAString& aName, const nsAString& aContentType,
+ uint64_t aLength)
+ : mIsFile(true)
+ , mImmutable(false)
+ , mContentType(aContentType)
+ , mName(aName)
+ , mStart(0)
+ , mLength(aLength)
+ , mLastModificationDate(INT64_MAX)
+ , mSerialNumber(NextSerialNumber())
+ {
+ // Ensure non-null mContentType by default
+ mContentType.SetIsVoid(false);
+ }
+
+ BlobImplBase(const nsAString& aContentType, uint64_t aLength)
+ : mIsFile(false)
+ , mImmutable(false)
+ , mContentType(aContentType)
+ , mStart(0)
+ , mLength(aLength)
+ , mLastModificationDate(INT64_MAX)
+ , mSerialNumber(NextSerialNumber())
+ {
+ // Ensure non-null mContentType by default
+ mContentType.SetIsVoid(false);
+ }
+
+ BlobImplBase(const nsAString& aContentType, uint64_t aStart,
+ uint64_t aLength)
+ : mIsFile(false)
+ , mImmutable(false)
+ , mContentType(aContentType)
+ , mStart(aStart)
+ , mLength(aLength)
+ , mLastModificationDate(INT64_MAX)
+ , mSerialNumber(NextSerialNumber())
+ {
+ NS_ASSERTION(aLength != UINT64_MAX,
+ "Must know length when creating slice");
+ // Ensure non-null mContentType by default
+ mContentType.SetIsVoid(false);
+ }
+
+ virtual void GetName(nsAString& aName) const override;
+
+ virtual void GetDOMPath(nsAString& aName) const override;
+
+ virtual void SetDOMPath(const nsAString& aName) override;
+
+ virtual int64_t GetLastModified(ErrorResult& aRv) override;
+
+ virtual void SetLastModified(int64_t aLastModified) override;
+
+ virtual void GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override;
+
+ virtual void GetMozFullPathInternal(nsAString& aFileName,
+ ErrorResult& aRv) const override;
+
+ virtual uint64_t GetSize(ErrorResult& aRv) override
+ {
+ return mLength;
+ }
+
+ virtual void GetType(nsAString& aType) override;
+
+ virtual uint64_t GetSerialNumber() const override { return mSerialNumber; }
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv) override
+ {
+ return nullptr;
+ }
+
+ virtual const nsTArray<RefPtr<BlobImpl>>*
+ GetSubBlobImpls() const override
+ {
+ return nullptr;
+ }
+
+ virtual void GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv) override
+ {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ virtual int64_t GetFileId() override;
+
+ virtual nsresult GetSendInfo(nsIInputStream** aBody,
+ uint64_t* aContentLength,
+ nsACString& aContentType,
+ nsACString& aCharset) override;
+
+ virtual nsresult GetMutable(bool* aMutable) const override;
+
+ virtual nsresult SetMutable(bool aMutable) override;
+
+ virtual void
+ SetLazyData(const nsAString& aName, const nsAString& aContentType,
+ uint64_t aLength, int64_t aLastModifiedDate) override
+ {
+ mName = aName;
+ mContentType = aContentType;
+ mLength = aLength;
+ mLastModificationDate = aLastModifiedDate;
+ mIsFile = !aName.IsVoid();
+ }
+
+ virtual bool IsMemoryFile() const override
+ {
+ return false;
+ }
+
+ virtual bool IsDateUnknown() const override
+ {
+ return mIsFile && mLastModificationDate == INT64_MAX;
+ }
+
+ virtual bool IsFile() const override
+ {
+ return mIsFile;
+ }
+
+ virtual bool IsSizeUnknown() const override
+ {
+ return mLength == UINT64_MAX;
+ }
+
+protected:
+ virtual ~BlobImplBase() {}
+
+ /**
+ * Returns a new, effectively-unique serial number. This should be used
+ * by implementations to obtain a serial number for GetSerialNumber().
+ * The implementation is thread safe.
+ */
+ static uint64_t NextSerialNumber();
+
+ bool mIsFile;
+ bool mImmutable;
+
+ nsString mContentType;
+ nsString mName;
+ nsString mPath; // The path relative to a directory chosen by the user
+
+ uint64_t mStart;
+ uint64_t mLength;
+
+ int64_t mLastModificationDate;
+
+ const uint64_t mSerialNumber;
+};
+
+class BlobImplString final : public BlobImplBase
+ , public nsIMemoryReporter
+{
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMEMORYREPORTER
+
+ static already_AddRefed<BlobImplString>
+ Create(const nsACString& aData, const nsAString& aContentType);
+
+ virtual void GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv) override;
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv) override;
+
+private:
+ BlobImplString(const nsACString& aData, const nsAString& aContentType);
+
+ ~BlobImplString();
+
+ nsCString mData;
+};
+
+/**
+ * This class may be used off the main thread, and in particular, its
+ * constructor and destructor may not run on the same thread. Be careful!
+ */
+class BlobImplMemory final : public BlobImplBase
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ BlobImplMemory(void* aMemoryBuffer, uint64_t aLength, const nsAString& aName,
+ const nsAString& aContentType, int64_t aLastModifiedDate)
+ : BlobImplBase(aName, aContentType, aLength, aLastModifiedDate)
+ , mDataOwner(new DataOwner(aMemoryBuffer, aLength))
+ {
+ NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data");
+ }
+
+ BlobImplMemory(void* aMemoryBuffer, uint64_t aLength,
+ const nsAString& aContentType)
+ : BlobImplBase(aContentType, aLength)
+ , mDataOwner(new DataOwner(aMemoryBuffer, aLength))
+ {
+ NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data");
+ }
+
+ virtual void GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv) override;
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv) override;
+
+ virtual bool IsMemoryFile() const override
+ {
+ return true;
+ }
+
+ class DataOwner final : public mozilla::LinkedListElement<DataOwner>
+ {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataOwner)
+ DataOwner(void* aMemoryBuffer, uint64_t aLength)
+ : mData(aMemoryBuffer)
+ , mLength(aLength)
+ {
+ mozilla::StaticMutexAutoLock lock(sDataOwnerMutex);
+
+ if (!sDataOwners) {
+ sDataOwners = new mozilla::LinkedList<DataOwner>();
+ EnsureMemoryReporterRegistered();
+ }
+ sDataOwners->insertBack(this);
+ }
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~DataOwner() {
+ mozilla::StaticMutexAutoLock lock(sDataOwnerMutex);
+
+ remove();
+ if (sDataOwners->isEmpty()) {
+ // Free the linked list if it's empty.
+ sDataOwners = nullptr;
+ }
+
+ free(mData);
+ }
+
+ public:
+ static void EnsureMemoryReporterRegistered();
+
+ // sDataOwners and sMemoryReporterRegistered may only be accessed while
+ // holding sDataOwnerMutex! You also must hold the mutex while touching
+ // elements of the linked list that DataOwner inherits from.
+ static mozilla::StaticMutex sDataOwnerMutex;
+ static mozilla::StaticAutoPtr<mozilla::LinkedList<DataOwner> > sDataOwners;
+ static bool sMemoryReporterRegistered;
+
+ void* mData;
+ uint64_t mLength;
+ };
+
+private:
+ // Create slice
+ BlobImplMemory(const BlobImplMemory* aOther, uint64_t aStart,
+ uint64_t aLength, const nsAString& aContentType)
+ : BlobImplBase(aContentType, aOther->mStart + aStart, aLength)
+ , mDataOwner(aOther->mDataOwner)
+ {
+ NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data");
+ mImmutable = aOther->mImmutable;
+ }
+
+ ~BlobImplMemory() {}
+
+ // Used when backed by a memory store
+ RefPtr<DataOwner> mDataOwner;
+};
+
+class BlobImplTemporaryBlob final : public BlobImplBase
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ BlobImplTemporaryBlob(PRFileDesc* aFD, uint64_t aStartPos,
+ uint64_t aLength, const nsAString& aContentType)
+ : BlobImplBase(aContentType, aLength)
+ , mStartPos(aStartPos)
+ {
+ mFileDescOwner = new nsTemporaryFileInputStream::FileDescOwner(aFD);
+ }
+
+ virtual void GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv) override;
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv) override;
+
+private:
+ BlobImplTemporaryBlob(const BlobImplTemporaryBlob* aOther,
+ uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType)
+ : BlobImplBase(aContentType, aLength)
+ , mStartPos(aStart)
+ , mFileDescOwner(aOther->mFileDescOwner)
+ {}
+
+ ~BlobImplTemporaryBlob() {}
+
+ uint64_t mStartPos;
+ RefPtr<nsTemporaryFileInputStream::FileDescOwner> mFileDescOwner;
+};
+
+class BlobImplFile : public BlobImplBase
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Create as a file
+ explicit BlobImplFile(nsIFile* aFile, bool aTemporary = false)
+ : BlobImplBase(EmptyString(), EmptyString(), UINT64_MAX, INT64_MAX)
+ , mFile(aFile)
+ , mWholeFile(true)
+ , mIsTemporary(aTemporary)
+ {
+ NS_ASSERTION(mFile, "must have file");
+ // Lazily get the content type and size
+ mContentType.SetIsVoid(true);
+ mFile->GetLeafName(mName);
+ }
+
+ // Create as a file
+ BlobImplFile(const nsAString& aName, const nsAString& aContentType,
+ uint64_t aLength, nsIFile* aFile)
+ : BlobImplBase(aName, aContentType, aLength, UINT64_MAX)
+ , mFile(aFile)
+ , mWholeFile(true)
+ , mIsTemporary(false)
+ {
+ NS_ASSERTION(mFile, "must have file");
+ }
+
+ BlobImplFile(const nsAString& aName, const nsAString& aContentType,
+ uint64_t aLength, nsIFile* aFile,
+ int64_t aLastModificationDate)
+ : BlobImplBase(aName, aContentType, aLength, aLastModificationDate)
+ , mFile(aFile)
+ , mWholeFile(true)
+ , mIsTemporary(false)
+ {
+ NS_ASSERTION(mFile, "must have file");
+ }
+
+ // Create as a file with custom name
+ BlobImplFile(nsIFile* aFile, const nsAString& aName,
+ const nsAString& aContentType)
+ : BlobImplBase(aName, aContentType, UINT64_MAX, INT64_MAX)
+ , mFile(aFile)
+ , mWholeFile(true)
+ , mIsTemporary(false)
+ {
+ NS_ASSERTION(mFile, "must have file");
+ if (aContentType.IsEmpty()) {
+ // Lazily get the content type and size
+ mContentType.SetIsVoid(true);
+ }
+ }
+
+ // Overrides
+ virtual uint64_t GetSize(ErrorResult& aRv) override;
+ virtual void GetType(nsAString& aType) override;
+ virtual int64_t GetLastModified(ErrorResult& aRv) override;
+ virtual void SetLastModified(int64_t aLastModified) override;
+ virtual void GetMozFullPathInternal(nsAString& aFullPath,
+ ErrorResult& aRv) const override;
+ virtual void GetInternalStream(nsIInputStream** aInputStream,
+ ErrorResult& aRv) override;
+
+ virtual bool IsDirectory() const override;
+
+ // We always have size and date for this kind of blob.
+ virtual bool IsSizeUnknown() const override { return false; }
+ virtual bool IsDateUnknown() const override { return false; }
+
+protected:
+ virtual ~BlobImplFile() {
+ if (mFile && mIsTemporary) {
+ NS_WARNING("In temporary ~BlobImplFile");
+ // Ignore errors if any, not much we can do. Clean-up will be done by
+ // https://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsAnonymousTemporaryFile.cpp?rev=6c1c7e45c902#127
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ mFile->Remove(false);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to remove temporary DOMFile.");
+ }
+ }
+
+private:
+ // Create slice
+ BlobImplFile(const BlobImplFile* aOther, uint64_t aStart,
+ uint64_t aLength, const nsAString& aContentType)
+ : BlobImplBase(aContentType, aOther->mStart + aStart, aLength)
+ , mFile(aOther->mFile)
+ , mWholeFile(false)
+ , mIsTemporary(false)
+ {
+ NS_ASSERTION(mFile, "must have file");
+ mImmutable = aOther->mImmutable;
+ }
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv) override;
+
+ nsCOMPtr<nsIFile> mFile;
+ bool mWholeFile;
+ bool mIsTemporary;
+};
+
+class EmptyBlobImpl final : public BlobImplBase
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ explicit EmptyBlobImpl(const nsAString& aContentType)
+ : BlobImplBase(aContentType, 0 /* aLength */)
+ {
+ mImmutable = true;
+ }
+
+ EmptyBlobImpl(const nsAString& aName,
+ const nsAString& aContentType,
+ int64_t aLastModifiedDate)
+ : BlobImplBase(aName, aContentType, 0, aLastModifiedDate)
+ {
+ mImmutable = true;
+ }
+
+ virtual void GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv) override;
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv) override;
+
+ virtual bool IsMemoryFile() const override
+ {
+ return true;
+ }
+
+private:
+ ~EmptyBlobImpl() {}
+};
+
+class BlobImplStream final : public BlobImplBase
+ , public nsIMemoryReporter
+{
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMEMORYREPORTER
+
+ static already_AddRefed<BlobImplStream>
+ Create(nsIInputStream* aInputStream,
+ const nsAString& aContentType,
+ uint64_t aLength);
+
+ static already_AddRefed<BlobImplStream>
+ Create(nsIInputStream* aInputStream,
+ const nsAString& aName,
+ const nsAString& aContentType,
+ int64_t aLastModifiedDate,
+ uint64_t aLength);
+
+ virtual void GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv) override;
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType, ErrorResult& aRv) override;
+
+ virtual bool IsMemoryFile() const override
+ {
+ return true;
+ }
+
+private:
+ BlobImplStream(nsIInputStream* aInputStream,
+ const nsAString& aContentType,
+ uint64_t aLength);
+
+ BlobImplStream(nsIInputStream* aInputStream,
+ const nsAString& aName,
+ const nsAString& aContentType,
+ int64_t aLastModifiedDate,
+ uint64_t aLength);
+
+ BlobImplStream(BlobImplStream* aOther,
+ const nsAString& aContentType,
+ uint64_t aStart,
+ uint64_t aLength);
+
+ ~BlobImplStream();
+
+ void MaybeRegisterMemoryReporter();
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_File_h
diff --git a/dom/base/FileList.cpp b/dom/base/FileList.cpp
new file mode 100644
index 000000000..79aef3e06
--- /dev/null
+++ b/dom/base/FileList.cpp
@@ -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/. */
+
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/FileList.h"
+#include "mozilla/dom/FileListBinding.h"
+#include "mozilla/dom/File.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FileList, mFiles, mParent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFileList)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMFileList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FileList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FileList)
+
+JSObject*
+FileList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::FileListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+FileList::GetLength(uint32_t* aLength)
+{
+ *aLength = Length();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileList::Item(uint32_t aIndex, nsISupports** aValue)
+{
+ nsCOMPtr<nsIDOMBlob> file = Item(aIndex);
+ file.forget(aValue);
+ return NS_OK;
+}
+
+File*
+FileList::Item(uint32_t aIndex) const
+{
+ if (aIndex >= mFiles.Length()) {
+ return nullptr;
+ }
+
+ return mFiles[aIndex];
+}
+
+File*
+FileList::IndexedGetter(uint32_t aIndex, bool& aFound) const
+{
+ aFound = aIndex < mFiles.Length();
+ return Item(aIndex);
+}
+
+void
+FileList::ToSequence(Sequence<RefPtr<File>>& aSequence,
+ ErrorResult& aRv) const
+{
+ MOZ_ASSERT(aSequence.IsEmpty());
+ if (mFiles.IsEmpty()) {
+ return;
+ }
+
+ if (!aSequence.SetLength(mFiles.Length(),
+ mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ for (uint32_t i = 0; i < mFiles.Length(); ++i) {
+ aSequence[i] = mFiles[i];
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/FileList.h b/dom/base/FileList.h
new file mode 100644
index 000000000..cf951ad34
--- /dev/null
+++ b/dom/base/FileList.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_FileList_h
+#define mozilla_dom_FileList_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDOMFileList.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class BlobImpls;
+class File;
+
+class FileList final : public nsIDOMFileList,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileList)
+
+ NS_DECL_NSIDOMFILELIST
+
+ explicit FileList(nsISupports* aParent)
+ : mParent(aParent)
+ {}
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsISupports* GetParentObject()
+ {
+ return mParent;
+ }
+
+ bool Append(File* aFile)
+ {
+ MOZ_ASSERT(aFile);
+ return mFiles.AppendElement(aFile, fallible);
+ }
+
+ bool Remove(uint32_t aIndex)
+ {
+ if (aIndex < mFiles.Length()) {
+ mFiles.RemoveElementAt(aIndex);
+ return true;
+ }
+
+ return false;
+ }
+
+ void Clear()
+ {
+ return mFiles.Clear();
+ }
+
+ static FileList* FromSupports(nsISupports* aSupports)
+ {
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIDOMFileList> list_qi = do_QueryInterface(aSupports);
+
+ // If this assertion fires the QI implementation for the object in
+ // question doesn't use the nsIDOMFileList pointer as the nsISupports
+ // pointer. That must be fixed, or we'll crash...
+ NS_ASSERTION(list_qi == static_cast<nsIDOMFileList*>(aSupports),
+ "Uh, fix QI!");
+ }
+#endif
+
+ return static_cast<FileList*>(aSupports);
+ }
+
+ File* Item(uint32_t aIndex) const;
+
+ File* IndexedGetter(uint32_t aIndex, bool& aFound) const;
+
+ uint32_t Length() const
+ {
+ return mFiles.Length();
+ }
+
+ void ToSequence(Sequence<RefPtr<File>>& aSequence,
+ ErrorResult& aRv) const;
+
+private:
+ ~FileList() {}
+
+ FallibleTArray<RefPtr<File>> mFiles;
+ nsCOMPtr<nsISupports> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileList_h
diff --git a/dom/base/FileReader.cpp b/dom/base/FileReader.cpp
new file mode 100644
index 000000000..003edc61f
--- /dev/null
+++ b/dom/base/FileReader.cpp
@@ -0,0 +1,790 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "FileReader.h"
+
+#include "nsIEventTarget.h"
+#include "nsIGlobalObject.h"
+#include "nsITimer.h"
+#include "nsITransport.h"
+#include "nsIStreamTransportService.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileReaderBinding.h"
+#include "mozilla/dom/ProgressEvent.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMJSUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "xpcpublic.h"
+
+#include "WorkerPrivate.h"
+#include "WorkerScope.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+#define ABORT_STR "abort"
+#define LOAD_STR "load"
+#define LOADSTART_STR "loadstart"
+#define LOADEND_STR "loadend"
+#define ERROR_STR "error"
+#define PROGRESS_STR "progress"
+
+const uint64_t kUnknownSize = uint64_t(-1);
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FileReader)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileReader,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressNotifier)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileReader,
+ DOMEventTargetHelper)
+ tmp->Shutdown();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressNotifier)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FileReader,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FileReader)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(FileReader, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(FileReader, DOMEventTargetHelper)
+
+class MOZ_RAII FileReaderDecreaseBusyCounter
+{
+ RefPtr<FileReader> mFileReader;
+public:
+ explicit FileReaderDecreaseBusyCounter(FileReader* aFileReader)
+ : mFileReader(aFileReader)
+ {}
+
+ ~FileReaderDecreaseBusyCounter()
+ {
+ mFileReader->DecreaseBusyCounter();
+ }
+};
+
+void
+FileReader::RootResultArrayBuffer()
+{
+ mozilla::HoldJSObjects(this);
+}
+
+//FileReader constructors/initializers
+
+FileReader::FileReader(nsIGlobalObject* aGlobal,
+ WorkerPrivate* aWorkerPrivate)
+ : DOMEventTargetHelper(aGlobal)
+ , mFileData(nullptr)
+ , mDataLen(0)
+ , mDataFormat(FILE_AS_BINARY)
+ , mResultArrayBuffer(nullptr)
+ , mProgressEventWasDelayed(false)
+ , mTimerIsActive(false)
+ , mReadyState(EMPTY)
+ , mTotal(0)
+ , mTransferred(0)
+ , mTarget(do_GetCurrentThread())
+ , mBusyCount(0)
+ , mWorkerPrivate(aWorkerPrivate)
+{
+ MOZ_ASSERT(aGlobal);
+ MOZ_ASSERT(NS_IsMainThread() == !mWorkerPrivate);
+ SetDOMStringToNull(mResult);
+}
+
+FileReader::~FileReader()
+{
+ Shutdown();
+ DropJSObjects(this);
+}
+
+/* static */ already_AddRefed<FileReader>
+FileReader::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ WorkerPrivate* workerPrivate = nullptr;
+
+ if (!NS_IsMainThread()) {
+ JSContext* cx = aGlobal.Context();
+ workerPrivate = GetWorkerPrivateFromContext(cx);
+ MOZ_ASSERT(workerPrivate);
+ }
+
+ RefPtr<FileReader> fileReader = new FileReader(global, workerPrivate);
+
+ return fileReader.forget();
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+FileReader::GetInterface(const nsIID & aIID, void **aResult)
+{
+ return QueryInterface(aIID, aResult);
+}
+
+void
+FileReader::GetResult(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult,
+ ErrorResult& aRv)
+{
+ JS::Rooted<JS::Value> result(aCx);
+
+ if (mDataFormat == FILE_AS_ARRAYBUFFER) {
+ if (mReadyState == DONE && mResultArrayBuffer) {
+ result.setObject(*mResultArrayBuffer);
+ } else {
+ result.setNull();
+ }
+
+ if (!JS_WrapValue(aCx, &result)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aResult.set(result);
+ return;
+ }
+
+ nsString tmpResult = mResult;
+ if (!xpc::StringToJsval(aCx, tmpResult, aResult)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+}
+
+static nsresult
+ReadFuncBinaryString(nsIInputStream* in,
+ void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ char16_t* dest = static_cast<char16_t*>(closure) + toOffset;
+ char16_t* end = dest + count;
+ const unsigned char* source = (const unsigned char*)fromRawSegment;
+ while (dest != end) {
+ *dest = *source;
+ ++dest;
+ ++source;
+ }
+ *writeCount = count;
+
+ return NS_OK;
+}
+
+void
+FileReader::OnLoadEndArrayBuffer()
+{
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(GetParentObject())) {
+ FreeDataAndDispatchError(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RootResultArrayBuffer();
+
+ JSContext* cx = jsapi.cx();
+
+ mResultArrayBuffer = JS_NewArrayBufferWithContents(cx, mDataLen, mFileData);
+ if (mResultArrayBuffer) {
+ mFileData = nullptr; // Transfer ownership
+ FreeDataAndDispatchSuccess();
+ return;
+ }
+
+ // Let's handle the error status.
+
+ JS::Rooted<JS::Value> exceptionValue(cx);
+ if (!JS_GetPendingException(cx, &exceptionValue) ||
+ // This should not really happen, exception should always be an object.
+ !exceptionValue.isObject()) {
+ JS_ClearPendingException(jsapi.cx());
+ FreeDataAndDispatchError(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS_ClearPendingException(jsapi.cx());
+
+ JS::Rooted<JSObject*> exceptionObject(cx, &exceptionValue.toObject());
+ JSErrorReport* er = JS_ErrorFromException(cx, exceptionObject);
+ if (!er || er->message()) {
+ FreeDataAndDispatchError(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ nsAutoString errorName;
+ JSFlatString* name = js::GetErrorTypeName(cx, er->exnType);
+ if (name) {
+ AssignJSFlatString(errorName, name);
+ }
+
+ mError =
+ new DOMError(GetOwner(), errorName,
+ NS_ConvertUTF8toUTF16(er->message().c_str()));
+
+ FreeDataAndDispatchError();
+}
+
+nsresult
+FileReader::DoAsyncWait()
+{
+ nsresult rv = IncreaseBusyCounter();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mAsyncStream->AsyncWait(this,
+ /* aFlags*/ 0,
+ /* aRequestedCount */ 0,
+ mTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DecreaseBusyCounter();
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FileReader::DoReadData(uint64_t aCount)
+{
+ MOZ_ASSERT(mAsyncStream);
+
+ if (mDataFormat == FILE_AS_BINARY) {
+ //Continuously update our binary string as data comes in
+ uint32_t oldLen = mResult.Length();
+ NS_ASSERTION(mResult.Length() == mDataLen, "unexpected mResult length");
+ if (uint64_t(oldLen) + aCount > UINT32_MAX)
+ return NS_ERROR_OUT_OF_MEMORY;
+ char16_t *buf = nullptr;
+ mResult.GetMutableData(&buf, oldLen + aCount, fallible);
+ NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t bytesRead = 0;
+ mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
+ &bytesRead);
+ NS_ASSERTION(bytesRead == aCount, "failed to read data");
+ }
+ else {
+ CheckedInt<uint64_t> size = mDataLen;
+ size += aCount;
+
+ //Update memory buffer to reflect the contents of the file
+ if (!size.isValid() ||
+ // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
+ size.value() > UINT32_MAX ||
+ size.value() > mTotal) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mDataFormat != FILE_AS_ARRAYBUFFER) {
+ mFileData = (char *) realloc(mFileData, mDataLen + aCount);
+ NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ uint32_t bytesRead = 0;
+ MOZ_DIAGNOSTIC_ASSERT(mFileData);
+ mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
+ NS_ASSERTION(bytesRead == aCount, "failed to read data");
+ }
+
+ mDataLen += aCount;
+ return NS_OK;
+}
+
+// Helper methods
+
+void
+FileReader::ReadFileContent(Blob& aBlob,
+ const nsAString &aCharset,
+ eDataFormat aDataFormat,
+ ErrorResult& aRv)
+{
+ //Implicit abort to clear any other activity going on
+ ErrorResult error;
+ Abort(error);
+ error.SuppressException();
+
+ if (mReadyState == LOADING) {
+ // A nested ReadAsSomething() as been called during one of the events
+ // dispatched by Abort(). We have to terminate this operation in order to
+ // continue the nested one.
+ aRv.Throw(NS_ERROR_ABORT);
+ return;
+ }
+
+ mError = nullptr;
+ SetDOMStringToNull(mResult);
+ mTransferred = 0;
+ mTotal = 0;
+ mReadyState = EMPTY;
+ FreeFileData();
+
+ mBlob = &aBlob;
+ mDataFormat = aDataFormat;
+ CopyUTF16toUTF8(aCharset, mCharset);
+
+ nsresult rv;
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ mBlob->GetInternalStream(getter_AddRefs(stream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr<nsITransport> transport;
+ aRv = sts->CreateInputTransport(stream,
+ /* aStartOffset */ 0,
+ /* aReadLimit */ -1,
+ /* aCloseWhenDone */ true,
+ getter_AddRefs(transport));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> wrapper;
+ aRv = transport->OpenInputStream(/* aFlags */ 0,
+ /* aSegmentSize */ 0,
+ /* aSegmentCount */ 0,
+ getter_AddRefs(wrapper));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!mAsyncStream);
+ mAsyncStream = do_QueryInterface(wrapper);
+ MOZ_ASSERT(mAsyncStream);
+
+ mTotal = mBlob->GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (mDataFormat == FILE_AS_ARRAYBUFFER) {
+ mFileData = js_pod_malloc<char>(mTotal);
+ if (!mFileData) {
+ NS_WARNING("Preallocation failed for ReadFileData");
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ aRv = DoAsyncWait();
+ if (NS_WARN_IF(aRv.Failed())) {
+ FreeFileData();
+ return;
+ }
+
+ //FileReader should be in loading state here
+ mReadyState = LOADING;
+ DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR));
+}
+
+nsresult
+FileReader::GetAsText(Blob *aBlob,
+ const nsACString &aCharset,
+ const char *aFileData,
+ uint32_t aDataLen,
+ nsAString& aResult)
+{
+ // The BOM sniffing is baked into the "decode" part of the Encoding
+ // Standard, which the File API references.
+ nsAutoCString encoding;
+ if (!nsContentUtils::CheckForBOM(
+ reinterpret_cast<const unsigned char *>(aFileData),
+ aDataLen,
+ encoding)) {
+ // BOM sniffing failed. Try the API argument.
+ if (!EncodingUtils::FindEncodingForLabel(aCharset,
+ encoding)) {
+ // API argument failed. Try the type property of the blob.
+ nsAutoString type16;
+ aBlob->GetType(type16);
+ NS_ConvertUTF16toUTF8 type(type16);
+ nsAutoCString specifiedCharset;
+ bool haveCharset;
+ int32_t charsetStart, charsetEnd;
+ NS_ExtractCharsetFromContentType(type,
+ specifiedCharset,
+ &haveCharset,
+ &charsetStart,
+ &charsetEnd);
+ if (!EncodingUtils::FindEncodingForLabel(specifiedCharset, encoding)) {
+ // Type property failed. Use UTF-8.
+ encoding.AssignLiteral("UTF-8");
+ }
+ }
+ }
+
+ nsDependentCSubstring data(aFileData, aDataLen);
+ return nsContentUtils::ConvertStringFromEncoding(encoding, data, aResult);
+}
+
+nsresult
+FileReader::GetAsDataURL(Blob *aBlob,
+ const char *aFileData,
+ uint32_t aDataLen,
+ nsAString& aResult)
+{
+ aResult.AssignLiteral("data:");
+
+ nsAutoString contentType;
+ aBlob->GetType(contentType);
+ if (!contentType.IsEmpty()) {
+ aResult.Append(contentType);
+ } else {
+ aResult.AppendLiteral("application/octet-stream");
+ }
+ aResult.AppendLiteral(";base64,");
+
+ nsCString encodedData;
+ nsresult rv = Base64Encode(Substring(aFileData, aDataLen), encodedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!AppendASCIItoUTF16(encodedData, aResult, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ JSObject*
+FileReader::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FileReaderBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+FileReader::StartProgressEventTimer()
+{
+ if (!mProgressNotifier) {
+ mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ if (mProgressNotifier) {
+ mProgressEventWasDelayed = false;
+ mTimerIsActive = true;
+ mProgressNotifier->Cancel();
+ mProgressNotifier->SetTarget(mTarget);
+ mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+void
+FileReader::ClearProgressEventTimer()
+{
+ mProgressEventWasDelayed = false;
+ mTimerIsActive = false;
+ if (mProgressNotifier) {
+ mProgressNotifier->Cancel();
+ }
+}
+
+void
+FileReader::FreeDataAndDispatchSuccess()
+{
+ FreeFileData();
+ mResult.SetIsVoid(false);
+ mAsyncStream = nullptr;
+ mBlob = nullptr;
+
+ // Dispatch event to signify end of a successful operation
+ DispatchProgressEvent(NS_LITERAL_STRING(LOAD_STR));
+ DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
+}
+
+void
+FileReader::FreeDataAndDispatchError()
+{
+ MOZ_ASSERT(mError);
+
+ FreeFileData();
+ mResult.SetIsVoid(true);
+ mAsyncStream = nullptr;
+ mBlob = nullptr;
+
+ // Dispatch error event to signify load failure
+ DispatchProgressEvent(NS_LITERAL_STRING(ERROR_STR));
+ DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
+}
+
+void
+FileReader::FreeDataAndDispatchError(nsresult aRv)
+{
+ // Set the status attribute, and dispatch the error event
+ switch (aRv) {
+ case NS_ERROR_FILE_NOT_FOUND:
+ mError = new DOMError(GetOwner(), NS_LITERAL_STRING("NotFoundError"));
+ break;
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ mError = new DOMError(GetOwner(), NS_LITERAL_STRING("SecurityError"));
+ break;
+ default:
+ mError = new DOMError(GetOwner(), NS_LITERAL_STRING("NotReadableError"));
+ break;
+ }
+
+ FreeDataAndDispatchError();
+}
+
+nsresult
+FileReader::DispatchProgressEvent(const nsAString& aType)
+{
+ ProgressEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mLoaded = mTransferred;
+
+ if (mTotal != kUnknownSize) {
+ init.mLengthComputable = true;
+ init.mTotal = mTotal;
+ } else {
+ init.mLengthComputable = false;
+ init.mTotal = 0;
+ }
+ RefPtr<ProgressEvent> event =
+ ProgressEvent::Constructor(this, aType, init);
+ event->SetTrusted(true);
+
+ return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+// nsITimerCallback
+NS_IMETHODIMP
+FileReader::Notify(nsITimer* aTimer)
+{
+ nsresult rv;
+ mTimerIsActive = false;
+
+ if (mProgressEventWasDelayed) {
+ rv = DispatchProgressEvent(NS_LITERAL_STRING("progress"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StartProgressEventTimer();
+ }
+
+ return NS_OK;
+}
+
+// InputStreamCallback
+NS_IMETHODIMP
+FileReader::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+ if (mReadyState != LOADING || aStream != mAsyncStream) {
+ return NS_OK;
+ }
+
+ // We use this class to decrease the busy counter at the end of this method.
+ // In theory we can do it immediatelly but, for debugging reasons, we want to
+ // be 100% sure we have a workerHolder when OnLoadEnd() is called.
+ FileReaderDecreaseBusyCounter RAII(this);
+
+ uint64_t aCount;
+ nsresult rv = aStream->Available(&aCount);
+
+ if (NS_SUCCEEDED(rv) && aCount) {
+ rv = DoReadData(aCount);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = DoAsyncWait();
+ }
+
+ if (NS_FAILED(rv) || !aCount) {
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ rv = NS_OK;
+ }
+ return OnLoadEnd(rv);
+ }
+
+ mTransferred += aCount;
+
+ //Notify the timer is the appropriate timeframe has passed
+ if (mTimerIsActive) {
+ mProgressEventWasDelayed = true;
+ } else {
+ rv = DispatchProgressEvent(NS_LITERAL_STRING(PROGRESS_STR));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StartProgressEventTimer();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FileReader::OnLoadEnd(nsresult aStatus)
+{
+ // Cancel the progress event timer
+ ClearProgressEventTimer();
+
+ // FileReader must be in DONE stage after an operation
+ mReadyState = DONE;
+
+ // Quick return, if failed.
+ if (NS_FAILED(aStatus)) {
+ FreeDataAndDispatchError(aStatus);
+ return NS_OK;
+ }
+
+ // In case we read a different number of bytes, we can assume that the
+ // underlying storage has changed. We should not continue.
+ if (mDataLen != mTotal) {
+ FreeDataAndDispatchError(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ // ArrayBuffer needs a custom handling.
+ if (mDataFormat == FILE_AS_ARRAYBUFFER) {
+ OnLoadEndArrayBuffer();
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ // We don't do anything special for Binary format.
+
+ if (mDataFormat == FILE_AS_DATAURL) {
+ rv = GetAsDataURL(mBlob, mFileData, mDataLen, mResult);
+ } else if (mDataFormat == FILE_AS_TEXT) {
+ if (!mFileData && mDataLen) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ } else if (!mFileData) {
+ rv = GetAsText(mBlob, mCharset, "", mDataLen, mResult);
+ } else {
+ rv = GetAsText(mBlob, mCharset, mFileData, mDataLen, mResult);
+ }
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FreeDataAndDispatchError(rv);
+ return NS_OK;
+ }
+
+ FreeDataAndDispatchSuccess();
+ return NS_OK;
+}
+
+void
+FileReader::Abort(ErrorResult& aRv)
+{
+ if (mReadyState != LOADING) {
+ // XXX The spec doesn't say this
+ aRv.Throw(NS_ERROR_DOM_FILE_ABORT_ERR);
+ return;
+ }
+
+ ClearProgressEventTimer();
+
+ mReadyState = DONE;
+
+ // XXX The spec doesn't say this
+ mError = new DOMError(GetOwner(), NS_LITERAL_STRING("AbortError"));
+
+ // Revert status and result attributes
+ SetDOMStringToNull(mResult);
+ mResultArrayBuffer = nullptr;
+
+ mAsyncStream = nullptr;
+ mBlob = nullptr;
+
+ //Clean up memory buffer
+ FreeFileData();
+
+ // Dispatch the events
+ DispatchProgressEvent(NS_LITERAL_STRING(ABORT_STR));
+ DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
+}
+
+nsresult
+FileReader::IncreaseBusyCounter()
+{
+ if (mWorkerPrivate && mBusyCount++ == 0 &&
+ !HoldWorker(mWorkerPrivate, Closing)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+FileReader::DecreaseBusyCounter()
+{
+ MOZ_ASSERT_IF(mWorkerPrivate, mBusyCount);
+ if (mWorkerPrivate && --mBusyCount == 0) {
+ ReleaseWorker();
+ }
+}
+
+bool
+FileReader::Notify(Status aStatus)
+{
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (aStatus > Running) {
+ Shutdown();
+ }
+
+ return true;
+}
+
+void
+FileReader::Shutdown()
+{
+ mReadyState = DONE;
+
+ if (mAsyncStream) {
+ mAsyncStream->Close();
+ mAsyncStream = nullptr;
+ }
+
+ FreeFileData();
+ mResultArrayBuffer = nullptr;
+
+ if (mWorkerPrivate && mBusyCount != 0) {
+ ReleaseWorker();
+ mWorkerPrivate = nullptr;
+ mBusyCount = 0;
+ }
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/base/FileReader.h b/dom/base/FileReader.h
new file mode 100644
index 000000000..225385e13
--- /dev/null
+++ b/dom/base/FileReader.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 mozilla_dom_FileReader_h
+#define mozilla_dom_FileReader_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "WorkerHolder.h"
+
+#define NS_PROGRESS_EVENT_INTERVAL 50
+
+class nsITimer;
+class nsIEventTarget;
+
+namespace mozilla {
+namespace dom {
+
+class Blob;
+class DOMError;
+
+namespace workers {
+class WorkerPrivate;
+}
+
+extern const uint64_t kUnknownSize;
+
+class FileReaderDecreaseBusyCounter;
+
+class FileReader final : public DOMEventTargetHelper,
+ public nsIInterfaceRequestor,
+ public nsSupportsWeakReference,
+ public nsIInputStreamCallback,
+ public nsITimerCallback,
+ public workers::WorkerHolder
+{
+ friend class FileReaderDecreaseBusyCounter;
+
+public:
+ FileReader(nsIGlobalObject* aGlobal,
+ workers::WorkerPrivate* aWorkerPrivate);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FileReader,
+ DOMEventTargetHelper)
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL
+ static already_AddRefed<FileReader>
+ Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
+ void ReadAsArrayBuffer(JSContext* aCx, Blob& aBlob, ErrorResult& aRv)
+ {
+ ReadFileContent(aBlob, EmptyString(), FILE_AS_ARRAYBUFFER, aRv);
+ }
+
+ void ReadAsText(Blob& aBlob, const nsAString& aLabel, ErrorResult& aRv)
+ {
+ ReadFileContent(aBlob, aLabel, FILE_AS_TEXT, aRv);
+ }
+
+ void ReadAsDataURL(Blob& aBlob, ErrorResult& aRv)
+ {
+ ReadFileContent(aBlob, EmptyString(), FILE_AS_DATAURL, aRv);
+ }
+
+ void Abort(ErrorResult& aRv);
+
+ uint16_t ReadyState() const
+ {
+ return static_cast<uint16_t>(mReadyState);
+ }
+
+ DOMError* GetError() const
+ {
+ return mError;
+ }
+
+ void GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
+ ErrorResult& aRv);
+
+ IMPL_EVENT_HANDLER(loadstart)
+ IMPL_EVENT_HANDLER(progress)
+ IMPL_EVENT_HANDLER(load)
+ IMPL_EVENT_HANDLER(abort)
+ IMPL_EVENT_HANDLER(error)
+ IMPL_EVENT_HANDLER(loadend)
+
+ void ReadAsBinaryString(Blob& aBlob, ErrorResult& aRv)
+ {
+ ReadFileContent(aBlob, EmptyString(), FILE_AS_BINARY, aRv);
+ }
+
+ // WorkerHolder
+ bool Notify(workers::Status) override;
+
+private:
+ virtual ~FileReader();
+
+ // This must be in sync with dom/webidl/FileReader.webidl
+ enum eReadyState {
+ EMPTY = 0,
+ LOADING = 1,
+ DONE = 2
+ };
+
+ enum eDataFormat {
+ FILE_AS_ARRAYBUFFER,
+ FILE_AS_BINARY,
+ FILE_AS_TEXT,
+ FILE_AS_DATAURL
+ };
+
+ void RootResultArrayBuffer();
+
+ void ReadFileContent(Blob& aBlob,
+ const nsAString &aCharset, eDataFormat aDataFormat,
+ ErrorResult& aRv);
+ nsresult GetAsText(Blob *aBlob, const nsACString &aCharset,
+ const char *aFileData, uint32_t aDataLen,
+ nsAString &aResult);
+ nsresult GetAsDataURL(Blob *aBlob, const char *aFileData,
+ uint32_t aDataLen, nsAString &aResult);
+
+ nsresult OnLoadEnd(nsresult aStatus);
+
+ void StartProgressEventTimer();
+ void ClearProgressEventTimer();
+
+ void FreeDataAndDispatchSuccess();
+ void FreeDataAndDispatchError();
+ void FreeDataAndDispatchError(nsresult aRv);
+ nsresult DispatchProgressEvent(const nsAString& aType);
+
+ nsresult DoAsyncWait();
+ nsresult DoReadData(uint64_t aCount);
+
+ void OnLoadEndArrayBuffer();
+
+ void FreeFileData()
+ {
+ free(mFileData);
+ mFileData = nullptr;
+ mDataLen = 0;
+ }
+
+ nsresult IncreaseBusyCounter();
+ void DecreaseBusyCounter();
+
+ void Shutdown();
+
+ char *mFileData;
+ RefPtr<Blob> mBlob;
+ nsCString mCharset;
+ uint32_t mDataLen;
+
+ eDataFormat mDataFormat;
+
+ nsString mResult;
+
+ JS::Heap<JSObject*> mResultArrayBuffer;
+
+ nsCOMPtr<nsITimer> mProgressNotifier;
+ bool mProgressEventWasDelayed;
+ bool mTimerIsActive;
+
+ nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
+
+ RefPtr<DOMError> mError;
+
+ eReadyState mReadyState;
+
+ uint64_t mTotal;
+ uint64_t mTransferred;
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+
+ uint64_t mBusyCount;
+
+ // Kept alive with a WorkerHolder.
+ workers::WorkerPrivate* mWorkerPrivate;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_FileReader_h
diff --git a/dom/base/FormData.cpp b/dom/base/FormData.cpp
new file mode 100644
index 000000000..6095286be
--- /dev/null
+++ b/dom/base/FormData.cpp
@@ -0,0 +1,430 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIVariant.h"
+#include "nsIInputStream.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/HTMLFormElement.h"
+
+#include "MultipartBlobImpl.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+FormData::FormData(nsISupports* aOwner)
+ : HTMLFormSubmission(NS_LITERAL_CSTRING("UTF-8"), nullptr)
+ , mOwner(aOwner)
+{
+}
+
+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(NS_LITERAL_STRING("blob"), 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_CLASS(FormData)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FormData)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+
+ 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)
+
+ 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_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FormData)
+
+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(nsIDOMFormData)
+ NS_INTERFACE_MAP_ENTRY(nsIXHRSendable)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFormData)
+NS_INTERFACE_MAP_END
+
+// -------------------------------------------------------------------------
+// HTMLFormSubmission
+nsresult
+FormData::GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream)
+{
+ NS_NOTREACHED("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;
+ }
+
+ AddNameBlobOrNullPair(aName, file);
+}
+
+void
+FormData::Append(const nsAString& aName, Directory* aDirectory)
+{
+ AddNameDirectoryPair(aName, aDirectory);
+}
+
+void
+FormData::Delete(const nsAString& aName)
+{
+ // 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)) {
+ mFormData.RemoveElementAt(i);
+ }
+ }
+}
+
+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::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob)
+{
+ RefPtr<File> file;
+
+ if (!aBlob) {
+ FormDataTuple* data = mFormData.AppendElement();
+ SetNameValuePair(data, aName, EmptyString(), true /* aWasNullBlob */);
+ return NS_OK;
+ }
+
+ ErrorResult rv;
+ file = GetOrCreateFileCalledBlob(*aBlob, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ FormDataTuple* data = mFormData.AppendElement();
+ SetNameFilePair(data, aName, file);
+ return NS_OK;
+}
+
+nsresult
+FormData::AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory)
+{
+ MOZ_ASSERT(aDirectory);
+
+ FormDataTuple* data = mFormData.AppendElement();
+ SetNameDirectoryPair(data, aName, 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,
+ bool aWasNullBlob)
+{
+ MOZ_ASSERT(aData);
+ aData->name = aName;
+ aData->wasNullBlob = aWasNullBlob;
+ aData->value.SetAsUSVString() = aValue;
+}
+
+void
+FormData::SetNameFilePair(FormDataTuple* aData,
+ const nsAString& aName,
+ File* aFile)
+{
+ MOZ_ASSERT(aData);
+ MOZ_ASSERT(aFile);
+
+ aData->name = aName;
+ aData->wasNullBlob = false;
+ aData->value.SetAsBlob() = aFile;
+}
+
+void
+FormData::SetNameDirectoryPair(FormDataTuple* aData,
+ const nsAString& aName,
+ Directory* aDirectory)
+{
+ MOZ_ASSERT(aData);
+ MOZ_ASSERT(aDirectory);
+
+ aData->name = aName;
+ aData->wasNullBlob = false;
+ aData->value.SetAsDirectory() = aDirectory;
+}
+
+// -------------------------------------------------------------------------
+// nsIDOMFormData
+
+NS_IMETHODIMP
+FormData::Append(const nsAString& aName, nsIVariant* aValue)
+{
+ uint16_t dataType;
+ nsresult rv = aValue->GetDataType(&dataType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dataType == nsIDataType::VTYPE_INTERFACE ||
+ dataType == nsIDataType::VTYPE_INTERFACE_IS) {
+ nsCOMPtr<nsISupports> supports;
+ nsID *iid;
+ rv = aValue->GetAsInterface(&iid, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ free(iid);
+
+ nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(supports);
+ RefPtr<Blob> blob = static_cast<Blob*>(domBlob.get());
+ if (domBlob) {
+ Optional<nsAString> temp;
+ ErrorResult rv;
+ Append(aName, *blob, temp, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ return NS_OK;
+ }
+ }
+
+ char16_t* stringData = nullptr;
+ uint32_t stringLen = 0;
+ rv = aValue->GetAsWStringWithSize(&stringLen, &stringData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString valAsString;
+ valAsString.Adopt(stringData, stringLen);
+
+ ErrorResult error;
+ Append(aName, valAsString, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ JSObject*
+FormData::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return FormDataBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed<FormData>
+FormData::Constructor(const GlobalObject& aGlobal,
+ const Optional<NonNull<HTMLFormElement> >& aFormElement,
+ ErrorResult& aRv)
+{
+ RefPtr<FormData> formData = new FormData(aGlobal.GetAsSupports());
+ if (aFormElement.WasPassed()) {
+ aRv = aFormElement.Value().WalkFormElements(formData);
+ }
+ return formData.forget();
+}
+
+// -------------------------------------------------------------------------
+// nsIXHRSendable
+
+NS_IMETHODIMP
+FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset)
+{
+ FSMultipartFormData fs(NS_LITERAL_CSTRING("UTF-8"), nullptr);
+
+ for (uint32_t i = 0; i < mFormData.Length(); ++i) {
+ if (mFormData[i].wasNullBlob) {
+ MOZ_ASSERT(mFormData[i].value.IsUSVString());
+ fs.AddNameBlobOrNullPair(mFormData[i].name, nullptr);
+ } else if (mFormData[i].value.IsUSVString()) {
+ fs.AddNameValuePair(mFormData[i].name,
+ mFormData[i].value.GetAsUSVString());
+ } else if (mFormData[i].value.IsBlob()) {
+ fs.AddNameBlobOrNullPair(mFormData[i].name,
+ mFormData[i].value.GetAsBlob());
+ } else {
+ MOZ_ASSERT(mFormData[i].value.IsDirectory());
+ fs.AddNameDirectoryPair(mFormData[i].name,
+ mFormData[i].value.GetAsDirectory());
+ }
+ }
+
+ fs.GetContentType(aContentType);
+ aCharset.Truncate();
+ *aContentLength = 0;
+ NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));
+
+ return NS_OK;
+}
diff --git a/dom/base/FormData.h b/dom/base/FormData.h
new file mode 100644
index 000000000..e213db164
--- /dev/null
+++ b/dom/base/FormData.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 mozilla_dom_FormData_h
+#define mozilla_dom_FormData_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormDataBinding.h"
+#include "nsIDOMFormData.h"
+#include "nsIXMLHttpRequest.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLFormElement;
+class GlobalObject;
+
+class FormData final : public nsIDOMFormData,
+ public nsIXHRSendable,
+ public HTMLFormSubmission,
+ public nsWrapperCache
+{
+private:
+ ~FormData() {}
+
+ struct FormDataTuple
+ {
+ nsString name;
+ bool wasNullBlob;
+ 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,
+ bool aWasNullBlob = false);
+
+ void SetNameFilePair(FormDataTuple* aData,
+ const nsAString& aName,
+ File* aFile);
+
+ void SetNameDirectoryPair(FormDataTuple* aData,
+ const nsAString& aName,
+ Directory* aDirectory);
+
+public:
+ explicit FormData(nsISupports* aOwner = nullptr);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FormData,
+ nsIDOMFormData)
+
+ NS_DECL_NSIDOMFORMDATA
+ NS_DECL_NSIXHRSENDABLE
+
+ // 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,
+ 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 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) override;
+
+ virtual nsresult AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue) override
+ {
+ FormDataTuple* data = mFormData.AppendElement();
+ SetNameValuePair(data, aName, aValue);
+ return NS_OK;
+ }
+
+ virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
+ Blob* aBlob) override;
+
+ virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) override;
+
+ typedef bool (*FormDataEntryCallback)(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;
+ }
+
+private:
+ nsCOMPtr<nsISupports> mOwner;
+
+ nsTArray<FormDataTuple> mFormData;
+};
+
+} // dom namespace
+} // mozilla namepsace
+
+#endif // mozilla_dom_FormData_h
diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp
new file mode 100644
index 000000000..293177ce7
--- /dev/null
+++ b/dom/base/FragmentOrElement.cpp
@@ -0,0 +1,2394 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMElement, implements nsIContent, provides
+ * utility methods for subclasses, and so forth.
+ */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
+
+#include "mozilla/dom/FragmentOrElement.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Attr.h"
+#include "nsDOMAttributeMap.h"
+#include "nsIAtom.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/Event.h"
+#include "nsIDocumentInlines.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDOMNodeList.h"
+#include "nsIContentIterator.h"
+#include "nsFocusManager.h"
+#include "nsILinkHandler.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsStyleConsts.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsIDOMEvent.h"
+#include "nsDOMCID.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsDOMCSSAttrDeclaration.h"
+#include "nsNameSpaceManager.h"
+#include "nsContentList.h"
+#include "nsDOMTokenList.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsError.h"
+#include "nsDOMString.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIDOMMutationEvent.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/MouseEvents.h"
+#include "nsNodeUtils.h"
+#include "nsDocument.h"
+#include "nsAttrValueOrString.h"
+#ifdef MOZ_XUL
+#include "nsXULElement.h"
+#endif /* MOZ_XUL */
+#include "nsFrameSelection.h"
+#ifdef DEBUG
+#include "nsRange.h"
+#endif
+
+#include "nsBindingManager.h"
+#include "nsXBLBinding.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIBoxObject.h"
+#include "nsSVGUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsTextFragment.h"
+#include "nsContentCID.h"
+
+#include "nsIDOMEventListener.h"
+#include "nsIWebNavigation.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+
+#include "js/GCAPI.h"
+
+#include "nsNodeInfoManager.h"
+#include "nsICategoryManager.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIEditor.h"
+#include "nsIEditorIMESupport.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIControllers.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIScrollableFrame.h"
+#include "ChildIterator.h"
+#include "mozilla/css/StyleRule.h" /* For nsCSSSelectorList */
+#include "nsRuleProcessorData.h"
+#include "nsTextNode.h"
+#include "mozilla/dom/NodeListBinding.h"
+
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#endif /* MOZ_XUL */
+
+#include "nsCCUncollectableMarker.h"
+
+#include "mozAutoDocUpdate.h"
+
+#include "mozilla/Sprintf.h"
+#include "nsDOMMutationObserver.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsCycleCollector.h"
+#include "xpcpublic.h"
+#include "nsIScriptError.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/CORSMode.h"
+
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+
+#include "nsStyledElement.h"
+#include "nsIContentInlines.h"
+#include "nsChildContentList.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+int32_t nsIContent::sTabFocusModel = eTabFocus_any;
+bool nsIContent::sTabFocusModelAppliesToXUL = false;
+uint64_t nsMutationGuard::sGeneration = 0;
+
+nsIContent*
+nsIContent::FindFirstNonChromeOnlyAccessContent() const
+{
+ // This handles also nested native anonymous content.
+ for (const nsIContent *content = this; content;
+ content = content->GetBindingParent()) {
+ if (!content->ChromeOnlyAccess()) {
+ // Oops, this function signature allows casting const to
+ // non-const. (Then again, so does GetChildAt(0)->GetParent().)
+ return const_cast<nsIContent*>(content);
+ }
+ }
+ return nullptr;
+}
+
+nsINode*
+nsIContent::GetFlattenedTreeParentNodeInternal() const
+{
+ nsINode* parentNode = GetParentNode();
+ if (!parentNode || !parentNode->IsContent()) {
+ MOZ_ASSERT(!parentNode || parentNode == OwnerDoc());
+ return parentNode;
+ }
+ nsIContent* parent = parentNode->AsContent();
+
+ if (parent && nsContentUtils::HasDistributedChildren(parent) &&
+ nsContentUtils::IsInSameAnonymousTree(parent, this)) {
+ // This node is distributed to insertion points, thus we
+ // need to consult the destination insertion points list to
+ // figure out where this node was inserted in the flattened tree.
+ // It may be the case that |parent| distributes its children
+ // but the child does not match any insertion points, thus
+ // the flattened tree parent is nullptr.
+ nsTArray<nsIContent*>* destInsertionPoints = GetExistingDestInsertionPoints();
+ parent = destInsertionPoints && !destInsertionPoints->IsEmpty() ?
+ destInsertionPoints->LastElement()->GetParent() : nullptr;
+ } else if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsIContent* insertionParent = GetXBLInsertionParent();
+ if (insertionParent) {
+ parent = insertionParent;
+ }
+ }
+
+ // Shadow roots never shows up in the flattened tree. Return the host
+ // instead.
+ if (parent && parent->IsInShadowTree()) {
+ ShadowRoot* parentShadowRoot = ShadowRoot::FromNode(parent);
+ if (parentShadowRoot) {
+ return parentShadowRoot->GetHost();
+ }
+ }
+
+ return parent;
+}
+
+nsIContent::IMEState
+nsIContent::GetDesiredIMEState()
+{
+ if (!IsEditableInternal()) {
+ // 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(NS_EVENT_STATE_MOZ_READWRITE)) {
+ return IMEState(IMEState::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();
+ }
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc) {
+ return IMEState(IMEState::DISABLED);
+ }
+ nsIPresShell* ps = doc->GetShell();
+ if (!ps) {
+ return IMEState(IMEState::DISABLED);
+ }
+ nsPresContext* pc = ps->GetPresContext();
+ if (!pc) {
+ return IMEState(IMEState::DISABLED);
+ }
+ nsIEditor* editor = nsContentUtils::GetHTMLEditor(pc);
+ nsCOMPtr<nsIEditorIMESupport> imeEditor = do_QueryInterface(editor);
+ if (!imeEditor) {
+ return IMEState(IMEState::DISABLED);
+ }
+ IMEState state;
+ imeEditor->GetPreferredIMEState(&state);
+ return state;
+}
+
+bool
+nsIContent::HasIndependentSelection()
+{
+ nsIFrame* frame = GetPrimaryFrame();
+ return (frame && frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION);
+}
+
+dom::Element*
+nsIContent::GetEditingHost()
+{
+ // If this isn't editable, return nullptr.
+ if (!IsEditableInternal()) {
+ return nullptr;
+ }
+
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ // If this is in designMode, we should return <body>
+ if (doc->HasFlag(NODE_IS_EDITABLE) && !IsInShadowTree()) {
+ return doc->GetBodyElement();
+ }
+
+ nsIContent* content = this;
+ for (dom::Element* parent = GetParentElement();
+ parent && parent->HasFlag(NODE_IS_EDITABLE);
+ parent = content->GetParentElement()) {
+ content = parent;
+ }
+ return content->AsElement();
+}
+
+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;
+ }
+
+ nsCOMPtr<nsIAtom> 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.
+ const nsIContent* content = this;
+ do {
+ if (content->GetAttr(kNameSpaceID_XMLNS, name, aNamespaceURI))
+ return NS_OK;
+ } while ((content = content->GetParent()));
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<nsIURI>
+nsIContent::GetBaseURI(bool aTryUseXHRDocBaseURI) const
+{
+ nsIDocument* doc = OwnerDoc();
+ // Start with document base
+ nsCOMPtr<nsIURI> base = doc->GetBaseURI(aTryUseXHRDocBaseURI);
+
+ // Collect array of xml:base attribute values up the parent chain. This
+ // is slightly slower for the case when there are xml:base attributes, but
+ // faster for the far more common case of there not being any such
+ // attributes.
+ // Also check for SVG elements which require special handling
+ AutoTArray<nsString, 5> baseAttrs;
+ nsString attr;
+ const nsIContent *elem = this;
+ do {
+ // First check for SVG specialness (why is this SVG specific?)
+ if (elem->IsSVGElement()) {
+ nsIContent* bindingParent = elem->GetBindingParent();
+ if (bindingParent) {
+ nsXBLBinding* binding = bindingParent->GetXBLBinding();
+ if (binding) {
+ // XXX sXBL/XBL2 issue
+ // If this is an anonymous XBL element use the binding
+ // document for the base URI.
+ // XXX Will fail with xml:base
+ base = binding->PrototypeBinding()->DocURI();
+ break;
+ }
+ }
+ }
+
+ nsIURI* explicitBaseURI = elem->GetExplicitBaseURI();
+ if (explicitBaseURI) {
+ base = explicitBaseURI;
+ break;
+ }
+
+ // Otherwise check for xml:base attribute
+ elem->GetAttr(kNameSpaceID_XML, nsGkAtoms::base, attr);
+ if (!attr.IsEmpty()) {
+ baseAttrs.AppendElement(attr);
+ }
+ elem = elem->GetParent();
+ } while(elem);
+
+ // Now resolve against all xml:base attrs
+ for (uint32_t i = baseAttrs.Length() - 1; i != uint32_t(-1); --i) {
+ nsCOMPtr<nsIURI> newBase;
+ nsresult rv = NS_NewURI(getter_AddRefs(newBase), baseAttrs[i],
+ doc->GetDocumentCharacterSet().get(), base);
+ // Do a security check, almost the same as nsDocument::SetBaseURL()
+ // Only need to do this on the final uri
+ if (NS_SUCCEEDED(rv) && i == 0) {
+ rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(NodePrincipal(), newBase,
+ nsIScriptSecurityManager::STANDARD);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ base.swap(newBase);
+ }
+ }
+
+ return base.forget();
+}
+
+//----------------------------------------------------------------------
+
+static inline JSObject*
+GetJSObjectChild(nsWrapperCache* aCache)
+{
+ return aCache->PreservingWrapper() ? aCache->GetWrapperPreserveColor() : nullptr;
+}
+
+static bool
+NeedsScriptTraverse(nsINode* aNode)
+{
+ return aNode->PreservingWrapper() && aNode->GetWrapperPreserveColor() &&
+ !aNode->IsBlackAndDoesNotNeedTracing(aNode);
+}
+
+//----------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsChildContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsChildContentList)
+
+// If nsChildContentList is changed so that any additional fields are
+// traversed by the cycle collector, then CAN_SKIP must be updated to
+// check that the additional fields are null.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsChildContentList)
+
+// nsChildContentList only ever has a single child, its wrapper, so if
+// the wrapper is black, the list can't be part of a garbage cycle.
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsChildContentList)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsChildContentList)
+ return tmp->IsBlackAndDoesNotNeedTracing(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+// CanSkipThis returns false to avoid problems with incomplete unlinking.
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsChildContentList)
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_INTERFACE_TABLE_HEAD(nsChildContentList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(nsChildContentList, nsINodeList, nsIDOMNodeList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsChildContentList)
+NS_INTERFACE_MAP_END
+
+JSObject*
+nsChildContentList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NodeListBinding::Wrap(cx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+nsChildContentList::GetLength(uint32_t* aLength)
+{
+ *aLength = mNode ? mNode->GetChildCount() : 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChildContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsINode* node = Item(aIndex);
+ if (!node) {
+ *aReturn = nullptr;
+
+ return NS_OK;
+ }
+
+ return CallQueryInterface(node, aReturn);
+}
+
+nsIContent*
+nsChildContentList::Item(uint32_t aIndex)
+{
+ if (mNode) {
+ return mNode->GetChildAt(aIndex);
+ }
+
+ return nullptr;
+}
+
+int32_t
+nsChildContentList::IndexOf(nsIContent* aContent)
+{
+ if (mNode) {
+ return mNode->IndexOf(aContent);
+ }
+
+ return -1;
+}
+
+//----------------------------------------------------------------------
+
+nsIHTMLCollection*
+FragmentOrElement::Children()
+{
+ FragmentOrElement::nsDOMSlots *slots = DOMSlots();
+
+ if (!slots->mChildrenList) {
+ slots->mChildrenList = new nsContentList(this, kNameSpaceID_Wildcard,
+ nsGkAtoms::_asterisk, nsGkAtoms::_asterisk,
+ false);
+ }
+
+ return slots->mChildrenList;
+}
+
+
+//----------------------------------------------------------------------
+
+
+NS_IMPL_ISUPPORTS(nsNodeWeakReference,
+ nsIWeakReference)
+
+nsNodeWeakReference::~nsNodeWeakReference()
+{
+ if (mNode) {
+ NS_ASSERTION(mNode->Slots()->mWeakReference == this,
+ "Weak reference has wrong value");
+ mNode->Slots()->mWeakReference = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsNodeWeakReference::QueryReferent(const nsIID& aIID, void** aInstancePtr)
+{
+ return mNode ? mNode->QueryInterface(aIID, aInstancePtr) :
+ NS_ERROR_NULL_POINTER;
+}
+
+size_t
+nsNodeWeakReference::SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+}
+
+
+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;
+}
+
+//----------------------------------------------------------------------
+FragmentOrElement::nsDOMSlots::nsDOMSlots()
+ : nsINode::nsSlots(),
+ mDataset(nullptr),
+ mBindingParent(nullptr)
+{
+}
+
+FragmentOrElement::nsDOMSlots::~nsDOMSlots()
+{
+ if (mAttributeMap) {
+ mAttributeMap->DropReference();
+ }
+}
+
+void
+FragmentOrElement::nsDOMSlots::Traverse(nsCycleCollectionTraversalCallback &cb, bool aIsXUL)
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mStyle");
+ cb.NoteXPCOMChild(mStyle.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mSMILOverrideStyle");
+ cb.NoteXPCOMChild(mSMILOverrideStyle.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mAttributeMap");
+ cb.NoteXPCOMChild(mAttributeMap.get());
+
+ if (aIsXUL) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mControllers");
+ cb.NoteXPCOMChild(mControllers);
+ }
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLBinding");
+ cb.NoteNativeChild(mXBLBinding, NS_CYCLE_COLLECTION_PARTICIPANT(nsXBLBinding));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLInsertionParent");
+ cb.NoteXPCOMChild(mXBLInsertionParent.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mShadowRoot");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mShadowRoot));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mContainingShadow");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildrenList");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mChildrenList));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mClassList");
+ cb.NoteXPCOMChild(mClassList.get());
+
+ if (mCustomElementData) {
+ for (uint32_t i = 0; i < mCustomElementData->mCallbackQueue.Length(); i++) {
+ mCustomElementData->mCallbackQueue[i]->Traverse(cb);
+ }
+ }
+}
+
+void
+FragmentOrElement::nsDOMSlots::Unlink(bool aIsXUL)
+{
+ mStyle = nullptr;
+ mSMILOverrideStyle = nullptr;
+ if (mAttributeMap) {
+ mAttributeMap->DropReference();
+ mAttributeMap = nullptr;
+ }
+ if (aIsXUL)
+ NS_IF_RELEASE(mControllers);
+
+ MOZ_ASSERT(!mXBLBinding);
+
+ mXBLInsertionParent = nullptr;
+ mShadowRoot = nullptr;
+ mContainingShadow = nullptr;
+ mChildrenList = nullptr;
+ mCustomElementData = nullptr;
+ mClassList = nullptr;
+}
+
+size_t
+FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ if (mAttributeMap) {
+ n += mAttributeMap->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - Superclass members (nsINode::nsSlots)
+ // - mStyle
+ // - mDataSet
+ // - mSMILOverrideStyle
+ // - mSMILOverrideStyleDeclaration
+ // - mChildrenList
+ // - mClassList
+
+ // The following members are not measured:
+ // - mBindingParent / mControllers: because they're non-owning
+ return n;
+}
+
+FragmentOrElement::FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsIContent(aNodeInfo)
+{
+}
+
+FragmentOrElement::FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsIContent(aNodeInfo)
+{
+}
+
+FragmentOrElement::~FragmentOrElement()
+{
+ NS_PRECONDITION(!IsInUncomposedDoc(),
+ "Please remove this from the document properly");
+ if (GetParent()) {
+ NS_RELEASE(mParent);
+ }
+}
+
+already_AddRefed<nsINodeList>
+FragmentOrElement::GetChildren(uint32_t aFilter)
+{
+ RefPtr<nsSimpleContentList> list = new nsSimpleContentList(this);
+ AllChildrenIterator iter(this, aFilter);
+ while (nsIContent* kid = iter.GetNextChild()) {
+ list->AppendElement(kid);
+ }
+
+ return list.forget();
+}
+
+static nsIContent*
+FindChromeAccessOnlySubtreeOwner(nsIContent* aContent)
+{
+ if (aContent->ChromeOnlyAccess()) {
+ bool chromeAccessOnly = false;
+ while (aContent && !chromeAccessOnly) {
+ chromeAccessOnly = aContent->IsRootOfChromeAccessOnlySubtree();
+ aContent = aContent->GetParent();
+ }
+ }
+ return aContent;
+}
+
+nsresult
+nsIContent::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ //FIXME! Document how this event retargeting works, Bug 329124.
+ aVisitor.mCanHandle = true;
+ aVisitor.mMayHaveListenerManager = HasListenerManager();
+
+ // Don't propagate mouseover and mouseout events when mouse is moving
+ // inside chrome access only content.
+ bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
+ 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 || GetShadowRoot())) {
+ nsCOMPtr<nsIContent> relatedTarget =
+ do_QueryInterface(aVisitor.mEvent->AsMouseEvent()->relatedTarget);
+ if (relatedTarget &&
+ relatedTarget->OwnerDoc() == OwnerDoc()) {
+
+ // In the web components case, we may need to stop propagation of events
+ // at shadow root host.
+ if (GetShadowRoot()) {
+ nsIContent* adjustedTarget =
+ Event::GetShadowRelatedTarget(this, relatedTarget);
+ if (this == adjustedTarget) {
+ aVisitor.mParentTarget = nullptr;
+ aVisitor.mCanHandle = false;
+ return NS_OK;
+ }
+ }
+
+ // 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()))) {
+ nsIContent* anonOwner = FindChromeAccessOnlySubtreeOwner(this);
+ if (anonOwner) {
+ nsIContent* 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 =
+ do_QueryInterface(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.mParentTarget = nullptr;
+ // Event should not propagate to non-anon content.
+ aVisitor.mCanHandle = isAnonForEvents;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nsIContent* parent = GetParent();
+
+ // Web components have a special event chain that need to account
+ // for destination insertion points where nodes have been distributed.
+ nsTArray<nsIContent*>* destPoints = GetExistingDestInsertionPoints();
+ if (destPoints && !destPoints->IsEmpty()) {
+ // Push destination insertion points to aVisitor.mDestInsertionPoints
+ // excluding shadow insertion points.
+ bool didPushNonShadowInsertionPoint = false;
+ for (uint32_t i = 0; i < destPoints->Length(); i++) {
+ nsIContent* point = destPoints->ElementAt(i);
+ if (!ShadowRoot::IsShadowInsertionPoint(point)) {
+ aVisitor.mDestInsertionPoints.AppendElement(point);
+ didPushNonShadowInsertionPoint = true;
+ }
+ }
+
+ // Next node in the event path is the final destination
+ // (non-shadow) insertion point that was pushed.
+ if (didPushNonShadowInsertionPoint) {
+ parent = aVisitor.mDestInsertionPoints.LastElement();
+ aVisitor.mDestInsertionPoints.SetLength(
+ aVisitor.mDestInsertionPoints.Length() - 1);
+ }
+ }
+
+ ShadowRoot* thisShadowRoot = ShadowRoot::FromNode(this);
+ if (thisShadowRoot) {
+ if (!aVisitor.mEvent->mFlags.mComposed) {
+ // 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.mParentTarget = parentTarget;
+ return NS_OK;
+ }
+
+ if (!aVisitor.mDestInsertionPoints.IsEmpty()) {
+ parent = aVisitor.mDestInsertionPoints.LastElement();
+ aVisitor.mDestInsertionPoints.SetLength(
+ aVisitor.mDestInsertionPoints.Length() - 1);
+ } else {
+ // The pool host for the youngest shadow root is shadow DOM host,
+ // for older shadow roots, it is the shadow insertion point
+ // where the shadow root is projected, nullptr if none exists.
+ parent = thisShadowRoot->GetPoolHost();
+ }
+ }
+
+ // 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 =
+ do_QueryInterface(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(do_QueryInterface(aVisitor.mEvent->mTarget));
+ if (content && content->GetBindingParent() == parent) {
+ aVisitor.mEventTargetAtParent = parent;
+ }
+ }
+
+ // check for an anonymous parent
+ // XXX XBL2/sXBL issue
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsIContent* insertionParent = GetXBLInsertionParent();
+ NS_ASSERTION(!(aVisitor.mEventTargetAtParent && insertionParent &&
+ aVisitor.mEventTargetAtParent != insertionParent),
+ "Retargeting and having insertion parent!");
+ if (insertionParent) {
+ parent = insertionParent;
+ }
+ }
+
+ if (!aVisitor.mEvent->mFlags.mComposedInNativeAnonymousContent &&
+ IsRootOfNativeAnonymousSubtree() && OwnerDoc() &&
+ OwnerDoc()->GetWindow()) {
+ aVisitor.mParentTarget = OwnerDoc()->GetWindow()->GetParentTarget();
+ } else if (parent) {
+ aVisitor.mParentTarget = parent;
+ } else {
+ aVisitor.mParentTarget = GetComposedDoc();
+ }
+ return NS_OK;
+}
+
+bool
+nsIContent::GetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAString& aResult) const
+{
+ if (IsElement()) {
+ return AsElement()->GetAttr(aNameSpaceID, aName, aResult);
+ }
+ aResult.Truncate();
+ return false;
+}
+
+bool
+nsIContent::HasAttr(int32_t aNameSpaceID, nsIAtom* aName) const
+{
+ return IsElement() && AsElement()->HasAttr(aNameSpaceID, aName);
+}
+
+bool
+nsIContent::AttrValueIs(int32_t aNameSpaceID,
+ nsIAtom* aName,
+ const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const
+{
+ return IsElement() &&
+ AsElement()->AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive);
+}
+
+bool
+nsIContent::AttrValueIs(int32_t aNameSpaceID,
+ nsIAtom* aName,
+ nsIAtom* aValue,
+ nsCaseTreatment aCaseSensitive) const
+{
+ return IsElement() &&
+ AsElement()->AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive);
+}
+
+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)) {
+ if (nsContentUtils::IsUserFocusIgnored(this)) {
+ if (aTabIndex) {
+ *aTabIndex = -1;
+ }
+ return false;
+ }
+ return focusable;
+ }
+ return false;
+}
+
+bool
+nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse)
+{
+ if (aTabIndex) {
+ *aTabIndex = -1; // Default, not tabbable
+ }
+ return false;
+}
+
+NS_IMETHODIMP
+FragmentOrElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
+{
+ return NS_OK;
+}
+
+bool
+FragmentOrElement::IsLink(nsIURI** aURI) const
+{
+ *aURI = nullptr;
+ return false;
+}
+
+nsIContent*
+FragmentOrElement::GetBindingParent() const
+{
+ nsDOMSlots *slots = GetExistingDOMSlots();
+
+ if (slots) {
+ return slots->mBindingParent;
+ }
+ return nullptr;
+}
+
+nsXBLBinding*
+FragmentOrElement::GetXBLBinding() const
+{
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return slots->mXBLBinding;
+ }
+ }
+
+ return nullptr;
+}
+
+void
+FragmentOrElement::SetXBLBinding(nsXBLBinding* aBinding,
+ nsBindingManager* aOldBindingManager)
+{
+ nsBindingManager* bindingManager;
+ if (aOldBindingManager) {
+ MOZ_ASSERT(!aBinding, "aOldBindingManager should only be provided "
+ "when removing a binding.");
+ bindingManager = aOldBindingManager;
+ } else {
+ bindingManager = OwnerDoc()->BindingManager();
+ }
+
+ // After this point, aBinding will be the most-derived binding for aContent.
+ // If we already have a binding for aContent, make sure to
+ // remove it from the attached stack. Otherwise we might end up firing its
+ // constructor twice (if aBinding inherits from it) or firing its constructor
+ // after aContent has been deleted (if aBinding is null and the content node
+ // dies before we process mAttachedStack).
+ RefPtr<nsXBLBinding> oldBinding = GetXBLBinding();
+ if (oldBinding) {
+ bindingManager->RemoveFromAttachedQueue(oldBinding);
+ }
+
+ if (aBinding) {
+ SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
+ nsDOMSlots *slots = DOMSlots();
+ slots->mXBLBinding = aBinding;
+ bindingManager->AddBoundContent(this);
+ } else {
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ slots->mXBLBinding = nullptr;
+ }
+ bindingManager->RemoveBoundContent(this);
+ if (oldBinding) {
+ oldBinding->SetBoundElement(nullptr);
+ }
+ }
+}
+
+nsIContent*
+FragmentOrElement::GetXBLInsertionParent() const
+{
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return slots->mXBLInsertionParent;
+ }
+ }
+
+ return nullptr;
+}
+
+ShadowRoot*
+FragmentOrElement::GetContainingShadow() const
+{
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return slots->mContainingShadow;
+ }
+ return nullptr;
+}
+
+void
+FragmentOrElement::SetShadowRoot(ShadowRoot* aShadowRoot)
+{
+ nsDOMSlots *slots = DOMSlots();
+ slots->mShadowRoot = aShadowRoot;
+}
+
+nsTArray<nsIContent*>&
+FragmentOrElement::DestInsertionPoints()
+{
+ nsDOMSlots *slots = DOMSlots();
+ return slots->mDestInsertionPoints;
+}
+
+nsTArray<nsIContent*>*
+FragmentOrElement::GetExistingDestInsertionPoints() const
+{
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return &slots->mDestInsertionPoints;
+ }
+ return nullptr;
+}
+
+void
+FragmentOrElement::SetXBLInsertionParent(nsIContent* aContent)
+{
+ if (aContent) {
+ nsDOMSlots *slots = DOMSlots();
+ SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
+ slots->mXBLInsertionParent = aContent;
+ } else {
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ slots->mXBLInsertionParent = nullptr;
+ }
+ }
+}
+
+CustomElementData*
+FragmentOrElement::GetCustomElementData() const
+{
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return slots->mCustomElementData;
+ }
+ return nullptr;
+}
+
+void
+FragmentOrElement::SetCustomElementData(CustomElementData* aData)
+{
+ nsDOMSlots *slots = DOMSlots();
+ MOZ_ASSERT(!slots->mCustomElementData, "Custom element data may not be changed once set.");
+ slots->mCustomElementData = aData;
+}
+
+nsresult
+FragmentOrElement::InsertChildAt(nsIContent* aKid,
+ uint32_t aIndex,
+ bool aNotify)
+{
+ NS_PRECONDITION(aKid, "null ptr");
+
+ return doInsertChildAt(aKid, aIndex, aNotify, mAttrsAndChildren);
+}
+
+void
+FragmentOrElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+ nsCOMPtr<nsIContent> oldKid = mAttrsAndChildren.GetSafeChildAt(aIndex);
+ NS_ASSERTION(oldKid == GetChildAt(aIndex), "Unexpected child in RemoveChildAt");
+
+ if (oldKid) {
+ doRemoveChildAt(aIndex, aNotify, oldKid, mAttrsAndChildren);
+ }
+}
+
+void
+FragmentOrElement::GetTextContentInternal(nsAString& aTextContent,
+ ErrorResult& aError)
+{
+ if (!nsContentUtils::GetNodeTextContent(this, true, aTextContent, fallible)) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+}
+
+void
+FragmentOrElement::SetTextContentInternal(const nsAString& aTextContent,
+ ErrorResult& aError)
+{
+ aError = nsContentUtils::SetNodeTextContent(this, aTextContent, false);
+}
+
+void
+FragmentOrElement::DestroyContent()
+{
+ nsIDocument *document = OwnerDoc();
+ document->BindingManager()->RemovedFromDocument(this, document,
+ nsBindingManager::eRunDtor);
+ document->ClearBoxObjectFor(this);
+
+ uint32_t i, count = mAttrsAndChildren.ChildCount();
+ for (i = 0; i < count; ++i) {
+ // The child can remove itself from the parent in BindToTree.
+ mAttrsAndChildren.ChildAt(i)->DestroyContent();
+ }
+ ShadowRoot* shadowRoot = GetShadowRoot();
+ if (shadowRoot) {
+ shadowRoot->DestroyContent();
+ }
+}
+
+void
+FragmentOrElement::SaveSubtreeState()
+{
+ uint32_t i, count = mAttrsAndChildren.ChildCount();
+ for (i = 0; i < count; ++i) {
+ mAttrsAndChildren.ChildAt(i)->SaveSubtreeState();
+ }
+}
+
+//----------------------------------------------------------------------
+
+// Generic DOMNode implementations
+
+void
+FragmentOrElement::FireNodeInserted(nsIDocument* aDoc,
+ nsINode* aParent,
+ nsTArray<nsCOMPtr<nsIContent> >& aNodes)
+{
+ uint32_t count = aNodes.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIContent* childContent = aNodes[i];
+
+ if (nsContentUtils::HasMutationListeners(childContent,
+ NS_EVENT_BITS_MUTATION_NODEINSERTED, aParent)) {
+ InternalMutationEvent mutation(true, eLegacyNodeInserted);
+ mutation.mRelatedNode = do_QueryInterface(aParent);
+
+ mozAutoSubtreeModified subtree(aDoc, aParent);
+ (new AsyncEventDispatcher(childContent, mutation))->RunDOMEventWhenSafe();
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+
+// nsISupports implementation
+
+#define SUBTREE_UNBINDINGS_PER_RUNNABLE 500
+
+class ContentUnbinder : public Runnable
+{
+public:
+ ContentUnbinder()
+ {
+ mLast = this;
+ }
+
+ ~ContentUnbinder()
+ {
+ Run();
+ }
+
+ void UnbindSubtree(nsIContent* aNode)
+ {
+ if (aNode->NodeType() != nsIDOMNode::ELEMENT_NODE &&
+ aNode->NodeType() != nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+ return;
+ }
+ FragmentOrElement* container = static_cast<FragmentOrElement*>(aNode);
+ uint32_t childCount = container->mAttrsAndChildren.ChildCount();
+ if (childCount) {
+ while (childCount-- > 0) {
+ // Hold a strong ref to the node when we remove it, because we may be
+ // the last reference to it. We need to call TakeChildAt() and
+ // update mFirstChild before calling UnbindFromTree, since this last
+ // can notify various observers and they should really see consistent
+ // tree state.
+ nsCOMPtr<nsIContent> child =
+ container->mAttrsAndChildren.TakeChildAt(childCount);
+ if (childCount == 0) {
+ container->mFirstChild = nullptr;
+ }
+ 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_DispatchToMainThread(next);
+ }
+ }
+ 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_DispatchToMainThread(e);
+ }
+
+ 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();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FragmentOrElement)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
+ nsINode::Unlink(tmp);
+
+ // The XBL binding is removed by RemoveFromBindingManagerRunnable
+ // which is dispatched in UnbindFromTree.
+
+ if (tmp->HasProperties()) {
+ if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
+ nsIAtom*** props = Element::HTMLSVGPropertiesToTraverseAndUnlink();
+ for (uint32_t i = 0; props[i]; ++i) {
+ tmp->DeleteProperty(*props[i]);
+ }
+ if (tmp->MayHaveAnimations()) {
+ nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
+ for (uint32_t i = 0; effectProps[i]; ++i) {
+ tmp->DeleteProperty(effectProps[i]);
+ }
+ }
+ }
+ }
+
+ // Unlink child content (and unbind our subtree).
+ if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) {
+ uint32_t childCount = tmp->mAttrsAndChildren.ChildCount();
+ if (childCount) {
+ // Don't allow script to run while we're unbinding everything.
+ nsAutoScriptBlocker scriptBlocker;
+ while (childCount-- > 0) {
+ // Hold a strong ref to the node when we remove it, because we may be
+ // the last reference to it. We need to call TakeChildAt() and
+ // update mFirstChild before calling UnbindFromTree, since this last
+ // can notify various observers and they should really see consistent
+ // tree state.
+ nsCOMPtr<nsIContent> child = tmp->mAttrsAndChildren.TakeChildAt(childCount);
+ if (childCount == 0) {
+ tmp->mFirstChild = nullptr;
+ }
+ child->UnbindFromTree();
+ }
+ }
+ } else if (!tmp->GetParent() && tmp->mAttrsAndChildren.ChildCount()) {
+ ContentUnbinder::Append(tmp);
+ } /* else {
+ The subtree root will end up to a ContentUnbinder, and that will
+ unbind the child nodes.
+ } */
+
+ // Clear flag here because unlinking slots will clear the
+ // containing shadow root pointer.
+ tmp->UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ nsIDocument* doc = tmp->OwnerDoc();
+ doc->BindingManager()->RemovedFromDocument(tmp, doc,
+ nsBindingManager::eDoNotRunDtor);
+
+ // Unlink any DOM slots of interest.
+ {
+ nsDOMSlots *slots = tmp->GetExistingDOMSlots();
+ if (slots) {
+ slots->Unlink(tmp->IsXULElement());
+ }
+ }
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FragmentOrElement)
+
+void
+FragmentOrElement::MarkUserData(void* aObject, nsIAtom* aKey, void* aChild,
+ void* aData)
+{
+ uint32_t* gen = static_cast<uint32_t*>(aData);
+ xpc_MarkInCCGeneration(static_cast<nsISupports*>(aChild), *gen);
+}
+
+void
+FragmentOrElement::MarkNodeChildren(nsINode* aNode)
+{
+ JSObject* o = GetJSObjectChild(aNode);
+ if (o) {
+ JS::ExposeObjectToActiveJS(o);
+ }
+
+ EventListenerManager* elm = aNode->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+
+ if (aNode->HasProperties()) {
+ nsIDocument* ownerDoc = aNode->OwnerDoc();
+ ownerDoc->PropertyTable(DOM_USER_DATA)->
+ Enumerate(aNode, FragmentOrElement::MarkUserData,
+ &nsCCUncollectableMarker::sGeneration);
+ }
+}
+
+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<nsTHashtable<nsPtrHashKey<nsINode>>> gCCBlackMarkedNodes;
+
+static void
+ClearBlackMarkedNodes()
+{
+ if (!gCCBlackMarkedNodes) {
+ return;
+ }
+ for (auto iter = gCCBlackMarkedNodes->ConstIter(); !iter.Done();
+ iter.Next()) {
+ nsINode* n = iter.Get()->GetKey();
+ n->SetCCMarkedRoot(false);
+ n->SetInCCBlackTree(false);
+ }
+ gCCBlackMarkedNodes = nullptr;
+}
+
+// static
+void
+FragmentOrElement::RemoveBlackMarkedNode(nsINode* aNode)
+{
+ if (!gCCBlackMarkedNodes) {
+ return;
+ }
+ gCCBlackMarkedNodes->RemoveEntry(aNode);
+}
+
+static bool
+IsCertainlyAliveNode(nsINode* aNode, nsIDocument* aDoc)
+{
+ MOZ_ASSERT(aNode->GetUncomposedDoc() == 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;
+ }
+
+ //XXXsmaug Need to figure out in which cases Shadow DOM can be optimized out
+ // from the CC graph.
+ nsIDocument* currentDoc = aNode->GetUncomposedDoc();
+ 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 nsTHashtable<nsPtrHashKey<nsINode> >(1020);
+ }
+
+ // nodesToUnpurple contains nodes which will be removed
+ // from the purple buffer if the DOM tree is black.
+ AutoTArray<nsIContent*, 1020> nodesToUnpurple;
+ // grayNodes need script traverse, so they aren't removed from
+ // the purple buffer, but are marked to be in black subtree so that
+ // traverse is faster.
+ AutoTArray<nsINode*, 1020> grayNodes;
+
+ bool foundBlack = root->IsBlack();
+ 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 black.
+ // 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)) {
+ foundBlack = foundBlack || node->IsBlack();
+ if (foundBlack && currentDoc) {
+ // If we can mark the whole document black, 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(foundBlack);
+ gCCBlackMarkedNodes->PutEntry(root);
+
+ if (!foundBlack) {
+ return false;
+ }
+
+ if (currentDoc) {
+ // Special case documents. If we know the document is black,
+ // 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->PutEntry(node);
+ }
+ }
+
+ // Subtree is black, 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(nsIDocument* aCurrentDoc, nsINode* aNode)
+{
+ return aCurrentDoc->GetShell() && aNode->IsElement() &&
+ aNode->AsElement()->GetPrimaryFrame();
+}
+
+bool
+OwnedByBindingManager(nsIDocument* aCurrentDoc, nsINode* aNode)
+{
+ return aNode->IsElement() && aNode->AsElement()->GetXBLBinding();
+}
+
+// CanSkip checks if aNode is black, and if it is, returns
+// true. If aNode is in a black 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 blackness 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();
+ nsIDocument* currentDoc = aNode->GetUncomposedDoc();
+ if (currentDoc && IsCertainlyAliveNode(aNode, currentDoc) &&
+ (!unoptimizable || NodeHasActiveFrame(currentDoc, aNode) ||
+ OwnedByBindingManager(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 foundBlack = root->IsBlack();
+ bool domOnlyCycle = false;
+ if (root != currentDoc) {
+ currentDoc = nullptr;
+ if (!foundBlack) {
+ 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 black.
+ // 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)) {
+ foundBlack = foundBlack || node->IsBlack();
+ if (foundBlack) {
+ domOnlyCycle = false;
+ if (currentDoc) {
+ // If we can mark the whole document black, 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 black tree or are in a DOM-only cycle.
+ nodesToClear.AppendElement(node);
+ }
+ }
+ }
+
+ if (!currentDoc || !foundBlack) {
+ 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 (!foundBlack) {
+ return false;
+ }
+
+ if (currentDoc) {
+ // Special case documents. If we know the document is black,
+ // we can mark the document to be in CCGeneration.
+ currentDoc->
+ MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration);
+ MarkNodeChildren(currentDoc);
+ }
+
+ // Subtree is black, 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->IsBlack()) {
+ return true;
+ }
+ nsIDocument* c = aNode->GetUncomposedDoc();
+ 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
+
+static const char* kNSURIs[] = {
+ " ([none])",
+ " (xmlns)",
+ " (xml)",
+ " (xhtml)",
+ " (XLink)",
+ " (XSLT)",
+ " (XBL)",
+ " (MathML)",
+ " (RDF)",
+ " (XUL)",
+ " (SVG)",
+ " (XML Events)"
+};
+
+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;
+ nsIAtom* idAtom = tmp->GetID();
+ if (idAtom) {
+ id.AppendLiteral(" id='");
+ id.Append(nsDependentAtomString(idAtom));
+ id.Append('\'');
+ }
+
+ nsAutoString classes;
+ const nsAttrValue* classAttrValue = tmp->GetClasses();
+ 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->IsInUncomposedDoc() &&
+ // Ignore xbl:content, which is never in the document and hence always
+ // appears to be orphaned.
+ !tmp->NodeInfo()->Equals(nsGkAtoms::content, kNameSpaceID_XBL)) {
+ orphan.AppendLiteral(" (orphan)");
+ }
+
+ const char* nsuri = nsid < ArrayLength(kNSURIs) ? kNSURIs[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())
+ }
+
+ // Always need to traverse script objects, so do that before we check
+ // if we're uncollectable.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+
+ if (!nsINode::Traverse(tmp, cb)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+
+ tmp->OwnerDoc()->BindingManager()->Traverse(tmp, cb);
+
+ // Check that whenever we have effect properties, MayHaveAnimations is set.
+#ifdef DEBUG
+ nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
+ for (uint32_t i = 0; effectProps[i]; ++i) {
+ MOZ_ASSERT_IF(tmp->GetProperty(effectProps[i]), tmp->MayHaveAnimations());
+ }
+#endif
+
+ if (tmp->HasProperties()) {
+ if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
+ nsIAtom*** 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->MayHaveAnimations()) {
+ nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
+ for (uint32_t i = 0; effectProps[i]; ++i) {
+ EffectSet* effectSet =
+ static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
+ if (effectSet) {
+ effectSet->Traverse(cb);
+ }
+ }
+ }
+ }
+ }
+
+ // Traverse attribute names and child content.
+ {
+ uint32_t i;
+ uint32_t attrs = tmp->mAttrsAndChildren.AttrCount();
+ for (i = 0; i < attrs; i++) {
+ const nsAttrName* name = tmp->mAttrsAndChildren.AttrNameAt(i);
+ if (!name->IsAtom()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mAttrsAndChildren[i]->NodeInfo()");
+ cb.NoteNativeChild(name->NodeInfo(),
+ NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
+ }
+ }
+
+ uint32_t kids = tmp->mAttrsAndChildren.ChildCount();
+ for (i = 0; i < kids; i++) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mAttrsAndChildren[i]");
+ cb.NoteXPCOMChild(tmp->mAttrsAndChildren.GetSafeChildAt(i));
+ }
+ }
+
+ // Traverse any DOM slots of interest.
+ {
+ nsDOMSlots *slots = tmp->GetExistingDOMSlots();
+ if (slots) {
+ slots->Traverse(cb, tmp->IsXULElement());
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+
+NS_INTERFACE_MAP_BEGIN(FragmentOrElement)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(FragmentOrElement)
+ NS_INTERFACE_MAP_ENTRY(Element)
+ NS_INTERFACE_MAP_ENTRY(nsIContent)
+ NS_INTERFACE_MAP_ENTRY(nsINode)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
+ 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_CYCLE_COLLECTING_ADDREF(FragmentOrElement)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(FragmentOrElement,
+ nsNodeUtils::LastRelease(this))
+
+//----------------------------------------------------------------------
+
+nsresult
+FragmentOrElement::CopyInnerTo(FragmentOrElement* aDst)
+{
+ uint32_t i, count = mAttrsAndChildren.AttrCount();
+ for (i = 0; i < count; ++i) {
+ const nsAttrName* name = mAttrsAndChildren.AttrNameAt(i);
+ const nsAttrValue* value = mAttrsAndChildren.AttrAt(i);
+ nsAutoString valStr;
+ value->ToString(valStr);
+ nsresult rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valStr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+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
+ NS_NOTREACHED("called FragmentOrElement::TextLength");
+
+ return 0;
+}
+
+nsresult
+FragmentOrElement::SetText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify)
+{
+ NS_ERROR("called FragmentOrElement::SetText");
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+FragmentOrElement::AppendText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify)
+{
+ NS_ERROR("called FragmentOrElement::AppendText");
+
+ return NS_ERROR_FAILURE;
+}
+
+bool
+FragmentOrElement::TextIsOnlyWhitespace()
+{
+ return false;
+}
+
+bool
+FragmentOrElement::HasTextForTranslation()
+{
+ return false;
+}
+
+void
+FragmentOrElement::AppendTextTo(nsAString& aResult)
+{
+ // We can remove this assertion if it turns out to be useful to be able
+ // to depend on this appending nothing.
+ NS_NOTREACHED("called FragmentOrElement::TextLength");
+}
+
+bool
+FragmentOrElement::AppendTextTo(nsAString& aResult, const mozilla::fallible_t&)
+{
+ // We can remove this assertion if it turns out to be useful to be able
+ // to depend on this appending nothing.
+ NS_NOTREACHED("called FragmentOrElement::TextLength");
+
+ return false;
+}
+
+uint32_t
+FragmentOrElement::GetChildCount() const
+{
+ return mAttrsAndChildren.ChildCount();
+}
+
+nsIContent *
+FragmentOrElement::GetChildAt(uint32_t aIndex) const
+{
+ return mAttrsAndChildren.GetSafeChildAt(aIndex);
+}
+
+nsIContent * const *
+FragmentOrElement::GetChildArray(uint32_t* aChildCount) const
+{
+ return mAttrsAndChildren.GetChildArray(aChildCount);
+}
+
+int32_t
+FragmentOrElement::IndexOf(const nsINode* aPossibleChild) const
+{
+ return mAttrsAndChildren.IndexOfChild(aPossibleChild);
+}
+
+static inline bool
+IsVoidTag(nsIAtom* aTag)
+{
+ static const nsIAtom* 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::BloomFilter<12, nsIAtom> 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(nsIAtom* aLocalName)
+{
+ return aLocalName && IsVoidTag(aLocalName);
+}
+
+void
+FragmentOrElement::GetMarkup(bool aIncludeSelf, nsAString& aMarkup)
+{
+ aMarkup.Truncate();
+
+ nsIDocument* 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_CreateInstance(PromiseFlatCString(
+ nsDependentCString(NS_DOC_ENCODER_CONTRACTID_BASE) +
+ 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_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "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);
+ nsIEditor* editor = elem ? elem->GetEditorInternal() : nullptr;
+ if (editor && editor->OutputsMozDirty()) {
+ flags &= ~nsIDocumentEncoder::OutputIgnoreMozDirty;
+ }
+ }
+
+ DebugOnly<nsresult> rv = docEncoder->NativeInit(doc, contentType, flags);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (aIncludeSelf) {
+ docEncoder->SetNativeNode(this);
+ } else {
+ docEncoder->SetNativeContainerNode(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)
+{
+ FragmentOrElement* target = this;
+ // Handle template case.
+ if (nsNodeUtils::IsTemplateElement(target)) {
+ 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;
+ }
+
+ nsIDocument* 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, UPDATE_CONTENT_MODEL, true);
+
+ // Remove childnodes.
+ uint32_t childCount = target->GetChildCount();
+ nsAutoMutationBatch mb(target, true, false);
+ for (uint32_t i = 0; i < childCount; ++i) {
+ target->RemoveChildAt(0, true);
+ }
+ mb.RemovalDone();
+
+ nsAutoScriptLoaderDisabler sld(doc);
+
+ nsIAtom* contextLocalName = NodeInfo()->NameAtom();
+ int32_t contextNameSpaceID = GetNameSpaceID();
+
+ ShadowRoot* shadowRoot = ShadowRoot::FromNode(this);
+ if (shadowRoot) {
+ // Fix up the context to be the host of the ShadowRoot.
+ contextLocalName = shadowRoot->GetHost()->NodeInfo()->NameAtom();
+ contextNameSpaceID = shadowRoot->GetHost()->GetNameSpaceID();
+ }
+
+ if (doc->IsHTMLDocument()) {
+ 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(target, 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;
+
+ static_cast<nsINode*>(target)->AppendChild(*df, aError);
+ mb.NodesAdded();
+ }
+ }
+}
+
+nsINode::nsSlots*
+FragmentOrElement::CreateSlots()
+{
+ return new nsDOMSlots();
+}
+
+void
+FragmentOrElement::FireNodeRemovedForChildren()
+{
+ nsIDocument* doc = OwnerDoc();
+ // Optimize the common case
+ if (!nsContentUtils::
+ HasMutationListeners(doc, NS_EVENT_BITS_MUTATION_NODEREMOVED)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> owningDoc = doc;
+
+ nsCOMPtr<nsINode> child;
+ for (child = GetFirstChild();
+ child && child->GetParentNode() == this;
+ child = child->GetNextSibling()) {
+ nsContentUtils::MaybeFireNodeRemoved(child, this, doc);
+ }
+}
+
+size_t
+FragmentOrElement::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ n += nsIContent::SizeOfExcludingThis(aMallocSizeOf);
+ n += mAttrsAndChildren.SizeOfExcludingThis(aMallocSizeOf);
+
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ if (slots) {
+ n += slots->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+void
+FragmentOrElement::SetIsElementInStyleScopeFlagOnSubtree(bool aInStyleScope)
+{
+ if (aInStyleScope && IsElementInStyleScope()) {
+ return;
+ }
+
+ if (IsElement()) {
+ SetIsElementInStyleScope(aInStyleScope);
+ SetIsElementInStyleScopeFlagOnShadowTree(aInStyleScope);
+ }
+
+ nsIContent* n = GetNextNode(this);
+ while (n) {
+ if (n->IsElementInStyleScope()) {
+ n = n->GetNextNonChildNode(this);
+ } else {
+ if (n->IsElement()) {
+ n->SetIsElementInStyleScope(aInStyleScope);
+ n->AsElement()->SetIsElementInStyleScopeFlagOnShadowTree(aInStyleScope);
+ }
+ n = n->GetNextNode(this);
+ }
+ }
+}
+
+void
+FragmentOrElement::SetIsElementInStyleScopeFlagOnShadowTree(bool aInStyleScope)
+{
+ NS_ASSERTION(IsElement(), "calling SetIsElementInStyleScopeFlagOnShadowTree "
+ "on a non-Element is useless");
+ ShadowRoot* shadowRoot = GetShadowRoot();
+ while (shadowRoot) {
+ shadowRoot->SetIsElementInStyleScopeFlagOnSubtree(aInStyleScope);
+ shadowRoot = shadowRoot->GetOlderShadowRoot();
+ }
+}
+
+#ifdef DEBUG
+static void
+AssertDirtyDescendantsBitPropagated(nsINode* aNode)
+{
+ MOZ_ASSERT(aNode->HasDirtyDescendantsForServo());
+ nsINode* parent = aNode->GetFlattenedTreeParentNode();
+ if (!parent->IsContent()) {
+ MOZ_ASSERT(parent == aNode->OwnerDoc());
+ MOZ_ASSERT(parent->HasDirtyDescendantsForServo());
+ } else {
+ AssertDirtyDescendantsBitPropagated(parent);
+ }
+}
+#else
+static void AssertDirtyDescendantsBitPropagated(nsINode* aNode) {}
+#endif
+
+void
+nsIContent::MarkAncestorsAsHavingDirtyDescendantsForServo()
+{
+ MOZ_ASSERT(IsInComposedDoc());
+
+ // Get the parent in the flattened tree.
+ nsINode* parent = GetFlattenedTreeParentNode();
+
+ // Loop until we hit a base case.
+ while (true) {
+
+ // Base case: the document.
+ if (!parent->IsContent()) {
+ MOZ_ASSERT(parent == OwnerDoc());
+ parent->SetHasDirtyDescendantsForServo();
+ return;
+ }
+
+ // Base case: the parent is already marked, and therefore
+ // so are all its ancestors.
+ if (parent->HasDirtyDescendantsForServo()) {
+ AssertDirtyDescendantsBitPropagated(parent);
+ return;
+ }
+
+ // Mark the parent and iterate.
+ parent->SetHasDirtyDescendantsForServo();
+ parent = parent->GetFlattenedTreeParentNode();
+ }
+}
diff --git a/dom/base/FragmentOrElement.h b/dom/base/FragmentOrElement.h
new file mode 100644
index 000000000..3cb5575fe
--- /dev/null
+++ b/dom/base/FragmentOrElement.h
@@ -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/. */
+
+/*
+ * Base class for all element classes as well as nsDocumentFragment. This
+ * provides an implementation of nsIDOMNode, implements nsIContent, provides
+ * utility methods for subclasses, and so forth.
+ */
+
+#ifndef FragmentOrElement_h___
+#define FragmentOrElement_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsAttrAndChildArray.h" // member
+#include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_*
+#include "nsIContent.h" // base class
+#include "nsIWeakReference.h" // base class
+#include "nsNodeUtils.h" // class member nsNodeUtils::CloneNodeImpl
+#include "nsIHTMLCollection.h"
+
+class ContentUnbinder;
+class nsContentList;
+class nsDOMAttributeMap;
+class nsDOMTokenList;
+class nsIControllers;
+class nsICSSDeclaration;
+class nsIDocument;
+class nsDOMStringMap;
+class nsIURI;
+
+namespace mozilla {
+class DeclarationBlock;
+namespace dom {
+class DOMIntersectionObserver;
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * A class that implements nsIWeakReference
+ */
+
+class nsNodeWeakReference final : public nsIWeakReference
+{
+public:
+ explicit nsNodeWeakReference(nsINode* aNode)
+ : mNode(aNode)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIWeakReference
+ NS_DECL_NSIWEAKREFERENCE
+ virtual size_t SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ void NoticeNodeDestruction()
+ {
+ mNode = nullptr;
+ }
+
+private:
+ ~nsNodeWeakReference();
+
+ nsINode* MOZ_NON_OWNING_REF mNode;
+};
+
+/**
+ * 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() {}
+
+ nsCOMPtr<nsINode> mNode;
+};
+
+/**
+ * A generic base class for DOM elements, implementing many nsIContent,
+ * nsIDOMNode and nsIDOMElement methods.
+ */
+namespace mozilla {
+namespace dom {
+
+class ShadowRoot;
+
+class FragmentOrElement : public nsIContent
+{
+public:
+ explicit FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+ explicit FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ NS_DECL_SIZEOF_EXCLUDING_THIS
+
+ // nsINode interface methods
+ virtual uint32_t GetChildCount() const override;
+ virtual nsIContent *GetChildAt(uint32_t aIndex) const override;
+ virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const override;
+ virtual int32_t IndexOf(const nsINode* aPossibleChild) const override;
+ virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify) override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+ virtual void GetTextContentInternal(nsAString& aTextContent,
+ mozilla::ErrorResult& aError) override;
+ virtual void SetTextContentInternal(const nsAString& aTextContent,
+ mozilla::ErrorResult& aError) override;
+
+ // nsIContent interface methods
+ virtual already_AddRefed<nsINodeList> GetChildren(uint32_t aFilter) override;
+ virtual const nsTextFragment *GetText() override;
+ virtual uint32_t TextLength() const override;
+ virtual nsresult SetText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify) override;
+ // Need to implement this here too to avoid hiding.
+ nsresult SetText(const nsAString& aStr, bool aNotify)
+ {
+ return SetText(aStr.BeginReading(), aStr.Length(), aNotify);
+ }
+ virtual nsresult AppendText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify) override;
+ virtual bool TextIsOnlyWhitespace() override;
+ virtual bool HasTextForTranslation() override;
+ virtual void AppendTextTo(nsAString& aResult) override;
+ MOZ_MUST_USE
+ virtual bool AppendTextTo(nsAString& aResult, const mozilla::fallible_t&) override;
+ virtual nsIContent *GetBindingParent() const override;
+ virtual nsXBLBinding *GetXBLBinding() const override;
+ virtual void SetXBLBinding(nsXBLBinding* aBinding,
+ nsBindingManager* aOldBindingManager = nullptr) override;
+ virtual ShadowRoot *GetContainingShadow() const override;
+ virtual nsTArray<nsIContent*> &DestInsertionPoints() override;
+ virtual nsTArray<nsIContent*> *GetExistingDestInsertionPoints() const override;
+ virtual void SetShadowRoot(ShadowRoot* aBinding) override;
+ virtual nsIContent *GetXBLInsertionParent() const override;
+ virtual void SetXBLInsertionParent(nsIContent* aContent) override;
+ virtual bool IsLink(nsIURI** aURI) const override;
+
+ virtual CustomElementData *GetCustomElementData() const override;
+ virtual void SetCustomElementData(CustomElementData* aData) override;
+
+ virtual void DestroyContent() override;
+ virtual void SaveSubtreeState() override;
+
+ NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) override;
+
+ nsIHTMLCollection* Children();
+ uint32_t ChildElementCount()
+ {
+ return Children()->Length();
+ }
+
+ /**
+ * Sets the IsElementInStyleScope flag on each element in the subtree rooted
+ * at this node, including any elements reachable through shadow trees.
+ *
+ * @param aInStyleScope The flag value to set.
+ */
+ void SetIsElementInStyleScopeFlagOnSubtree(bool aInStyleScope);
+
+public:
+ /**
+ * If there are listeners for DOMNodeInserted event, fires the event on all
+ * aNodes
+ */
+ static void FireNodeInserted(nsIDocument* aDoc,
+ nsINode* aParent,
+ nsTArray<nsCOMPtr<nsIContent> >& aNodes);
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(FragmentOrElement)
+
+ /**
+ * Fire a DOMNodeRemoved mutation event for all children of this node
+ */
+ void FireNodeRemovedForChildren();
+
+ virtual bool OwnedOnlyByTheDOMTree() override
+ {
+ uint32_t rc = mRefCnt.get();
+ if (GetParent()) {
+ --rc;
+ }
+ rc -= mAttrsAndChildren.ChildCount();
+ return rc == 0;
+ }
+
+ virtual bool IsPurple() override
+ {
+ return mRefCnt.IsPurple();
+ }
+
+ virtual void RemovePurple() override
+ {
+ mRefCnt.RemovePurple();
+ }
+
+ 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();
+ static void MarkUserData(void* aObject, nsIAtom* aKey, void* aChild,
+ void *aData);
+
+ /**
+ * Is the HTML local name a void element?
+ */
+ static bool IsHTMLVoid(nsIAtom* aLocalName);
+protected:
+ virtual ~FragmentOrElement();
+
+ /**
+ * Copy attributes and state to another element
+ * @param aDest the object to copy to
+ */
+ nsresult CopyInnerTo(FragmentOrElement* aDest);
+
+public:
+ // Because of a bug in MS C++ compiler nsDOMSlots must be declared public,
+ // otherwise nsXULElement::nsXULSlots doesn't compile.
+ /**
+ * 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 nsDOMSlots : public nsINode::nsSlots
+ {
+ public:
+ nsDOMSlots();
+ virtual ~nsDOMSlots();
+
+ void Traverse(nsCycleCollectionTraversalCallback &cb, bool aIsXUL);
+ void Unlink(bool aIsXUL);
+
+ 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]
+
+ /**
+ * SMIL Overridde style rules (for SMIL animation of CSS properties)
+ * @see nsIContent::GetSMILOverrideStyle
+ */
+ nsCOMPtr<nsICSSDeclaration> mSMILOverrideStyle;
+
+ /**
+ * Holds any SMIL override style declaration for this element.
+ */
+ RefPtr<mozilla::DeclarationBlock> mSMILOverrideStyleDeclaration;
+
+ /**
+ * An object implementing nsIDOMMozNamedAttrMap for this content (attributes)
+ * @see FragmentOrElement::GetAttributes
+ */
+ RefPtr<nsDOMAttributeMap> mAttributeMap;
+
+ union {
+ /**
+ * The nearest enclosing content node with a binding that created us.
+ * @see FragmentOrElement::GetBindingParent
+ */
+ nsIContent* mBindingParent; // [Weak]
+
+ /**
+ * The controllers of the XUL Element.
+ */
+ nsIControllers* mControllers; // [OWNER]
+ };
+
+ /**
+ * An object implementing the .children property for this element.
+ */
+ RefPtr<nsContentList> mChildrenList;
+
+ /**
+ * An object implementing the .classList property for this element.
+ */
+ RefPtr<nsDOMTokenList> mClassList;
+
+ /**
+ * ShadowRoot bound to the element.
+ */
+ RefPtr<ShadowRoot> mShadowRoot;
+
+ /**
+ * The root ShadowRoot of this element if it is in a shadow tree.
+ */
+ RefPtr<ShadowRoot> mContainingShadow;
+
+ /**
+ * An array of web component insertion points to which this element
+ * is distributed.
+ */
+ nsTArray<nsIContent*> mDestInsertionPoints;
+
+ /**
+ * XBL binding installed on the element.
+ */
+ RefPtr<nsXBLBinding> mXBLBinding;
+
+ /**
+ * XBL binding installed on the lement.
+ */
+ nsCOMPtr<nsIContent> mXBLInsertionParent;
+
+ /**
+ * Web components custom element data.
+ */
+ RefPtr<CustomElementData> mCustomElementData;
+
+ /**
+ * Registered Intersection Observers on the element.
+ */
+ struct IntersectionObserverRegistration {
+ DOMIntersectionObserver* observer;
+ int32_t previousThreshold;
+ };
+
+ nsTArray<IntersectionObserverRegistration> mRegisteredIntersectionObservers;
+ };
+
+protected:
+ void GetMarkup(bool aIncludeSelf, nsAString& aMarkup);
+ void SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError);
+
+ // Override from nsINode
+ virtual nsINode::nsSlots* CreateSlots() override;
+
+ nsDOMSlots *DOMSlots()
+ {
+ return static_cast<nsDOMSlots*>(Slots());
+ }
+
+ nsDOMSlots *GetExistingDOMSlots() const
+ {
+ return static_cast<nsDOMSlots*>(GetExistingSlots());
+ }
+
+ /**
+ * Calls SetIsElementInStyleScopeFlagOnSubtree for each shadow tree attached
+ * to this node, which is assumed to be an Element.
+ *
+ * @param aInStyleScope The IsElementInStyleScope flag value to set.
+ */
+ void SetIsElementInStyleScopeFlagOnShadowTree(bool aInStyleScope);
+
+ friend class ::ContentUnbinder;
+ /**
+ * Array containing all attributes and children for this element
+ */
+ nsAttrAndChildArray mAttrsAndChildren;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#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 000000000..142c16cc4
--- /dev/null
+++ b/dom/base/FromParser.h
@@ -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/. */
+
+#ifndef mozilla_dom_FromParser_h
+#define mozilla_dom_FromParser_h
+
+namespace mozilla {
+namespace 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 dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FromParser_h
diff --git a/dom/base/GroupedSHistory.cpp b/dom/base/GroupedSHistory.cpp
new file mode 100644
index 000000000..266b1fd36
--- /dev/null
+++ b/dom/base/GroupedSHistory.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 "GroupedSHistory.h"
+#include "TabParent.h"
+#include "PartialSHistory.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION(GroupedSHistory, mPartialHistories)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GroupedSHistory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GroupedSHistory)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GroupedSHistory)
+ NS_INTERFACE_MAP_ENTRY(nsIGroupedSHistory)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGroupedSHistory)
+NS_INTERFACE_MAP_END
+
+GroupedSHistory::GroupedSHistory()
+ : mCount(0),
+ mIndexOfActivePartialHistory(-1)
+{
+}
+
+NS_IMETHODIMP
+GroupedSHistory::GetCount(uint32_t* aResult)
+{
+ MOZ_ASSERT(aResult);
+ *aResult = mCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::AppendPartialSessionHistory(nsIPartialSHistory* aPartialHistory)
+{
+ if (!aPartialHistory) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ nsCOMPtr<nsIPartialSHistory> partialHistory(aPartialHistory);
+ if (!partialHistory || mPartialHistories.Contains(partialHistory)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Remove all items after active one and deactive it, unless it's the first
+ // call and no active partial history has been set yet.
+ if (mIndexOfActivePartialHistory >= 0) {
+ PurgePartialHistories(mIndexOfActivePartialHistory);
+ nsCOMPtr<nsIPartialSHistory> prevPartialHistory =
+ mPartialHistories[mIndexOfActivePartialHistory];
+ if (NS_WARN_IF(!prevPartialHistory)) {
+ // Cycle collected?
+ return NS_ERROR_UNEXPECTED;
+ }
+ prevPartialHistory->OnDeactive();
+ }
+
+ // Attach the partial history.
+ uint32_t offset = mCount;
+ mCount += partialHistory->GetCount();
+ mPartialHistories.AppendElement(partialHistory);
+ partialHistory->OnAttachGroupedSessionHistory(offset);
+ mIndexOfActivePartialHistory = mPartialHistories.Count() - 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::OnPartialSessionHistoryChange(
+ nsIPartialSHistory* aPartialSessionHistory)
+{
+ if (!aPartialSessionHistory) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ nsCOMPtr<nsIPartialSHistory> partialHistory(aPartialSessionHistory);
+ int32_t index = mPartialHistories.IndexOf(partialHistory);
+ if (NS_WARN_IF(index != mIndexOfActivePartialHistory) ||
+ NS_WARN_IF(index < 0)) {
+ // Non-active or not attached partialHistory
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ PurgePartialHistories(index);
+
+ // Update global count.
+ uint32_t count = partialHistory->GetCount();
+ uint32_t offset = partialHistory->GetGlobalIndexOffset();
+ mCount = count + offset;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GroupedSHistory::GotoIndex(uint32_t aGlobalIndex,
+ nsIFrameLoader** aTargetLoaderToSwap)
+{
+ nsCOMPtr<nsIPartialSHistory> currentPartialHistory =
+ mPartialHistories[mIndexOfActivePartialHistory];
+ if (!currentPartialHistory) {
+ // Cycle collected?
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (uint32_t i = 0; i < mPartialHistories.Length(); i++) {
+ nsCOMPtr<nsIPartialSHistory> partialHistory = mPartialHistories[i];
+ if (NS_WARN_IF(!partialHistory)) {
+ // Cycle collected?
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Examine index range.
+ uint32_t offset = partialHistory->GetGlobalIndexOffset();
+ uint32_t count = partialHistory->GetCount();
+ if (offset <= aGlobalIndex && (offset + count) > aGlobalIndex) {
+ uint32_t targetIndex = aGlobalIndex - offset;
+ partialHistory->GetOwnerFrameLoader(aTargetLoaderToSwap);
+ if ((size_t)mIndexOfActivePartialHistory == i) {
+ return NS_OK;
+ }
+ mIndexOfActivePartialHistory = i;
+ if (NS_FAILED(currentPartialHistory->OnDeactive()) ||
+ NS_FAILED(partialHistory->OnActive(mCount, targetIndex))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+ }
+
+ // Index not found.
+ NS_WARNING("Out of index request!");
+ return NS_ERROR_FAILURE;
+}
+
+void
+GroupedSHistory::PurgePartialHistories(uint32_t aLastPartialIndexToKeep)
+{
+ uint32_t lastIndex = mPartialHistories.Length() - 1;
+ if (aLastPartialIndexToKeep >= lastIndex) {
+ // Nothing to remove.
+ return;
+ }
+
+ // Close tabs.
+ for (uint32_t i = lastIndex; i > aLastPartialIndexToKeep; i--) {
+ nsCOMPtr<nsIPartialSHistory> partialHistory = mPartialHistories[i];
+ if (!partialHistory) {
+ // Cycle collected?
+ return;
+ }
+
+ nsCOMPtr<nsIFrameLoader> loader;
+ partialHistory->GetOwnerFrameLoader(getter_AddRefs(loader));
+ loader->RequestFrameLoaderClose();
+ }
+
+ // Remove references.
+ mPartialHistories.RemoveElementsAt(aLastPartialIndexToKeep + 1,
+ lastIndex - aLastPartialIndexToKeep);
+}
+
+/* static */ bool
+GroupedSHistory::GroupedHistoryEnabled() {
+ return Preferences::GetBool("browser.groupedhistory.enabled", false);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/GroupedSHistory.h b/dom/base/GroupedSHistory.h
new file mode 100644
index 000000000..8c88a0aaf
--- /dev/null
+++ b/dom/base/GroupedSHistory.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 GroupedSHistory_h
+#define GroupedSHistory_h
+
+#include "nsIFrameLoader.h"
+#include "nsIGroupedSHistory.h"
+#include "nsIPartialSHistory.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace dom {
+
+
+/**
+ * GroupedSHistory connects session histories across multiple frameloaders.
+ * Each frameloader has a PartialSHistory, and GroupedSHistory has an array
+ * refering to all participating PartialSHistory(s).
+ *
+ * The following figure illustrates the idea. In this case, the GroupedSHistory
+ * is composed of 3 frameloaders, and the active one is frameloader 1.
+ * GroupedSHistory is always attached to the active frameloader.
+ *
+ * +----------------------------------------------------+
+ * | |
+ * | v
+ * +------------------+ +-------------------+ +-----------------+
+ * | FrameLoader 1 | | PartialSHistory 1 | | GroupedSHistory |
+ * | (active) |----->| (active) |<--+---| |
+ * +------------------+ +-------------------+ | +-----------------+
+ * |
+ * +------------------+ +-------------------+ |
+ * | FrameLoader 2 | | PartialSHistory 2 | |
+ * | (inactive) |----->| (inactive) |<--+
+ * +------------------+ +-------------------+ |
+ * |
+ * +------------------+ +-------------------+ |
+ * | FrameLoader 3 | | PartialSHistory 3 | |
+ * | (inactive) |----->| (inactive) |<--+
+ * +------------------+ +-------------------+
+ *
+ * If a history navigation leads to frameloader 3, it becomes the active one,
+ * and GroupedSHistory is re-attached to frameloader 3.
+ *
+ * +------------------+ +-------------------+
+ * | FrameLoader 1 | | PartialSHistory 1 |
+ * | (inactive) |----->| (inactive) |<--+
+ * +------------------+ +-------------------+ |
+ * |
+ * +------------------+ +-------------------+ |
+ * | FrameLoader 2 | | PartialSHistory 2 | |
+ * | (inactive) |----->| (inactive) |<--+
+ * +------------------+ +-------------------+ |
+ * |
+ * +------------------+ +-------------------+ | +-----------------+
+ * | FrameLoader 3 | | PartialSHistory 3 | | | GroupedSHistory |
+ * | (active) |----->| (active) |<--+---| |
+ * +------------------+ +-------------------+ +-----------------+
+ * | ^
+ * | |
+ * +----------------------------------------------------+
+ */
+class GroupedSHistory final : public nsIGroupedSHistory
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(GroupedSHistory)
+ NS_DECL_NSIGROUPEDSHISTORY
+ GroupedSHistory();
+
+ /**
+ * Get the value of preference "browser.groupedhistory.enabled" to determine
+ * if grouped session history should be enabled.
+ */
+ static bool GroupedHistoryEnabled();
+
+private:
+ ~GroupedSHistory() {}
+
+ /**
+ * Remove all partial histories and close tabs after the given index (of
+ * mPartialHistories, not the index of session history entries).
+ */
+ void PurgePartialHistories(uint32_t aLastPartialIndexToKeep);
+
+ // The total number of entries in all partial histories.
+ uint32_t mCount;
+
+ // The index of currently active partial history in mPartialHistories.
+ // Use int32_t as we have invalid index and nsCOMArray also uses int32_t.
+ int32_t mIndexOfActivePartialHistory;
+
+ // All participating nsIPartialSHistory objects.
+ nsCOMArray<nsIPartialSHistory> mPartialHistories;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* GroupedSHistory_h */
diff --git a/dom/base/HTMLSplitOnSpacesTokenizer.h b/dom/base/HTMLSplitOnSpacesTokenizer.h
new file mode 100644
index 000000000..eb4ded016
--- /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/IdleDeadline.cpp b/dom/base/IdleDeadline.cpp
new file mode 100644
index 000000000..ef475e543
--- /dev/null
+++ b/dom/base/IdleDeadline.cpp
@@ -0,0 +1,70 @@
+/* -*- 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 <algorithm>
+
+#include "mozilla/dom/IdleDeadline.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 {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(IdleDeadline, mWindow)
+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)
+{
+}
+
+IdleDeadline::~IdleDeadline()
+{
+}
+
+JSObject*
+IdleDeadline::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return IdleDeadlineBinding::Wrap(aCx, this, aGivenProto);
+}
+
+DOMHighResTimeStamp
+IdleDeadline::TimeRemaining()
+{
+ if (mDidTimeout) {
+ return 0.0;
+ }
+
+ 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;
+ }
+
+ return std::max(mDeadline - performance->Now(), 0.0);
+}
+
+bool
+IdleDeadline::DidTimeout() const
+{
+ return mDidTimeout;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/IdleDeadline.h b/dom/base/IdleDeadline.h
new file mode 100644
index 000000000..000d6da8a
--- /dev/null
+++ b/dom/base/IdleDeadline.h
@@ -0,0 +1,55 @@
+/* -*- 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_IdleDeadline_h
+#define mozilla_dom_IdleDeadline_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class IdleDeadline final
+ : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ IdleDeadline(nsPIDOMWindowInner* aWindow, bool aDidTimeout,
+ DOMHighResTimeStamp aDeadline);
+
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ DOMHighResTimeStamp TimeRemaining();
+ bool DidTimeout() const;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IdleDeadline)
+
+private:
+ ~IdleDeadline();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ const bool mDidTimeout;
+ const DOMHighResTimeStamp mDeadline;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_IdleDeadline_h
diff --git a/dom/base/IdleRequest.cpp b/dom/base/IdleRequest.cpp
new file mode 100644
index 000000000..26190f98b
--- /dev/null
+++ b/dom/base/IdleRequest.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 "IdleRequest.h"
+
+#include "mozilla/Function.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/IdleDeadline.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsISupportsPrimitives.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+IdleRequest::IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+ IdleRequestCallback& aCallback, uint32_t aHandle)
+ : mWindow(aWindow)
+ , mCallback(&aCallback)
+ , mHandle(aHandle)
+ , mTimeoutHandle(Nothing())
+{
+ MOZ_ASSERT(aWindow);
+
+ // Get the calling location.
+ nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn);
+}
+
+IdleRequest::~IdleRequest()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequest)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequest)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequest)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsIIncrementalRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimeoutHandler)
+NS_INTERFACE_MAP_END
+
+nsresult
+IdleRequest::SetTimeout(uint32_t aTimeout)
+{
+ int32_t handle;
+ nsresult rv = nsGlobalWindow::Cast(mWindow)->SetTimeoutOrInterval(
+ this, aTimeout, false, Timeout::Reason::eIdleCallbackTimeout, &handle);
+ mTimeoutHandle = Some(handle);
+
+ return rv;
+}
+
+nsresult
+IdleRequest::Run()
+{
+ if (mCallback) {
+ RunIdleRequestCallback(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+IdleRequest::Cancel()
+{
+ mCallback = nullptr;
+ CancelTimeout();
+ if (isInList()) {
+ remove();
+ }
+ Release();
+
+ return NS_OK;
+}
+
+void
+IdleRequest::SetDeadline(TimeStamp aDeadline)
+{
+ mozilla::dom::Performance* perf = mWindow->GetPerformance();
+ mDeadline =
+ perf ? perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline) : 0.0;
+}
+
+nsresult
+IdleRequest::RunIdleRequestCallback(bool aDidTimeout)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aDidTimeout) {
+ CancelTimeout();
+ }
+
+ remove();
+ ErrorResult error;
+ RefPtr<IdleDeadline> deadline =
+ new IdleDeadline(mWindow, aDidTimeout, mDeadline);
+ mCallback->Call(*deadline, error, "requestIdleCallback handler");
+ mCallback = nullptr;
+ Release();
+
+ return error.StealNSResult();
+}
+
+void
+IdleRequest::CancelTimeout()
+{
+ if (mTimeoutHandle.isSome()) {
+ nsGlobalWindow::Cast(mWindow)->ClearTimeoutOrInterval(
+ mTimeoutHandle.value(), Timeout::Reason::eIdleCallbackTimeout);
+ }
+}
+
+nsresult
+IdleRequest::Call()
+{
+ SetDeadline(TimeStamp::Now());
+ return RunIdleRequestCallback(true);
+}
+
+void
+IdleRequest::GetLocation(const char** aFileName, uint32_t* aLineNo,
+ uint32_t* aColumn)
+{
+ *aFileName = mFileName.get();
+ *aLineNo = mLineNo;
+ *aColumn = mColumn;
+}
+
+void
+IdleRequest::MarkForCC()
+{
+ mCallback->MarkForCC();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/IdleRequest.h b/dom/base/IdleRequest.h
new file mode 100644
index 000000000..cb234430a
--- /dev/null
+++ b/dom/base/IdleRequest.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 mozilla_dom_idlerequest_h
+#define mozilla_dom_idlerequest_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsITimeoutHandler.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class IdleRequestCallback;
+
+class IdleRequest final : public nsITimeoutHandler
+ , public nsIRunnable
+ , public nsICancelableRunnable
+ , public nsIIncrementalRunnable
+ , public LinkedListElement<IdleRequest>
+{
+public:
+ IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+ IdleRequestCallback& aCallback, uint32_t aHandle);
+
+ virtual nsresult Call() override;
+ virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
+ uint32_t* aColumn) override;
+ virtual void MarkForCC() override;
+
+ nsresult SetTimeout(uint32_t aTimout);
+ nsresult RunIdleRequestCallback(bool aDidTimeout);
+ void CancelTimeout();
+
+ NS_DECL_NSIRUNNABLE;
+ virtual nsresult Cancel() override;
+ virtual void SetDeadline(mozilla::TimeStamp aDeadline) override;
+
+ uint32_t Handle() const
+ {
+ return mHandle;
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequest, nsITimeoutHandler)
+
+private:
+ ~IdleRequest();
+
+ // filename, line number and JS language version string of the
+ // caller of setTimeout()
+ nsCString mFileName;
+ uint32_t mLineNo;
+ uint32_t mColumn;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<IdleRequestCallback> mCallback;
+ uint32_t mHandle;
+ mozilla::Maybe<int32_t> mTimeoutHandle;
+ DOMHighResTimeStamp mDeadline;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_idlerequest_h
diff --git a/dom/base/IframeSandboxKeywordList.h b/dom/base/IframeSandboxKeywordList.h
new file mode 100644
index 000000000..6e3132f51
--- /dev/null
+++ b/dom/base/IframeSandboxKeywordList.h
@@ -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/. */
+
+/* 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-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)
diff --git a/dom/base/ImageEncoder.cpp b/dom/base/ImageEncoder.cpp
new file mode 100644
index 000000000..a41dee080
--- /dev/null
+++ b/dom/base/ImageEncoder.cpp
@@ -0,0 +1,586 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/layers/AsyncCanvasRenderer.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Unused.h"
+#include "gfxUtils.h"
+#include "nsIThreadPool.h"
+#include "nsNetUtil.h"
+#include "nsXPCOMCIDInternal.h"
+#include "WorkerPrivate.h"
+#include "YCbCrUtils.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace 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) : mImage(aImage) {}
+
+ // It retrieves a SourceSurface reference and convert color format on main
+ // thread and passes DataSourceSurface to caller thread.
+ NS_IMETHOD Run() override {
+ // It guarantees the reference will be released on main thread.
+ nsCountedRef<nsMainThreadSourceSurfaceRef> surface;
+ surface.own(mImage->GetAsSourceSurface().take());
+
+ if (surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8) {
+ mDataSourceSurface = surface->GetDataSurface();
+ } else {
+ mDataSourceSurface = gfxUtils::
+ CopySurfaceToDataSourceSurfaceWithFormat(surface,
+ gfx::SurfaceFormat::B8G8R8A8);
+ }
+ return NS_OK;
+ }
+
+ already_AddRefed<gfx::DataSourceSurface> GetDataSurfaceSafe() {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+ SyncRunnable::DispatchToThread(mainThread, 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(Move(aImage));
+ return helper->GetDataSurfaceSafe();
+}
+
+class EncodingCompleteEvent : public CancelableRunnable
+{
+ virtual ~EncodingCompleteEvent() {}
+
+public:
+ explicit EncodingCompleteEvent(EncodeCompleteCallback* aEncodeCompleteCallback)
+ : mImgSize(0)
+ , mType()
+ , mImgData(nullptr)
+ , mEncodeCompleteCallback(aEncodeCompleteCallback)
+ , mFailed(false)
+ {
+ if (!NS_IsMainThread() && workers::GetCurrentThreadWorkerPrivate()) {
+ mCreationThread = NS_GetCurrentThread();
+ } else {
+ NS_GetMainThread(getter_AddRefs(mCreationThread));
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsresult rv = NS_OK;
+
+ if (!mFailed) {
+ // The correct parentObject has to be set by the mEncodeCompleteCallback.
+ RefPtr<Blob> blob =
+ Blob::CreateMemoryBlob(nullptr, mImgData, mImgSize, mType);
+ MOZ_ASSERT(blob);
+
+ rv = mEncodeCompleteCallback->ReceiveBlob(blob.forget());
+ }
+
+ mEncodeCompleteCallback = nullptr;
+
+ return rv;
+ }
+
+ void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType)
+ {
+ mImgData = aImgData;
+ mImgSize = aImgSize;
+ mType = aType;
+ }
+
+ void SetFailed()
+ {
+ mFailed = true;
+ }
+
+ nsIThread* GetCreationThread()
+ {
+ return mCreationThread;
+ }
+
+private:
+ uint64_t mImgSize;
+ nsAutoString mType;
+ void* mImgData;
+ nsCOMPtr<nsIThread> mCreationThread;
+ RefPtr<EncodeCompleteCallback> mEncodeCompleteCallback;
+ bool mFailed;
+};
+
+class EncodingRunnable : public Runnable
+{
+ virtual ~EncodingRunnable() {}
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ 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 aUsingCustomOptions)
+ : mType(aType)
+ , mOptions(aOptions)
+ , mImageBuffer(Move(aImageBuffer))
+ , mImage(aImage)
+ , mEncoder(aEncoder)
+ , mEncodingCompleteEvent(aEncodingCompleteEvent)
+ , mFormat(aFormat)
+ , mSize(aSize)
+ , mUsingCustomOptions(aUsingCustomOptions)
+ {}
+
+ nsresult ProcessImageData(uint64_t* aImgSize, void** aImgData)
+ {
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = ImageEncoder::ExtractDataInternal(mType,
+ mOptions,
+ mImageBuffer.get(),
+ mFormat,
+ mSize,
+ 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,
+ EmptyString(),
+ mImageBuffer.get(),
+ mFormat,
+ mSize,
+ mImage,
+ nullptr,
+ nullptr,
+ getter_AddRefs(stream),
+ mEncoder);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stream->Available(aImgSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(*aImgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
+
+ rv = NS_ReadInputStreamToBuffer(stream, aImgData, *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->GetCreationThread()->
+ Dispatch(mEncodingCompleteEvent, nsIThread::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ // 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 mUsingCustomOptions;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED0(EncodingRunnable, Runnable);
+
+StaticRefPtr<nsIThreadPool> ImageEncoder::sThreadPool;
+
+/* static */
+nsresult
+ImageEncoder::ExtractData(nsAString& aType,
+ const nsAString& aOptions,
+ const nsIntSize aSize,
+ nsICanvasRenderingContextInternal* aContext,
+ layers::AsyncCanvasRenderer* aRenderer,
+ nsIInputStream** aStream)
+{
+ nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
+ if (!encoder) {
+ return NS_IMAGELIB_ERROR_NO_ENCODER;
+ }
+
+ return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, nullptr,
+ aContext, aRenderer, aStream, encoder);
+}
+
+/* static */
+nsresult
+ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType,
+ const nsAString& aOptions,
+ bool aUsingCustomOptions,
+ layers::Image* aImage,
+ EncodeCompleteCallback* aEncodeCallback)
+{
+ nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
+ if (!encoder) {
+ return NS_IMAGELIB_ERROR_NO_ENCODER;
+ }
+
+ nsresult rv = EnsureThreadPool();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ 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,
+ aUsingCustomOptions);
+ return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+/* static */
+nsresult
+ImageEncoder::ExtractDataAsync(nsAString& aType,
+ const nsAString& aOptions,
+ bool aUsingCustomOptions,
+ UniquePtr<uint8_t[]> aImageBuffer,
+ int32_t aFormat,
+ const nsIntSize aSize,
+ EncodeCompleteCallback* aEncodeCallback)
+{
+ nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
+ if (!encoder) {
+ return NS_IMAGELIB_ERROR_NO_ENCODER;
+ }
+
+ nsresult rv = EnsureThreadPool();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<EncodingCompleteEvent> completeEvent =
+ new EncodingCompleteEvent(aEncodeCallback);
+
+ nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
+ aOptions,
+ Move(aImageBuffer),
+ nullptr,
+ encoder,
+ completeEvent,
+ aFormat,
+ aSize,
+ aUsingCustomOptions);
+ return sThreadPool->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+/*static*/ nsresult
+ImageEncoder::GetInputStream(int32_t aWidth,
+ int32_t aHeight,
+ uint8_t* aImageBuffer,
+ int32_t aFormat,
+ imgIEncoder* aEncoder,
+ const char16_t* aEncoderOptions,
+ nsIInputStream** aStream)
+{
+ nsresult rv =
+ aEncoder->InitFromData(aImageBuffer,
+ aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4,
+ aFormat,
+ nsDependentString(aEncoderOptions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(aEncoder, aStream);
+}
+
+/* static */
+nsresult
+ImageEncoder::ExtractDataInternal(const nsAString& aType,
+ const nsAString& aOptions,
+ uint8_t* aImageBuffer,
+ int32_t aFormat,
+ const nsIntSize aSize,
+ layers::Image* aImage,
+ nsICanvasRenderingContextInternal* aContext,
+ layers::AsyncCanvasRenderer* aRenderer,
+ nsIInputStream** aStream,
+ imgIEncoder* aEncoder)
+{
+ if (aSize.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIInputStream> imgStream;
+
+ // get image bytes
+ nsresult rv;
+ if (aImageBuffer) {
+ if (BufferSizeFromDimensions(aSize.width, aSize.height, 4) == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = ImageEncoder::GetInputStream(
+ aSize.width,
+ aSize.height,
+ aImageBuffer,
+ aFormat,
+ aEncoder,
+ nsPromiseFlatString(aOptions).get(),
+ getter_AddRefs(imgStream));
+ } else if (aContext) {
+ NS_ConvertUTF16toUTF8 encoderType(aType);
+ rv = aContext->GetInputStream(encoderType.get(),
+ nsPromiseFlatString(aOptions).get(),
+ getter_AddRefs(imgStream));
+ } else if (aRenderer) {
+ NS_ConvertUTF16toUTF8 encoderType(aType);
+ rv = aRenderer->GetInputStream(encoderType.get(),
+ nsPromiseFlatString(aOptions).get(),
+ getter_AddRefs(imgStream));
+ } else if (aImage) {
+ // 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 = do_QueryInterface(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;
+ }
+ 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 = do_QueryInterface(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 != NS_LITERAL_STRING("image/png")) {
+ // 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();
+}
+
+class EncoderThreadPoolTerminator final : public nsIObserver
+{
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Observe(nsISupports *, const char *topic, const char16_t *) override
+ {
+ NS_ASSERTION(!strcmp(topic, "xpcom-shutdown-threads"),
+ "Unexpected topic");
+ if (ImageEncoder::sThreadPool) {
+ ImageEncoder::sThreadPool->Shutdown();
+ ImageEncoder::sThreadPool = nullptr;
+ }
+ return NS_OK;
+ }
+ private:
+ ~EncoderThreadPoolTerminator() {}
+};
+
+NS_IMPL_ISUPPORTS(EncoderThreadPoolTerminator, nsIObserver)
+
+static void
+RegisterEncoderThreadPoolTerminatorObserver()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ASSERTION(os, "do_GetService failed");
+ os->AddObserver(new EncoderThreadPoolTerminator(),
+ "xpcom-shutdown-threads",
+ false);
+}
+
+/* static */
+nsresult
+ImageEncoder::EnsureThreadPool()
+{
+ if (!sThreadPool) {
+ nsCOMPtr<nsIThreadPool> threadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
+ sThreadPool = threadPool;
+
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
+ RegisterEncoderThreadPoolTerminatorObserver();
+ }));
+ } else {
+ RegisterEncoderThreadPoolTerminatorObserver();
+ }
+
+ const uint32_t kThreadLimit = 2;
+ const uint32_t kIdleThreadLimit = 1;
+ const uint32_t kIdleThreadTimeoutMs = 30000;
+
+ nsresult rv = sThreadPool->SetName(NS_LITERAL_CSTRING("EncodingRunnable"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = sThreadPool->SetThreadLimit(kThreadLimit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = sThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = sThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/ImageEncoder.h b/dom/base/ImageEncoder.h
new file mode 100644
index 000000000..fd30a94d4
--- /dev/null
+++ b/dom/base/ImageEncoder.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/. */
+
+#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 "nsLayoutUtils.h"
+#include "nsSize.h"
+
+class nsICanvasRenderingContextInternal;
+class nsIThreadPool;
+
+namespace mozilla {
+
+namespace layers {
+class AsyncCanvasRenderer;
+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,
+ nsICanvasRenderingContextInternal* aContext,
+ layers::AsyncCanvasRenderer* 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,
+ 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,
+ 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 char16_t* 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,
+ layers::Image* aImage,
+ nsICanvasRenderingContextInternal* aContext,
+ layers::AsyncCanvasRenderer* 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);
+
+ static nsresult EnsureThreadPool();
+
+ // Thread pool for dispatching EncodingRunnable.
+ static StaticRefPtr<nsIThreadPool> sThreadPool;
+
+ friend class EncodingRunnable;
+ friend class EncoderThreadPoolTerminator;
+};
+
+/**
+ * The callback interface of ExtractDataAsync and ExtractDataFromLayersImageAsync.
+ * ReceiveBlob() is called on main thread when encoding is complete.
+ */
+class EncodeCompleteCallback
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(EncodeCompleteCallback)
+
+ virtual nsresult ReceiveBlob(already_AddRefed<Blob> aBlob) = 0;
+
+protected:
+ virtual ~EncodeCompleteCallback() {}
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // ImageEncoder_h
diff --git a/dom/base/ImageTracker.cpp b/dom/base/ImageTracker.cpp
new file mode 100644
index 000000000..9fef059bc
--- /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"
+
+namespace mozilla {
+namespace dom {
+
+ImageTracker::ImageTracker()
+ : mLocking(false)
+ , mAnimating(true)
+{
+}
+
+ImageTracker::~ImageTracker()
+{
+ SetLockingState(false);
+}
+
+nsresult
+ImageTracker::Add(imgIRequest* aImage)
+{
+ MOZ_ASSERT(aImage);
+
+ // See if the image is already in the hashtable. If it is, get the old count.
+ uint32_t oldCount = 0;
+ mImages.Get(aImage, &oldCount);
+
+ // Put the image in the hashtable, with the proper count.
+ mImages.Put(aImage, oldCount + 1);
+
+ nsresult rv = NS_OK;
+
+ // If this is the first insertion and we're locking images, lock this image
+ // too.
+ if (oldCount == 0 && mLocking) {
+ rv = aImage->LockImage();
+ }
+
+ // If this is the first insertion and we're animating images, request
+ // that this image be animated too.
+ if (oldCount == 0 && mAnimating) {
+ nsresult rv2 = aImage->IncrementAnimationConsumers();
+ rv = NS_SUCCEEDED(rv) ? rv2 : 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.
+ uint32_t count = 0;
+ DebugOnly<bool> found = mImages.Get(aImage, &count);
+ MOZ_ASSERT(found, "Removing image that wasn't in the tracker!");
+ MOZ_ASSERT(count > 0, "Entry in the cache tracker with count 0!");
+
+ // We're removing, so decrement the count.
+ count--;
+
+ // If the count is now zero, remove from the tracker.
+ // Otherwise, set the new value.
+ if (count != 0) {
+ mImages.Put(aImage, count);
+ return NS_OK;
+ }
+
+ mImages.Remove(aImage);
+
+ 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;
+}
+
+nsresult
+ImageTracker::SetLockingState(bool aLocked)
+{
+ if (XRE_IsContentProcess() &&
+ !Preferences::GetBool("image.mem.allow_locking_in_content_processes", true)) {
+ return NS_OK;
+ }
+
+ // If there's no change, there's nothing to do.
+ if (mLocking == aLocked)
+ return NS_OK;
+
+ // Otherwise, iterate over our images and perform the appropriate action.
+ for (auto iter = mImages.Iter(); !iter.Done(); iter.Next()) {
+ imgIRequest* image = iter.Key();
+ if (aLocked) {
+ image->LockImage();
+ } else {
+ image->UnlockImage();
+ }
+ }
+
+ // Update state.
+ mLocking = aLocked;
+
+ return NS_OK;
+}
+
+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 (auto iter = mImages.Iter(); !iter.Done(); iter.Next()) {
+ imgIRequest* image = iter.Key();
+ if (aAnimating) {
+ image->IncrementAnimationConsumers();
+ } else {
+ image->DecrementAnimationConsumers();
+ }
+ }
+
+ // Update state.
+ mAnimating = aAnimating;
+}
+
+void
+ImageTracker::RequestDiscardAll()
+{
+ for (auto iter = mImages.Iter(); !iter.Done(); iter.Next()) {
+ iter.Key()->RequestDiscard();
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/ImageTracker.h b/dom/base/ImageTracker.h
new file mode 100644
index 000000000..d33dc55f8
--- /dev/null
+++ b/dom/base/ImageTracker.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/. */
+
+/* table of images used in a document, for batch locking/unlocking and
+ * animating */
+
+#ifndef mozilla_dom_ImageTracker
+#define mozilla_dom_ImageTracker
+
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+
+class imgIRequest;
+
+namespace mozilla {
+namespace 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.
+ nsresult SetLockingState(bool aLocked);
+
+ // 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();
+
+private:
+ ~ImageTracker();
+
+ nsDataHashtable<nsPtrHashKey<imgIRequest>, uint32_t> mImages;
+ bool mLocking;
+ bool mAnimating;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ImageTracker
diff --git a/dom/base/ImportManager.cpp b/dom/base/ImportManager.cpp
new file mode 100644
index 000000000..d0e514b59
--- /dev/null
+++ b/dom/base/ImportManager.cpp
@@ -0,0 +1,748 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ImportManager.h"
+
+#include "mozilla/EventListenerManager.h"
+#include "HTMLLinkElement.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsScriptLoader.h"
+#include "nsNetUtil.h"
+
+//-----------------------------------------------------------------------------
+// AutoError
+//-----------------------------------------------------------------------------
+
+class AutoError {
+public:
+ explicit AutoError(mozilla::dom::ImportLoader* loader, bool scriptsBlocked = true)
+ : mLoader(loader)
+ , mPassed(false)
+ , mScriptsBlocked(scriptsBlocked)
+ {}
+
+ ~AutoError()
+ {
+ if (!mPassed) {
+ mLoader->Error(mScriptsBlocked);
+ }
+ }
+
+ void Pass() { mPassed = true; }
+
+private:
+ mozilla::dom::ImportLoader* mLoader;
+ bool mPassed;
+ bool mScriptsBlocked;
+};
+
+namespace mozilla {
+namespace dom {
+
+//-----------------------------------------------------------------------------
+// ImportLoader::Updater
+//-----------------------------------------------------------------------------
+
+void
+ImportLoader::Updater::GetReferrerChain(nsINode* aNode,
+ nsTArray<nsINode*>& aResult)
+{
+ // We fill up the array backward. First the last link: aNode.
+ MOZ_ASSERT(mLoader->mLinks.Contains(aNode));
+
+ aResult.AppendElement(aNode);
+ nsINode* node = aNode;
+ RefPtr<ImportManager> manager = mLoader->Manager();
+ for (ImportLoader* referrersLoader = manager->Find(node->OwnerDoc());
+ referrersLoader;
+ referrersLoader = manager->Find(node->OwnerDoc()))
+ {
+ // Then walking up the main referrer chain and append each link
+ // to the array.
+ node = referrersLoader->GetMainReferrer();
+ MOZ_ASSERT(node);
+ aResult.AppendElement(node);
+ }
+
+ // The reversed order is more useful for consumers.
+ // XXX: This should probably go to nsTArray or some generic utility
+ // lib for our containers that we don't have... I would really like to
+ // get rid of this part...
+ uint32_t l = aResult.Length();
+ for (uint32_t i = 0; i < l / 2; i++) {
+ Swap(aResult[i], aResult[l - i - 1]);
+ }
+}
+
+bool
+ImportLoader::Updater::ShouldUpdate(nsTArray<nsINode*>& aNewPath)
+{
+ if (mLoader->Manager()->GetNearestPredecessor(mLoader->GetMainReferrer()) !=
+ mLoader->mBlockingPredecessor) {
+ return true;
+ }
+ // Let's walk down on the main referrer chains of both the current main and
+ // the new link, and find the last pair of links that are from the same
+ // document. This is the junction point between the two referrer chain. Their
+ // order in the subimport list of that document will determine if we have to
+ // update the spanning tree or this new edge changes nothing in the script
+ // execution order.
+ nsTArray<nsINode*> oldPath;
+ GetReferrerChain(mLoader->mLinks[mLoader->mMainReferrer], oldPath);
+ uint32_t max = std::min(oldPath.Length(), aNewPath.Length());
+ MOZ_ASSERT(max > 0);
+ uint32_t lastCommonImportAncestor = 0;
+
+ for (uint32_t i = 0;
+ i < max && oldPath[i]->OwnerDoc() == aNewPath[i]->OwnerDoc();
+ i++)
+ {
+ lastCommonImportAncestor = i;
+ }
+
+ MOZ_ASSERT(lastCommonImportAncestor < max);
+ nsINode* oldLink = oldPath[lastCommonImportAncestor];
+ nsINode* newLink = aNewPath[lastCommonImportAncestor];
+
+ if ((lastCommonImportAncestor == max - 1) &&
+ newLink == oldLink ) {
+ // If one chain contains the other entirely, then this is a simple cycle,
+ // nothing to be done here.
+ MOZ_ASSERT(oldPath.Length() != aNewPath.Length(),
+ "This would mean that new link == main referrer link");
+ return false;
+ }
+
+ MOZ_ASSERT(aNewPath != oldPath,
+ "How could this happen?");
+ nsIDocument* doc = oldLink->OwnerDoc();
+ MOZ_ASSERT(doc->HasSubImportLink(newLink));
+ MOZ_ASSERT(doc->HasSubImportLink(oldLink));
+
+ return doc->IndexOfSubImportLink(newLink) < doc->IndexOfSubImportLink(oldLink);
+}
+
+void
+ImportLoader::Updater::UpdateMainReferrer(uint32_t aNewIdx)
+{
+ MOZ_ASSERT(aNewIdx < mLoader->mLinks.Length());
+ nsINode* newMainReferrer = mLoader->mLinks[aNewIdx];
+
+ // This new link means we have to execute our scripts sooner...
+ // Let's make sure that unblocking a loader does not trigger a script execution.
+ // So we start with placing the new blockers and only then will we remove any
+ // blockers.
+ if (mLoader->IsBlocking()) {
+ // Our import parent is changed, let's block the new one and later unblock
+ // the old one.
+ newMainReferrer->OwnerDoc()->
+ ScriptLoader()->AddParserBlockingScriptExecutionBlocker();
+ newMainReferrer->OwnerDoc()->BlockDOMContentLoaded();
+ }
+
+ if (mLoader->mDocument) {
+ // Our nearest predecessor has changed. So let's add the ScriptLoader to the
+ // new one if there is any. And remove it from the old one.
+ RefPtr<ImportManager> manager = mLoader->Manager();
+ nsScriptLoader* loader = mLoader->mDocument->ScriptLoader();
+ ImportLoader*& pred = mLoader->mBlockingPredecessor;
+ ImportLoader* newPred = manager->GetNearestPredecessor(newMainReferrer);
+ if (pred) {
+ if (newPred) {
+ newPred->AddBlockedScriptLoader(loader);
+ }
+ pred->RemoveBlockedScriptLoader(loader);
+ }
+ }
+
+ if (mLoader->IsBlocking()) {
+ mLoader->mImportParent->
+ ScriptLoader()->RemoveParserBlockingScriptExecutionBlocker();
+ mLoader->mImportParent->UnblockDOMContentLoaded();
+ }
+
+ // Finally update mMainReferrer to point to the newly added link.
+ mLoader->mMainReferrer = aNewIdx;
+ mLoader->mImportParent = newMainReferrer->OwnerDoc();
+}
+
+nsINode*
+ImportLoader::Updater::NextDependant(nsINode* aCurrentLink,
+ nsTArray<nsINode*>& aPath,
+ NodeTable& aVisitedNodes, bool aSkipChildren)
+{
+ // Depth first graph traversal.
+ if (!aSkipChildren) {
+ // "first child"
+ ImportLoader* loader = mLoader->Manager()->Find(aCurrentLink);
+ if (loader && loader->GetDocument()) {
+ nsINode* firstSubImport = loader->GetDocument()->GetSubImportLink(0);
+ if (firstSubImport && !aVisitedNodes.Contains(firstSubImport)) {
+ aPath.AppendElement(aCurrentLink);
+ aVisitedNodes.PutEntry(firstSubImport);
+ return firstSubImport;
+ }
+ }
+ }
+
+ aPath.AppendElement(aCurrentLink);
+ // "(parent's) next sibling"
+ while(aPath.Length() > 1) {
+ aCurrentLink = aPath[aPath.Length() - 1];
+ aPath.RemoveElementAt(aPath.Length() - 1);
+
+ // Let's find the next "sibling"
+ ImportLoader* loader = mLoader->Manager()->Find(aCurrentLink->OwnerDoc());
+ MOZ_ASSERT(loader && loader->GetDocument(), "How can this happend?");
+ nsIDocument* doc = loader->GetDocument();
+ MOZ_ASSERT(doc->HasSubImportLink(aCurrentLink));
+ uint32_t idx = doc->IndexOfSubImportLink(aCurrentLink);
+ nsINode* next = doc->GetSubImportLink(idx + 1);
+ if (next) {
+ // Note: If we found an already visited link that means the parent links has
+ // closed the circle it's always the "first child" section that should find
+ // the first already visited node. Let's just assert that.
+ MOZ_ASSERT(!aVisitedNodes.Contains(next));
+ aVisitedNodes.PutEntry(next);
+ return next;
+ }
+ }
+
+ return nullptr;
+}
+
+void
+ImportLoader::Updater::UpdateDependants(nsINode* aNode,
+ nsTArray<nsINode*>& aPath)
+{
+ NodeTable visitedNodes;
+ nsINode* current = aNode;
+ uint32_t initialLength = aPath.Length();
+ bool neededUpdate = true;
+ while ((current = NextDependant(current, aPath, visitedNodes, !neededUpdate))) {
+ if (!current || aPath.Length() <= initialLength) {
+ break;
+ }
+ ImportLoader* loader = mLoader->Manager()->Find(current);
+ if (!loader) {
+ continue;
+ }
+ Updater& updater = loader->mUpdater;
+ neededUpdate = updater.ShouldUpdate(aPath);
+ if (neededUpdate) {
+ updater.UpdateMainReferrer(loader->mLinks.IndexOf(current));
+ }
+ }
+}
+
+void
+ImportLoader::Updater::UpdateSpanningTree(nsINode* aNode)
+{
+ if (mLoader->mReady || mLoader->mStopped) {
+ // Scripts already executed, nothing to be done here.
+ return;
+ }
+
+ if (mLoader->mLinks.Length() == 1) {
+ // If this is the first referrer, let's mark it.
+ mLoader->mMainReferrer = 0;
+ return;
+ }
+
+ nsTArray<nsINode*> newReferrerChain;
+ GetReferrerChain(aNode, newReferrerChain);
+ if (ShouldUpdate(newReferrerChain)) {
+ UpdateMainReferrer(mLoader->mLinks.Length() - 1);
+ UpdateDependants(aNode, newReferrerChain);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ImportLoader
+//-----------------------------------------------------------------------------
+
+NS_INTERFACE_MAP_BEGIN(ImportLoader)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ImportLoader)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ImportLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportLoader)
+
+NS_IMPL_CYCLE_COLLECTION(ImportLoader,
+ mDocument,
+ mImportParent,
+ mLinks)
+
+ImportLoader::ImportLoader(nsIURI* aURI, nsIDocument* aImportParent)
+ : mURI(aURI)
+ , mImportParent(aImportParent)
+ , mBlockingPredecessor(nullptr)
+ , mReady(false)
+ , mStopped(false)
+ , mBlockingScripts(false)
+ , mUpdater(this)
+{
+}
+
+void
+ImportLoader::BlockScripts()
+{
+ MOZ_ASSERT(!mBlockingScripts);
+ mImportParent->ScriptLoader()->AddParserBlockingScriptExecutionBlocker();
+ mImportParent->BlockDOMContentLoaded();
+ mBlockingScripts = true;
+}
+
+void
+ImportLoader::UnblockScripts()
+{
+ MOZ_ASSERT(mBlockingScripts);
+ mImportParent->ScriptLoader()->RemoveParserBlockingScriptExecutionBlocker();
+ mImportParent->UnblockDOMContentLoaded();
+ for (uint32_t i = 0; i < mBlockedScriptLoaders.Length(); i++) {
+ mBlockedScriptLoaders[i]->RemoveParserBlockingScriptExecutionBlocker();
+ }
+ mBlockedScriptLoaders.Clear();
+ mBlockingScripts = false;
+}
+
+void
+ImportLoader::SetBlockingPredecessor(ImportLoader* aLoader)
+{
+ mBlockingPredecessor = aLoader;
+}
+
+void
+ImportLoader::DispatchEventIfFinished(nsINode* aNode)
+{
+ MOZ_ASSERT(!(mReady && mStopped));
+ if (mReady) {
+ DispatchLoadEvent(aNode);
+ }
+ if (mStopped) {
+ DispatchErrorEvent(aNode);
+ }
+}
+
+void
+ImportLoader::AddBlockedScriptLoader(nsScriptLoader* aScriptLoader)
+{
+ if (mBlockedScriptLoaders.Contains(aScriptLoader)) {
+ return;
+ }
+
+ aScriptLoader->AddParserBlockingScriptExecutionBlocker();
+
+ // Let's keep track of the pending script loaders.
+ mBlockedScriptLoaders.AppendElement(aScriptLoader);
+}
+
+bool
+ImportLoader::RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader)
+{
+ aScriptLoader->RemoveParserBlockingScriptExecutionBlocker();
+ return mBlockedScriptLoaders.RemoveElement(aScriptLoader);
+}
+
+void
+ImportLoader::AddLinkElement(nsINode* aNode)
+{
+ // If a new link element is added to the import tree that
+ // refers to an import that is already finished loading or
+ // stopped trying, we need to fire the corresponding event
+ // on it.
+ mLinks.AppendElement(aNode);
+ mUpdater.UpdateSpanningTree(aNode);
+ DispatchEventIfFinished(aNode);
+}
+
+void
+ImportLoader::RemoveLinkElement(nsINode* aNode)
+{
+ mLinks.RemoveElement(aNode);
+}
+
+// Events has to be fired with a script runner, so mImport can
+// be set on the link element before the load event is fired even
+// if ImportLoader::Get returns an already loaded import and we
+// fire the load event immediately on the new referring link element.
+class AsyncEvent : public Runnable {
+public:
+ AsyncEvent(nsINode* aNode, bool aSuccess)
+ : mNode(aNode)
+ , mSuccess(aSuccess)
+ {
+ MOZ_ASSERT(mNode);
+ }
+
+ NS_IMETHOD Run() override {
+ return nsContentUtils::DispatchTrustedEvent(mNode->OwnerDoc(),
+ mNode,
+ mSuccess ? NS_LITERAL_STRING("load")
+ : NS_LITERAL_STRING("error"),
+ /* aCanBubble = */ false,
+ /* aCancelable = */ false);
+ }
+
+private:
+ nsCOMPtr<nsINode> mNode;
+ bool mSuccess;
+};
+
+void
+ImportLoader::DispatchErrorEvent(nsINode* aNode)
+{
+ nsContentUtils::AddScriptRunner(new AsyncEvent(aNode, /* aSuccess = */ false));
+}
+
+void
+ImportLoader::DispatchLoadEvent(nsINode* aNode)
+{
+ nsContentUtils::AddScriptRunner(new AsyncEvent(aNode, /* aSuccess = */ true));
+}
+
+void
+ImportLoader::Done()
+{
+ mReady = true;
+ uint32_t l = mLinks.Length();
+ for (uint32_t i = 0; i < l; i++) {
+ DispatchLoadEvent(mLinks[i]);
+ }
+ UnblockScripts();
+ ReleaseResources();
+}
+
+void
+ImportLoader::Error(bool aUnblockScripts)
+{
+ mDocument = nullptr;
+ mStopped = true;
+ uint32_t l = mLinks.Length();
+ for (uint32_t i = 0; i < l; i++) {
+ DispatchErrorEvent(mLinks[i]);
+ }
+ if (aUnblockScripts) {
+ UnblockScripts();
+ }
+ ReleaseResources();
+}
+
+// Release all the resources we don't need after there is no more
+// data available on the channel, and the parser is done.
+void ImportLoader::ReleaseResources()
+{
+ mParserStreamListener = nullptr;
+ mImportParent = nullptr;
+}
+
+nsIPrincipal*
+ImportLoader::Principal()
+{
+ MOZ_ASSERT(mImportParent);
+ nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(master);
+ MOZ_ASSERT(sop);
+ return sop->GetPrincipal();
+}
+
+void
+ImportLoader::Open()
+{
+ AutoError ae(this, false);
+
+ nsCOMPtr<nsILoadGroup> loadGroup =
+ mImportParent->MasterDocument()->GetDocumentLoadGroup();
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+ mURI,
+ mImportParent,
+ nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS,
+ nsIContentPolicy::TYPE_SUBDOCUMENT,
+ loadGroup,
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_BACKGROUND);
+
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = channel->AsyncOpen2(this);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ BlockScripts();
+ ae.Pass();
+}
+
+NS_IMETHODIMP
+ImportLoader::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ MOZ_ASSERT(mParserStreamListener);
+
+ AutoError ae(this);
+ nsresult rv;
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mParserStreamListener->OnDataAvailable(channel, aContext,
+ aStream, aOffset,
+ aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ae.Pass();
+ return rv;
+}
+
+NS_IMETHODIMP
+ImportLoader::HandleEvent(nsIDOMEvent *aEvent)
+{
+#ifdef DEBUG
+ nsAutoString type;
+ aEvent->GetType(type);
+ MOZ_ASSERT(type.EqualsLiteral("DOMContentLoaded"));
+#endif
+ Done();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImportLoader::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ // OnStartRequest throws a special error code to let us know that we
+ // shouldn't do anything else.
+ if (aStatus == NS_ERROR_DOM_ABORT_ERR) {
+ // We failed in OnStartRequest, nothing more to do (we've already
+ // dispatched an error event) just return here.
+ MOZ_ASSERT(mStopped);
+ return NS_OK;
+ }
+
+ if (mParserStreamListener) {
+ mParserStreamListener->OnStopRequest(aRequest, aContext, aStatus);
+ }
+
+ if (!mDocument) {
+ // If at this point we don't have a document, then the error was
+ // already reported.
+ return NS_ERROR_DOM_ABORT_ERR;
+ }
+
+ nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mDocument);
+ EventListenerManager* manager = eventTarget->GetOrCreateListenerManager();
+ manager->AddEventListenerByType(this,
+ NS_LITERAL_STRING("DOMContentLoaded"),
+ TrustedEventsAtSystemGroupBubble());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ImportLoader::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ AutoError ae(this);
+ nsIPrincipal* principal = Principal();
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ return NS_ERROR_DOM_ABORT_ERR;
+ }
+
+ if (nsContentUtils::IsSystemPrincipal(principal)) {
+ // We should never import non-system documents and run their scripts with system principal!
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(channel,
+ getter_AddRefs(channelPrincipal));
+ if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ channel->SetOwner(principal);
+
+ nsAutoCString type;
+ channel->GetContentType(type);
+ if (!type.EqualsLiteral("text/html")) {
+ NS_WARNING("ImportLoader wrong content type");
+ return NS_ERROR_DOM_ABORT_ERR;
+ }
+
+ // The scope object is same for all the imports in an import tree,
+ // let's get it form the import parent.
+ nsCOMPtr<nsIGlobalObject> global = mImportParent->GetScopeObject();
+ nsCOMPtr<nsIDOMDocument> importDoc;
+ nsCOMPtr<nsIURI> baseURI = mImportParent->GetBaseURI();
+ const nsAString& emptyStr = EmptyString();
+ nsresult rv = NS_NewDOMDocument(getter_AddRefs(importDoc),
+ emptyStr, emptyStr, nullptr, mURI,
+ baseURI, principal, false, global,
+ DocumentFlavorHTML);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
+
+ // The imported document must know which master document it belongs to.
+ mDocument = do_QueryInterface(importDoc);
+ nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
+ mDocument->SetMasterDocument(master);
+
+ // We want to inherit the sandbox flags and fullscreen enabled flag
+ // from the master document.
+ mDocument->SetSandboxFlags(master->GetSandboxFlags());
+
+ // We have to connect the blank document we created with the channel we opened,
+ // and create its own LoadGroup for it.
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ channel->GetLoadGroup(getter_AddRefs(loadGroup));
+ nsCOMPtr<nsILoadGroup> newLoadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+ NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
+ newLoadGroup->SetLoadGroup(loadGroup);
+ rv = mDocument->StartDocumentLoad("import", channel, newLoadGroup,
+ nullptr, getter_AddRefs(listener),
+ true);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
+
+ nsCOMPtr<nsIURI> originalURI;
+ rv = channel->GetOriginalURI(getter_AddRefs(originalURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
+
+ nsCOMPtr<nsIURI> URI;
+ rv = channel->GetURI(getter_AddRefs(URI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
+ MOZ_ASSERT(URI, "URI of a channel should never be null");
+
+ bool equals;
+ rv = URI->Equals(originalURI, &equals);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
+
+ if (!equals) {
+ // In case of a redirection we must add the new URI to the import map.
+ Manager()->AddLoaderWithNewURI(this, URI);
+ }
+
+ // Let's start the parser.
+ mParserStreamListener = listener;
+ rv = listener->OnStartRequest(aRequest, aContext);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
+
+ ae.Pass();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ImportManager
+//-----------------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION(ImportManager,
+ mImports)
+
+NS_INTERFACE_MAP_BEGIN(ImportManager)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(ImportManager)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ImportManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportManager)
+
+already_AddRefed<ImportLoader>
+ImportManager::Get(nsIURI* aURI, nsINode* aNode, nsIDocument* aOrigDocument)
+{
+ // Check if we have a loader for that URI, if not create one,
+ // and start it up.
+ RefPtr<ImportLoader> loader;
+ mImports.Get(aURI, getter_AddRefs(loader));
+ bool needToStart = false;
+ if (!loader) {
+ loader = new ImportLoader(aURI, aOrigDocument);
+ mImports.Put(aURI, loader);
+ needToStart = true;
+ }
+
+ MOZ_ASSERT(loader);
+ // Let's keep track of the sub imports links in each document. It will
+ // be used later for scrip execution order calculation. (see UpdateSpanningTree)
+ // NOTE: removing and adding back the link to the tree somewhere else will
+ // NOT have an effect on script execution order.
+ if (!aOrigDocument->HasSubImportLink(aNode)) {
+ aOrigDocument->AddSubImportLink(aNode);
+ }
+
+ loader->AddLinkElement(aNode);
+
+ if (needToStart) {
+ loader->Open();
+ }
+
+ return loader.forget();
+}
+
+ImportLoader*
+ImportManager::Find(nsIDocument* aImport)
+{
+ return mImports.GetWeak(aImport->GetDocumentURIObject());
+}
+
+ImportLoader*
+ImportManager::Find(nsINode* aLink)
+{
+ HTMLLinkElement* linkElement = static_cast<HTMLLinkElement*>(aLink);
+ nsCOMPtr<nsIURI> uri = linkElement->GetHrefURI();
+ return mImports.GetWeak(uri);
+}
+
+void
+ImportManager::AddLoaderWithNewURI(ImportLoader* aLoader, nsIURI* aNewURI)
+{
+ mImports.Put(aNewURI, aLoader);
+}
+
+ImportLoader* ImportManager::GetNearestPredecessor(nsINode* aNode)
+{
+ // Return the previous link if there is any in the same document.
+ nsIDocument* doc = aNode->OwnerDoc();
+ int32_t idx = doc->IndexOfSubImportLink(aNode);
+ MOZ_ASSERT(idx != -1, "aNode must be a sub import link of its owner document");
+
+ for (; idx > 0; idx--) {
+ HTMLLinkElement* link =
+ static_cast<HTMLLinkElement*>(doc->GetSubImportLink(idx - 1));
+ nsCOMPtr<nsIURI> uri = link->GetHrefURI();
+ RefPtr<ImportLoader> ret;
+ mImports.Get(uri, getter_AddRefs(ret));
+ // Only main referrer links are interesting.
+ if (ret->GetMainReferrer() == link) {
+ return ret;
+ }
+ }
+
+ if (idx == 0) {
+ if (doc->IsMasterDocument()) {
+ // If there is no previous one, and it was the master document, then
+ // there is no predecessor.
+ return nullptr;
+ }
+ // Else we find the main referrer of the import parent of the link's document.
+ // And do a recursion.
+ ImportLoader* owner = Find(doc);
+ MOZ_ASSERT(owner);
+ nsCOMPtr<nsINode> mainReferrer = owner->GetMainReferrer();
+ return GetNearestPredecessor(mainReferrer);
+ }
+
+ return nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/ImportManager.h b/dom/base/ImportManager.h
new file mode 100644
index 000000000..258d4691c
--- /dev/null
+++ b/dom/base/ImportManager.h
@@ -0,0 +1,283 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ *
+ * For each import tree there is one master document (the root) and one
+ * import manager. The import manager is a map of URI ImportLoader pairs.
+ * An ImportLoader is responsible for loading an import document from a
+ * given location, and sending out load or error events to all the link
+ * nodes that refer to it when it's done. For loading it opens up a
+ * channel, using the same CSP as the master document. It then creates a
+ * blank document, and starts parsing the data from the channel. When
+ * there is no more data on the channel we wait for the DOMContentLoaded
+ * event from the parsed document. For the duration of the loading
+ * process the scripts on the parent documents are blocked. When an error
+ * occurs, or the DOMContentLoaded event is received, the scripts on the
+ * parent document are unblocked and we emit the corresponding event on
+ * all the referrer link nodes. If a new link node is added to one of the
+ * DOM trees in the import tree that refers to an import that was already
+ * loaded, the already existing ImportLoader is being used (without
+ * loading the referred import document twice) and if necessary the
+ * load/error is emitted on it immediately.
+ *
+ * Ownership model:
+ *
+ * ImportDocument ----------------------------
+ * ^ |
+ * | v
+ * MasterDocument <- ImportManager <-ImportLoader
+ * ^ ^
+ * | |
+ * LinkElement <-----------------------------
+ *
+ */
+
+#ifndef mozilla_dom_ImportManager_h__
+#define mozilla_dom_ImportManager_h__
+
+#include "nsTArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDOMEventListener.h"
+#include "nsIStreamListener.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsRefPtrHashtable.h"
+#include "nsScriptLoader.h"
+#include "nsURIHashKey.h"
+
+class nsIDocument;
+class nsIPrincipal;
+class nsINode;
+class AutoError;
+
+namespace mozilla {
+namespace dom {
+
+class ImportManager;
+
+typedef nsTHashtable<nsPtrHashKey<nsINode>> NodeTable;
+
+class ImportLoader final : public nsIStreamListener
+ , public nsIDOMEventListener
+{
+
+ // A helper inner class to decouple the logic of updating the import graph
+ // after a new import link has been found by one of the parsers.
+ class Updater {
+
+ public:
+ explicit Updater(ImportLoader* aLoader) : mLoader(aLoader)
+ {}
+
+ // After a new link is added that refers to this import, we
+ // have to update the spanning tree, since given this new link the
+ // priority of this import might be higher in the scripts
+ // execution order than before. It updates mMainReferrer, mImportParent,
+ // the corresponding pending ScriptRunners, etc.
+ // It also handles updating additional dependant loaders via the
+ // UpdateDependants calls.
+ // (NOTE: See GetMainReferrer about spanning tree.)
+ void UpdateSpanningTree(nsINode* aNode);
+
+ private:
+ // Returns an array of links that forms a referring chain from
+ // the master document to this import. Each link in the array
+ // is marked as main referrer in the list.
+ void GetReferrerChain(nsINode* aNode, nsTArray<nsINode*>& aResult);
+
+ // Once we find a new referrer path to our import, we have to see if
+ // it changes the load order hence we have to do an update on the graph.
+ bool ShouldUpdate(nsTArray<nsINode*>& aNewPath);
+ void UpdateMainReferrer(uint32_t newIdx);
+
+ // It's a depth first graph traversal algorithm, for UpdateDependants. The
+ // nodes in the graph are the import link elements, and there is a directed
+ // edge from link1 to link2 if link2 is a subimport in the import document
+ // of link1.
+ // If the ImportLoader that aCurrentLink points to didn't need to be updated
+ // the algorithm skips its "children" (subimports). Note, that this graph can
+ // also contain cycles, aVisistedLinks is used to track the already visited
+ // links to avoid an infinite loop.
+ // aPath - (in/out) the referrer link chain of aCurrentLink when called, and
+ // of the next link when the function returns
+ // aVisitedLinks - (in/out) list of links that the traversal already visited
+ // (to handle cycles in the graph)
+ // aSkipChildren - when aCurrentLink points to an import that did not need
+ // to be updated, we can skip its sub-imports ('children')
+ nsINode* NextDependant(nsINode* aCurrentLink,
+ nsTArray<nsINode*>& aPath,
+ NodeTable& aVisitedLinks, bool aSkipChildren);
+
+ // When we find a new link that changes the load order of the known imports,
+ // we also have to check all the subimports of it, to see if they need an
+ // update too. (see test_imports_nested_2.html)
+ void UpdateDependants(nsINode* aNode, nsTArray<nsINode*>& aPath);
+
+ ImportLoader* mLoader;
+ };
+
+ friend class ::AutoError;
+ friend class ImportManager;
+ friend class Updater;
+
+public:
+ ImportLoader(nsIURI* aURI, nsIDocument* aOriginDocument);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ImportLoader, nsIStreamListener)
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // We need to listen to DOMContentLoaded event to know when the document
+ // is fully leaded.
+ NS_IMETHOD HandleEvent(nsIDOMEvent *aEvent) override;
+
+ // Validation then opening and starting up the channel.
+ void Open();
+ void AddLinkElement(nsINode* aNode);
+ void RemoveLinkElement(nsINode* aNode);
+ bool IsReady() { return mReady; }
+ bool IsStopped() { return mStopped; }
+ bool IsBlocking() { return mBlockingScripts; }
+
+ ImportManager* Manager() {
+ MOZ_ASSERT(mDocument || mImportParent, "One of them should be always set");
+ return (mDocument ? mDocument : mImportParent)->ImportManager();
+ }
+
+ // Simply getter for the import document. Can return a partially parsed
+ // document if called too early.
+ nsIDocument* GetDocument()
+ {
+ return mDocument;
+ }
+
+ // Getter for the import document that is used in the spec. Returns
+ // nullptr if the import is not yet ready.
+ nsIDocument* GetImport()
+ {
+ if (!mReady) {
+ return nullptr;
+ }
+ return mDocument;
+ }
+
+ // There is only one referring link that is marked as primary link per
+ // imports. This is the one that has to be taken into account when
+ // scrip execution order is determined. Links marked as primary link form
+ // a spanning tree in the import graph. (Eliminating the cycles and
+ // multiple parents.) This spanning tree is recalculated every time
+ // a new import link is added to the manager.
+ nsINode* GetMainReferrer()
+ {
+ if (mLinks.IsEmpty()) {
+ return nullptr;
+ }
+ return mLinks[mMainReferrer];
+ }
+
+ // An import is not only blocked by its import children, but also
+ // by its predecessors. It's enough to find the closest predecessor
+ // and wait for that to run its scripts. We keep track of all the
+ // ScriptRunners that are waiting for this import. NOTE: updating
+ // the main referrer might change this list.
+ void AddBlockedScriptLoader(nsScriptLoader* aScriptLoader);
+ bool RemoveBlockedScriptLoader(nsScriptLoader* aScriptLoader);
+ void SetBlockingPredecessor(ImportLoader* aLoader);
+
+private:
+ ~ImportLoader() {}
+
+ // If a new referrer LinkElement was added, let's
+ // see if we are already finished and if so fire
+ // the right event.
+ void DispatchEventIfFinished(nsINode* aNode);
+
+ // Dispatch event for a single referrer LinkElement.
+ void DispatchErrorEvent(nsINode* aNode);
+ void DispatchLoadEvent(nsINode* aNode);
+
+ // Must be called when an error has occured during load.
+ void Error(bool aUnblockScripts);
+
+ // Must be called when the import document has been loaded successfully.
+ void Done();
+
+ // When the reading from the channel and the parsing
+ // of the document is done, we can release the resources
+ // that we don't need any longer to hold on.
+ void ReleaseResources();
+
+ // While the document is being loaded we must block scripts
+ // on the import parent document.
+ void BlockScripts();
+ void UnblockScripts();
+
+ nsIPrincipal* Principal();
+
+ nsCOMPtr<nsIDocument> mDocument;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIStreamListener> mParserStreamListener;
+ nsCOMPtr<nsIDocument> mImportParent;
+ ImportLoader* mBlockingPredecessor;
+
+ // List of the LinkElements that are referring to this import
+ // we need to keep track of them so we can fire event on them.
+ nsTArray<nsCOMPtr<nsINode>> mLinks;
+
+ // List of pending ScriptLoaders that are waiting for this import
+ // to finish.
+ nsTArray<RefPtr<nsScriptLoader>> mBlockedScriptLoaders;
+
+ // There is always exactly one referrer link that is flagged as
+ // the main referrer the primary link. This is the one that is
+ // used in the script execution order calculation.
+ // ("Branch" according to the spec.)
+ uint32_t mMainReferrer;
+ bool mReady;
+ bool mStopped;
+ bool mBlockingScripts;
+ Updater mUpdater;
+};
+
+class ImportManager final : public nsISupports
+{
+ typedef nsRefPtrHashtable<nsURIHashKey, ImportLoader> ImportMap;
+
+ ~ImportManager() {}
+
+public:
+ ImportManager() {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ImportManager)
+
+ // Finds the ImportLoader that belongs to aImport in the map.
+ ImportLoader* Find(nsIDocument* aImport);
+
+ // Find the ImportLoader aLink refers to.
+ ImportLoader* Find(nsINode* aLink);
+
+ void AddLoaderWithNewURI(ImportLoader* aLoader, nsIURI* aNewURI);
+
+ // When a new import link is added, this getter either creates
+ // a new ImportLoader for it, or returns an existing one if
+ // it was already created and in the import map.
+ already_AddRefed<ImportLoader> Get(nsIURI* aURI, nsINode* aNode,
+ nsIDocument* aOriginDocument);
+
+ // It finds the predecessor for an import link node that runs its
+ // scripts the latest among its predecessors.
+ ImportLoader* GetNearestPredecessor(nsINode* aNode);
+
+private:
+ ImportMap mImports;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ImportManager_h__
diff --git a/dom/base/IndexedDBHelper.jsm b/dom/base/IndexedDBHelper.jsm
new file mode 100644
index 000000000..587fb901f
--- /dev/null
+++ b/dom/base/IndexedDBHelper.jsm
@@ -0,0 +1,208 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+var DEBUG = 0;
+var debug;
+if (DEBUG) {
+ debug = function (s) { dump("-*- IndexedDBHelper: " + s + "\n"); }
+} else {
+ debug = function (s) {}
+}
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+this.EXPORTED_SYMBOLS = ["IndexedDBHelper"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.importGlobalProperties(["indexedDB"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, 'Services',
+ 'resource://gre/modules/Services.jsm');
+
+function getErrorName(err) {
+ return err && err.name || "UnknownError";
+}
+
+this.IndexedDBHelper = 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.currentThread.dispatch(() => invokeCallbacks(getErrorName(e)),
+ Ci.nsIThread.DISPATCH_NORMAL);
+ 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.currentThread.dispatch(aSuccessCb,
+ Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ 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.
+ * @param failureCb
+ * Error callback to call when an error is encountered.
+ */
+ newTxn: function newTxn(txn_type, store_name, callback, successCb, failureCb) {
+ this.ensureDB(function () {
+ 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 (event) {
+ if (DEBUG) debug("Transaction complete. Returning to callback.");
+ if (successCb) {
+ successCb(txn.result);
+ }
+ };
+
+ txn.onabort = function (event) {
+ if (DEBUG) debug("Caught error on transaction");
+ /*
+ * event.target.error may be null
+ * if txn was aborted by calling txn.abort()
+ */
+ if (failureCb) {
+ failureCb(getErrorName(event.target.error));
+ }
+ };
+ callback(txn, stores);
+ }.bind(this), 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/Link.cpp b/dom/base/Link.cpp
new file mode 100644
index 000000000..43bbe5c3a
--- /dev/null
+++ b/dom/base/Link.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 "Link.h"
+
+#include "mozilla/EventStates.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Element.h"
+#include "nsIURL.h"
+#include "nsISizeOf.h"
+#include "nsIDocShell.h"
+#include "nsIPrefetchService.h"
+#include "nsCPrefetchService.h"
+#include "nsStyleLinkElement.h"
+
+#include "nsEscape.h"
+#include "nsGkAtoms.h"
+#include "nsHTMLDNSPrefetch.h"
+#include "nsString.h"
+#include "mozAutoDocUpdate.h"
+
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace dom {
+
+Link::Link(Element *aElement)
+ : mElement(aElement)
+ , mHistory(services::GetHistoryService())
+ , mLinkState(eLinkState_NotLink)
+ , mNeedsRegistration(false)
+ , mRegistered(false)
+{
+ MOZ_ASSERT(mElement, "Must have an element");
+}
+
+Link::~Link()
+{
+ UnregisterFromHistory();
+}
+
+bool
+Link::ElementHasHref() const
+{
+ return mElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href) ||
+ (!mElement->IsHTMLElement() &&
+ mElement->HasAttr(kNameSpaceID_XLink, nsGkAtoms::href));
+}
+
+void
+Link::TryDNSPrefetch()
+{
+ MOZ_ASSERT(mElement->IsInComposedDoc());
+ if (ElementHasHref() && nsHTMLDNSPrefetch::IsAllowed(mElement->OwnerDoc())) {
+ nsHTMLDNSPrefetch::PrefetchLow(this);
+ }
+}
+
+void
+Link::CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
+ nsWrapperCache::FlagsType aRequestedFlag)
+{
+ // If prefetch was deferred, clear flag and move on
+ if (mElement->HasFlag(aDeferredFlag)) {
+ mElement->UnsetFlags(aDeferredFlag);
+ // Else if prefetch was requested, clear flag and send cancellation
+ } else if (mElement->HasFlag(aRequestedFlag)) {
+ mElement->UnsetFlags(aRequestedFlag);
+ // Possible that hostname could have changed since binding, but since this
+ // covers common cases, most DNS prefetch requests will be canceled
+ nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
+ }
+}
+
+void
+Link::TryDNSPrefetchPreconnectOrPrefetch()
+{
+ MOZ_ASSERT(mElement->IsInComposedDoc());
+ if (!ElementHasHref()) {
+ return;
+ }
+
+ nsAutoString rel;
+ if (!mElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel)) {
+ return;
+ }
+
+ if (!nsContentUtils::PrefetchEnabled(mElement->OwnerDoc()->GetDocShell())) {
+ return;
+ }
+
+ uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel,
+ mElement->NodePrincipal());
+
+ if ((linkTypes & nsStyleLinkElement::ePREFETCH) ||
+ (linkTypes & nsStyleLinkElement::eNEXT)){
+ nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
+ if (prefetchService) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (uri) {
+ nsCOMPtr<nsIDOMNode> domNode = GetAsDOMNode(mElement);
+ prefetchService->PrefetchURI(uri,
+ mElement->OwnerDoc()->GetDocumentURI(),
+ domNode, linkTypes & nsStyleLinkElement::ePREFETCH);
+ return;
+ }
+ }
+ }
+
+ if (linkTypes & nsStyleLinkElement::ePRECONNECT) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (uri && mElement->OwnerDoc()) {
+ mElement->OwnerDoc()->MaybePreconnect(uri,
+ mElement->AttrValueToCORSMode(mElement->GetParsedAttr(nsGkAtoms::crossorigin)));
+ return;
+ }
+ }
+
+ if (linkTypes & nsStyleLinkElement::eDNS_PREFETCH) {
+ if (nsHTMLDNSPrefetch::IsAllowed(mElement->OwnerDoc())) {
+ nsHTMLDNSPrefetch::PrefetchLow(this);
+ }
+ }
+}
+
+void
+Link::CancelPrefetch()
+{
+ nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
+ if (prefetchService) {
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (uri) {
+ nsCOMPtr<nsIDOMNode> domNode = GetAsDOMNode(mElement);
+ prefetchService->CancelPrefetchURI(uri, domNode);
+ }
+ }
+}
+
+void
+Link::SetLinkState(nsLinkState aState)
+{
+ NS_ASSERTION(mRegistered,
+ "Setting the link state of an unregistered Link!");
+ NS_ASSERTION(mLinkState != aState,
+ "Setting state to the currently set state!");
+
+ // Set our current state as appropriate.
+ mLinkState = aState;
+
+ // Per IHistory interface documentation, we are no longer registered.
+ mRegistered = false;
+
+ MOZ_ASSERT(LinkState() == NS_EVENT_STATE_VISITED ||
+ LinkState() == NS_EVENT_STATE_UNVISITED,
+ "Unexpected state obtained from LinkState()!");
+
+ // Tell the element to update its visited state
+ mElement->UpdateState(true);
+}
+
+EventStates
+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!
+ 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()) {
+ // Only try and register once.
+ self->mNeedsRegistration = false;
+
+ nsCOMPtr<nsIURI> hrefURI(GetURI());
+
+ // Assume that we are not visited until we are told otherwise.
+ self->mLinkState = eLinkState_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) {
+ nsresult rv = mHistory->RegisterVisitedCallback(hrefURI, self);
+ if (NS_SUCCEEDED(rv)) {
+ self->mRegistered = true;
+
+ // And make sure we are in the document's link map.
+ element->GetComposedDoc()->AddStyleRelevantLink(self);
+ }
+ }
+ }
+
+ // Otherwise, return our known state.
+ if (mLinkState == eLinkState_Visited) {
+ return NS_EVENT_STATE_VISITED;
+ }
+
+ if (mLinkState == eLinkState_Unvisited) {
+ return NS_EVENT_STATE_UNVISITED;
+ }
+
+ return EventStates();
+}
+
+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(GetURIToMutate());
+ 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);
+ (void)uri->SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)));
+
+ SetHrefAttribute(uri);
+}
+
+void
+Link::SetPassword(const nsAString &aPassword)
+{
+ nsCOMPtr<nsIURI> uri(GetURIToMutate());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ uri->SetPassword(NS_ConvertUTF16toUTF8(aPassword));
+ SetHrefAttribute(uri);
+}
+
+void
+Link::SetUsername(const nsAString &aUsername)
+{
+ nsCOMPtr<nsIURI> uri(GetURIToMutate());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ uri->SetUsername(NS_ConvertUTF16toUTF8(aUsername));
+ SetHrefAttribute(uri);
+}
+
+void
+Link::SetHost(const nsAString &aHost)
+{
+ nsCOMPtr<nsIURI> uri(GetURIToMutate());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ (void)uri->SetHostPort(NS_ConvertUTF16toUTF8(aHost));
+ SetHrefAttribute(uri);
+}
+
+void
+Link::SetHostname(const nsAString &aHostname)
+{
+ nsCOMPtr<nsIURI> uri(GetURIToMutate());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ (void)uri->SetHost(NS_ConvertUTF16toUTF8(aHostname));
+ SetHrefAttribute(uri);
+}
+
+void
+Link::SetPathname(const nsAString &aPathname)
+{
+ nsCOMPtr<nsIURI> uri(GetURIToMutate());
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (!url) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ (void)url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname));
+ SetHrefAttribute(uri);
+}
+
+void
+Link::SetSearch(const nsAString& aSearch)
+{
+ nsCOMPtr<nsIURI> uri(GetURIToMutate());
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (!url) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ (void)url->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
+ SetHrefAttribute(uri);
+}
+
+void
+Link::SetPort(const nsAString &aPort)
+{
+ nsCOMPtr<nsIURI> uri(GetURIToMutate());
+ 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;
+ }
+ }
+
+ (void)uri->SetPort(port);
+ SetHrefAttribute(uri);
+}
+
+void
+Link::SetHash(const nsAString &aHash)
+{
+ nsCOMPtr<nsIURI> uri(GetURIToMutate());
+ if (!uri) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ (void)uri->SetRef(NS_ConvertUTF16toUTF8(aHash));
+ 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) {
+ _protocol.AssignLiteral("http");
+ }
+ else {
+ 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());
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (!url) {
+ // Do not throw! Not having a valid URI or URL should result in an empty
+ // string.
+ return;
+ }
+
+ nsAutoCString file;
+ nsresult rv = url->GetFilePath(file);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(file, _pathname);
+ }
+}
+
+void
+Link::GetSearch(nsAString &_search)
+{
+ _search.Truncate();
+
+ nsCOMPtr<nsIURI> uri(GetURI());
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (!url) {
+ // Do not throw! Not having a valid URI or URL should result in an empty
+ // string.
+ return;
+ }
+
+ nsAutoCString search;
+ nsresult rv = url->GetQuery(search);
+ if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
+ CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + 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('#'));
+ if (nsContentUtils::GettersDecodeURLHash()) {
+ NS_UnescapeURL(ref); // XXX may result in random non-ASCII bytes!
+ }
+ AppendUTF8toUTF16(ref, _hash);
+ }
+}
+
+void
+Link::ResetLinkState(bool aNotify, bool aHasHref)
+{
+ nsLinkState defaultState;
+
+ // The default state for links with an href is unvisited.
+ if (aHasHref) {
+ defaultState = eLinkState_Unvisited;
+ } else {
+ defaultState = eLinkState_NotLink;
+ }
+
+ // 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 && mLinkState != eLinkState_NotLink) {
+ nsIDocument *doc = mElement->GetComposedDoc();
+ if (doc && (mRegistered || mLinkState == eLinkState_Visited)) {
+ // Tell the document to forget about this link if we've registered
+ // with it before.
+ doc->ForgetLink(this);
+ }
+
+ UnregisterFromHistory();
+ }
+
+ // If we have an href, we should register with the history.
+ mNeedsRegistration = aHasHref;
+
+ // If we've cached the URI, reset always invalidates it.
+ mCachedURI = nullptr;
+
+ // Update our state back to the default.
+ mLinkState = defaultState;
+
+ // 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 (mLinkState == eLinkState_Unvisited) {
+ mElement->UpdateLinkState(NS_EVENT_STATE_UNVISITED);
+ } else {
+ mElement->UpdateLinkState(EventStates());
+ }
+ }
+}
+
+void
+Link::UnregisterFromHistory()
+{
+ // If we are not registered, we have nothing to do.
+ if (!mRegistered) {
+ return;
+ }
+
+ NS_ASSERTION(mCachedURI, "mRegistered is true, but we have no cached URI?!");
+
+ // And tell History to stop tracking us.
+ if (mHistory) {
+ nsresult rv = mHistory->UnregisterVisitedCallback(mCachedURI, this);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "This should only fail if we misuse the API!");
+ if (NS_SUCCEEDED(rv)) {
+ mRegistered = false;
+ }
+ }
+}
+
+already_AddRefed<nsIURI>
+Link::GetURIToMutate()
+{
+ nsCOMPtr<nsIURI> uri(GetURI());
+ if (!uri) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIURI> clone;
+ (void)uri->Clone(getter_AddRefs(clone));
+ return clone.forget();
+}
+
+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::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+
+ if (mCachedURI) {
+ nsCOMPtr<nsISizeOf> iface = do_QueryInterface(mCachedURI);
+ if (iface) {
+ n += iface->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ // The following members don't need to be measured:
+ // - mElement, because it is a pointer-to-self used to avoid QIs
+ // - mHistory, because it is non-owning
+
+ return n;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/Link.h b/dom/base/Link.h
new file mode 100644
index 000000000..42bbfa791
--- /dev/null
+++ b/dom/base/Link.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/. */
+
+/**
+ * This is the base class for all link classes.
+ */
+
+#ifndef mozilla_dom_Link_h__
+#define mozilla_dom_Link_h__
+
+#include "mozilla/IHistory.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsIContent.h" // for nsLinkState
+
+namespace mozilla {
+
+class EventStates;
+
+namespace dom {
+
+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)
+
+ /**
+ * aElement is the element pointer corresponding to this link.
+ */
+ explicit Link(Element* aElement);
+ virtual void SetLinkState(nsLinkState aState);
+
+ /**
+ * @return NS_EVENT_STATE_VISITED if this link is visited,
+ * NS_EVENT_STATE_UNVISTED if this link is not visited, or 0 if this
+ * link is not actually a link.
+ */
+ EventStates LinkState() const;
+
+ /**
+ * @return the URI this link is for, if available.
+ */
+ nsIURI* GetURI() const;
+ virtual nsIURI* GetURIExternal() const {
+ return GetURI();
+ }
+
+ /**
+ * 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; }
+
+ /**
+ * DNS prefetch has been deferred until later, e.g. page load complete.
+ */
+ virtual void OnDNSPrefetchDeferred() { /*do nothing*/ }
+
+ /**
+ * DNS prefetch has been submitted to Host Resolver.
+ */
+ virtual void OnDNSPrefetchRequested() { /*do nothing*/ }
+
+ /**
+ * Checks if DNS Prefetching is ok
+ *
+ * @returns boolean
+ * Defaults to true; should be overridden for specialised cases
+ */
+ virtual bool HasDeferredDNSPrefetchRequest() { return true; }
+
+ virtual size_t
+ SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ bool ElementHasHref() const;
+
+ // This is called by HTMLAnchorElement.
+ void TryDNSPrefetch();
+ void CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
+ nsWrapperCache::FlagsType aRequestedFlag);
+
+ // This is called by HTMLLinkElement.
+ void TryDNSPrefetchPreconnectOrPrefetch();
+ void CancelPrefetch();
+
+protected:
+ virtual ~Link();
+
+ /**
+ * Return true if the link has associated URI.
+ */
+ bool HasURI() const
+ {
+ if (HasCachedURI()) {
+ return true;
+ }
+
+ return !!GetURI();
+ }
+
+ 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();
+
+ already_AddRefed<nsIURI> GetURIToMutate();
+ void SetHrefAttribute(nsIURI *aURI);
+
+ mutable nsCOMPtr<nsIURI> mCachedURI;
+
+ Element * const mElement;
+
+ // Strong reference to History. The link has to unregister before History
+ // can disappear.
+ nsCOMPtr<IHistory> mHistory;
+
+ uint16_t mLinkState;
+
+ bool mNeedsRegistration;
+
+ bool mRegistered;
+};
+
+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/Location.cpp b/dom/base/Location.cpp
new file mode 100644
index 000000000..e3b614931
--- /dev/null
+++ b/dom/base/Location.cpp
@@ -0,0 +1,941 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIScriptSecurityManager.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptContext.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIWebNavigation.h"
+#include "nsCDefaultURIFixup.h"
+#include "nsIURIFixup.h"
+#include "nsIURL.h"
+#include "nsIJARURI.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsEscape.h"
+#include "nsIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsError.h"
+#include "nsDOMClassInfoID.h"
+#include "nsReadableUtils.h"
+#include "nsITextToSubURI.h"
+#include "nsJSUtils.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/Likely.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsNullPrincipal.h"
+#include "ScriptSettings.h"
+#include "mozilla/dom/LocationBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+static nsresult
+GetDocumentCharacterSetForURI(const nsAString& aHref, nsACString& aCharset)
+{
+ aCharset.Truncate();
+
+ if (nsIDocument* doc = GetEntryDocument()) {
+ aCharset = doc->GetDocumentCharacterSet();
+ }
+
+ return NS_OK;
+}
+
+Location::Location(nsPIDOMWindowInner* aWindow, nsIDocShell *aDocShell)
+ : mInnerWindow(aWindow)
+{
+ MOZ_ASSERT(aDocShell);
+ MOZ_ASSERT(mInnerWindow->IsInnerWindow());
+
+ mDocShell = do_GetWeakReference(aDocShell);
+}
+
+Location::~Location()
+{
+}
+
+// QueryInterface implementation for Location
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Location)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMLocation)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMLocation)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Location)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Location)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInnerWindow);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Location)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInnerWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Location)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Location)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Location)
+
+void
+Location::SetDocShell(nsIDocShell *aDocShell)
+{
+ mDocShell = do_GetWeakReference(aDocShell);
+}
+
+nsIDocShell *
+Location::GetDocShell()
+{
+ nsCOMPtr<nsIDocShell> docshell(do_QueryReferent(mDocShell));
+ return docshell;
+}
+
+nsresult
+Location::CheckURL(nsIURI* aURI, nsIDocShellLoadInfo** aLoadInfo)
+{
+ *aLoadInfo = nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
+ NS_ENSURE_TRUE(docShell, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ nsCOMPtr<nsIURI> sourceURI;
+ net::ReferrerPolicy referrerPolicy = net::RP_Default;
+
+ if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
+ // No cx means that there's no JS running, or at least no JS that
+ // was run through code that properly pushed a context onto the
+ // context stack (as all code that runs JS off of web pages
+ // does). We won't bother with security checks in this case, but
+ // we need to create the loadinfo etc.
+
+ // Get security manager.
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ENSURE_STATE(ssm);
+
+ // Check to see if URI is allowed.
+ nsresult rv = ssm->CheckLoadURIFromScript(cx, aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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<nsIDocument> doc = incumbent ? incumbent->GetDoc() : nullptr;
+
+ if (doc) {
+ nsCOMPtr<nsIURI> docOriginalURI, docCurrentURI, principalURI;
+ docOriginalURI = doc->GetOriginalURI();
+ docCurrentURI = doc->GetDocumentURI();
+ rv = doc->NodePrincipal()->GetURI(getter_AddRefs(principalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ triggeringPrincipal = doc->NodePrincipal();
+ referrerPolicy = doc->GetReferrerPolicy();
+
+ bool urisEqual = false;
+ if (docOriginalURI && docCurrentURI && principalURI) {
+ principalURI->Equals(docOriginalURI, &urisEqual);
+ }
+ if (urisEqual) {
+ sourceURI = docCurrentURI;
+ }
+ else {
+ // Use principalURI as long as it is not an nsNullPrincipalURI. We
+ // could add a method such as GetReferrerURI to principals to make this
+ // cleaner, but given that we need to start using Source Browsing
+ // Context for referrer (see Bug 960639) this may be wasted effort at
+ // this stage.
+ if (principalURI) {
+ bool isNullPrincipalScheme;
+ rv = principalURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME,
+ &isNullPrincipalScheme);
+ if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) {
+ sourceURI = principalURI;
+ }
+ }
+ }
+ }
+ else {
+ // No document; determine triggeringPrincipal by quering the
+ // subjectPrincipal, wich is the principal of the current JS
+ // compartment, or a null principal in case there is no
+ // compartment yet.
+ triggeringPrincipal = nsContentUtils::SubjectPrincipal();
+ }
+ }
+
+ // Create load info
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
+
+ loadInfo->SetTriggeringPrincipal(triggeringPrincipal);
+
+ if (sourceURI) {
+ loadInfo->SetReferrer(sourceURI);
+ loadInfo->SetReferrerPolicy(referrerPolicy);
+ }
+
+ loadInfo.swap(*aLoadInfo);
+
+ return NS_OK;
+}
+
+nsresult
+Location::GetURI(nsIURI** aURI, bool aGetInnermostURI)
+{
+ *aURI = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
+ 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<nsIURIFixup> urifixup(do_GetService(NS_URIFIXUP_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return urifixup->CreateExposableURI(uri, aURI);
+}
+
+nsresult
+Location::GetWritableURI(nsIURI** aURI, const nsACString* aNewRef)
+{
+ *aURI = nullptr;
+
+ nsCOMPtr<nsIURI> uri;
+
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv) || !uri) {
+ return rv;
+ }
+
+ if (!aNewRef) {
+ return uri->Clone(aURI);
+ }
+
+ return uri->CloneWithNewRef(*aNewRef, aURI);
+}
+
+nsresult
+Location::SetURI(nsIURI* aURI, bool aReplace)
+{
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
+ if (docShell) {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+
+ if(NS_FAILED(CheckURL(aURI, getter_AddRefs(loadInfo))))
+ return NS_ERROR_FAILURE;
+
+ if (aReplace) {
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContentAndReplace);
+ } else {
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContent);
+ }
+
+ // Get the incumbent script's browsing context to set as source.
+ nsCOMPtr<nsPIDOMWindowInner> sourceWindow =
+ do_QueryInterface(mozilla::dom::GetIncumbentGlobal());
+ if (sourceWindow) {
+ loadInfo->SetSourceDocShell(sourceWindow->GetDocShell());
+ }
+
+ return docShell->LoadURI(aURI, loadInfo,
+ nsIWebNavigation::LOAD_FLAGS_NONE, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Location::GetHash(nsAString& aHash)
+{
+ aHash.SetLength(0);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv) || !uri) {
+ return rv;
+ }
+
+ nsAutoCString ref;
+ nsAutoString unicodeRef;
+
+ rv = uri->GetRef(ref);
+
+ if (nsContentUtils::GettersDecodeURLHash()) {
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsITextToSubURI> textToSubURI(
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString charset;
+ uri->GetOriginCharset(charset);
+
+ rv = textToSubURI->UnEscapeURIForUI(charset, ref, unicodeRef);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Oh, well. No intl here!
+ NS_UnescapeURL(ref);
+ CopyASCIItoUTF16(ref, unicodeRef);
+ rv = NS_OK;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && !unicodeRef.IsEmpty()) {
+ aHash.Assign(char16_t('#'));
+ aHash.Append(unicodeRef);
+ }
+ } else { // URL Hash should simply return the value of the Ref segment
+ if (NS_SUCCEEDED(rv) && !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;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+Location::SetHash(const nsAString& aHash)
+{
+ NS_ConvertUTF16toUTF8 hash(aHash);
+ if (hash.IsEmpty() || hash.First() != char16_t('#')) {
+ hash.Insert(char16_t('#'), 0);
+ }
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetWritableURI(getter_AddRefs(uri), &hash);
+ if (NS_FAILED(rv) || !uri) {
+ return rv;
+ }
+
+ return SetURI(uri);
+}
+
+NS_IMETHODIMP
+Location::GetHost(nsAString& aHost)
+{
+ 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);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Location::SetHost(const nsAString& aHost)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetWritableURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv) || !uri)) {
+ return rv;
+ }
+
+ rv = uri->SetHostPort(NS_ConvertUTF16toUTF8(aHost));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return SetURI(uri);
+}
+
+NS_IMETHODIMP
+Location::GetHostname(nsAString& aHostname)
+{
+ aHostname.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri), true);
+ if (uri) {
+ nsContentUtils::GetHostOrIPv6WithBrackets(uri, aHostname);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Location::SetHostname(const nsAString& aHostname)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetWritableURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv) || !uri)) {
+ return rv;
+ }
+
+ rv = uri->SetHost(NS_ConvertUTF16toUTF8(aHostname));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return SetURI(uri);
+}
+
+NS_IMETHODIMP
+Location::GetHref(nsAString& aHref)
+{
+ aHref.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult result;
+
+ result = GetURI(getter_AddRefs(uri));
+
+ if (uri) {
+ nsAutoCString uriString;
+
+ result = uri->GetSpec(uriString);
+
+ if (NS_SUCCEEDED(result)) {
+ AppendUTF8toUTF16(uriString, aHref);
+ }
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+Location::SetHref(const nsAString& aHref)
+{
+ nsAutoString oldHref;
+ nsresult rv = NS_OK;
+
+ JSContext *cx = nsContentUtils::GetCurrentJSContext();
+ if (cx) {
+ rv = SetHrefWithContext(cx, aHref, false);
+ } else {
+ rv = GetHref(oldHref);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIURI> oldUri;
+
+ rv = NS_NewURI(getter_AddRefs(oldUri), oldHref);
+
+ if (oldUri) {
+ rv = SetHrefWithBase(aHref, oldUri, false);
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+Location::SetHrefWithContext(JSContext* cx, const nsAString& aHref,
+ bool aReplace)
+{
+ nsCOMPtr<nsIURI> base;
+
+ // Get the source of the caller
+ nsresult result = GetSourceBaseURL(cx, getter_AddRefs(base));
+
+ if (NS_FAILED(result)) {
+ return result;
+ }
+
+ return SetHrefWithBase(aHref, base, aReplace);
+}
+
+nsresult
+Location::SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
+ bool aReplace)
+{
+ nsresult result;
+ nsCOMPtr<nsIURI> newUri;
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
+
+ nsAutoCString docCharset;
+ if (NS_SUCCEEDED(GetDocumentCharacterSetForURI(aHref, docCharset)))
+ result = NS_NewURI(getter_AddRefs(newUri), aHref, docCharset.get(), 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 = nsGlobalWindow::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<nsIScriptGlobalObject> ourGlobal =
+ docShell ? docShell->GetScriptGlobalObject() : nullptr;
+ inScriptTag = (ourGlobal == scriptContext->GetGlobalObject());
+ }
+ }
+
+ return SetURI(newUri, aReplace || inScriptTag);
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+Location::GetOrigin(nsAString& aOrigin)
+{
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(uri, NS_OK);
+
+ nsAutoString origin;
+ rv = nsContentUtils::GetUTFOrigin(uri, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOrigin = origin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Location::GetPathname(nsAString& aPathname)
+{
+ aPathname.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult result = NS_OK;
+
+ result = GetURI(getter_AddRefs(uri));
+
+ nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(uri));
+ if (url) {
+ nsAutoCString file;
+
+ result = url->GetFilePath(file);
+
+ if (NS_SUCCEEDED(result)) {
+ AppendUTF8toUTF16(file, aPathname);
+ }
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+Location::SetPathname(const nsAString& aPathname)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetWritableURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv) || !uri)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(uri));
+ if (url && NS_SUCCEEDED(url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname)))) {
+ return SetURI(uri);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Location::GetPort(nsAString& aPort)
+{
+ aPort.SetLength(0);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult result = NS_OK;
+
+ result = GetURI(getter_AddRefs(uri), true);
+
+ if (uri) {
+ int32_t port;
+ result = uri->GetPort(&port);
+
+ if (NS_SUCCEEDED(result) && -1 != port) {
+ nsAutoString portStr;
+ portStr.AppendInt(port);
+ aPort.Append(portStr);
+ }
+
+ // Don't propagate this exception to caller
+ result = NS_OK;
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+Location::SetPort(const nsAString& aPort)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetWritableURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv) || !uri)) {
+ return rv;
+ }
+
+ // 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);
+ }
+ }
+
+ rv = uri->SetPort(port);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return SetURI(uri);
+}
+
+NS_IMETHODIMP
+Location::GetProtocol(nsAString& aProtocol)
+{
+ aProtocol.SetLength(0);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult result = NS_OK;
+
+ result = GetURI(getter_AddRefs(uri));
+
+ if (uri) {
+ nsAutoCString protocol;
+
+ result = uri->GetScheme(protocol);
+
+ if (NS_SUCCEEDED(result)) {
+ CopyASCIItoUTF16(protocol, aProtocol);
+ aProtocol.Append(char16_t(':'));
+ }
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+Location::SetProtocol(const nsAString& aProtocol)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetWritableURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv) || !uri)) {
+ return rv;
+ }
+
+ rv = uri->SetScheme(NS_ConvertUTF16toUTF8(aProtocol));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsAutoCString newSpec;
+ rv = uri->GetSpec(newSpec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // 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)) {
+ return rv;
+ }
+
+ return SetURI(uri);
+}
+
+NS_IMETHODIMP
+Location::GetSearch(nsAString& aSearch)
+{
+ 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);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Location::SetSearch(const nsAString& aSearch)
+{
+ nsresult rv = SetSearchInternal(aSearch);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Location::SetSearchInternal(const nsAString& aSearch)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetWritableURI(getter_AddRefs(uri));
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (NS_WARN_IF(NS_FAILED(rv) || !url)) {
+ return rv;
+ }
+
+ rv = url->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return SetURI(uri);
+}
+
+NS_IMETHODIMP
+Location::Reload(bool aForceget)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
+ nsCOMPtr<nsPIDOMWindowOuter> window = docShell ? docShell->GetWindow()
+ : nullptr;
+
+ 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.
+
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+
+ nsIPresShell *shell;
+ nsPresContext *pcx;
+ if (doc && (shell = doc->GetShell()) && (pcx = shell->GetPresContext())) {
+ pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW, eRestyle_Subtree);
+ }
+
+ return NS_OK;
+ }
+
+ if (webNav) {
+ uint32_t reloadFlags = nsIWebNavigation::LOAD_FLAGS_NONE;
+
+ if (aForceget) {
+ reloadFlags = nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY;
+ }
+ rv = webNav->Reload(reloadFlags);
+ if (rv == NS_BINDING_ABORTED) {
+ // This happens 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.
+ rv = NS_OK;
+ }
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+Location::Replace(const nsAString& aUrl)
+{
+ nsresult rv = NS_OK;
+ if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
+ return SetHrefWithContext(cx, aUrl, true);
+ }
+
+ nsAutoString oldHref;
+
+ rv = GetHref(oldHref);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> oldUri;
+
+ rv = NS_NewURI(getter_AddRefs(oldUri), oldHref);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SetHrefWithBase(aUrl, oldUri, true);
+}
+
+NS_IMETHODIMP
+Location::Assign(const nsAString& aUrl)
+{
+ if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
+ return SetHrefWithContext(cx, aUrl, false);
+ }
+
+ nsAutoString oldHref;
+ nsresult result = NS_OK;
+
+ result = GetHref(oldHref);
+
+ if (NS_SUCCEEDED(result)) {
+ nsCOMPtr<nsIURI> oldUri;
+
+ result = NS_NewURI(getter_AddRefs(oldUri), oldHref);
+
+ if (oldUri) {
+ result = SetHrefWithBase(aUrl, oldUri, false);
+ }
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+Location::ToString(nsAString& aReturn)
+{
+ return GetHref(aReturn);
+}
+
+NS_IMETHODIMP
+Location::ValueOf(nsIDOMLocation** aReturn)
+{
+ nsCOMPtr<nsIDOMLocation> loc(this);
+ loc.forget(aReturn);
+ return NS_OK;
+}
+
+nsresult
+Location::GetSourceBaseURL(JSContext* cx, nsIURI** sourceURL)
+{
+ *sourceURL = nullptr;
+ nsIDocument* 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 && GetDocShell()) {
+ nsCOMPtr<nsPIDOMWindowOuter> docShellWin =
+ do_QueryInterface(GetDocShell()->GetScriptGlobalObject());
+ if (docShellWin) {
+ doc = docShellWin->GetDoc();
+ }
+ }
+ NS_ENSURE_TRUE(doc, NS_OK);
+ *sourceURL = doc->GetBaseURI().take();
+ return NS_OK;
+}
+
+bool
+Location::CallerSubsumes(nsIPrincipal* aSubjectPrincipal)
+{
+ MOZ_ASSERT(aSubjectPrincipal);
+
+ // 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 = mInnerWindow->GetOuterWindow();
+ 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 LocationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/base/Location.h b/dom/base/Location.h
new file mode 100644
index 000000000..cc96ce558
--- /dev/null
+++ b/dom/base/Location.h
@@ -0,0 +1,258 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsCycleCollectionParticipant.h"
+#include "nsIDOMLocation.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+class nsIDocShell;
+class nsIDocShellLoadInfo;
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+
+//*****************************************************************************
+// Location: Script "location" object
+//*****************************************************************************
+
+class Location final : public nsIDOMLocation
+ , public nsWrapperCache
+{
+public:
+ Location(nsPIDOMWindowInner* aWindow, nsIDocShell *aDocShell);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Location,
+ nsIDOMLocation)
+
+ void SetDocShell(nsIDocShell *aDocShell);
+ nsIDocShell *GetDocShell();
+
+ // nsIDOMLocation
+ NS_DECL_NSIDOMLOCATION
+
+ #define THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME() { \
+ if (!CallerSubsumes(&aSubjectPrincipal)) { \
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR); \
+ return; \
+ } \
+ }
+
+ // WebIDL API:
+ void Assign(const nsAString& aUrl,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = Assign(aUrl);
+ }
+
+ void Replace(const nsAString& aUrl,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ aError = Replace(aUrl);
+ }
+
+ void Reload(bool aForceget,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = Reload(aForceget);
+ }
+
+ void GetHref(nsAString& aHref,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetHref(aHref);
+ }
+
+ void SetHref(const nsAString& aHref,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ aError = SetHref(aHref);
+ }
+
+ void GetOrigin(nsAString& aOrigin,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetOrigin(aOrigin);
+ }
+
+ void GetProtocol(nsAString& aProtocol,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetProtocol(aProtocol);
+ }
+
+ void SetProtocol(const nsAString& aProtocol,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = SetProtocol(aProtocol);
+ }
+
+ void GetHost(nsAString& aHost,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetHost(aHost);
+ }
+
+ void SetHost(const nsAString& aHost,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = SetHost(aHost);
+ }
+
+ void GetHostname(nsAString& aHostname,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetHostname(aHostname);
+ }
+
+ void SetHostname(const nsAString& aHostname,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = SetHostname(aHostname);
+ }
+
+ void GetPort(nsAString& aPort,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetPort(aPort);
+ }
+
+ void SetPort(const nsAString& aPort,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = SetPort(aPort);
+ }
+
+ void GetPathname(nsAString& aPathname,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetPathname(aPathname);
+ }
+
+ void SetPathname(const nsAString& aPathname,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = SetPathname(aPathname);
+ }
+
+ void GetSearch(nsAString& aSeach,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetSearch(aSeach);
+ }
+
+ void SetSearch(const nsAString& aSeach,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = SetSearch(aSeach);
+ }
+
+ void GetHash(nsAString& aHash,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = GetHash(aHash);
+ }
+
+ void SetHash(const nsAString& aHash,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ THROW_AND_RETURN_IF_CALLER_DOESNT_SUBSUME();
+ aError = SetHash(aHash);
+ }
+
+ void Stringify(nsAString& aRetval,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+ {
+ // GetHref checks CallerSubsumes.
+ GetHref(aRetval, aSubjectPrincipal, aError);
+ }
+
+ nsPIDOMWindowInner* GetParentObject() const
+ {
+ return mInnerWindow;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ virtual ~Location();
+
+ nsresult SetSearchInternal(const nsAString& aSearch);
+
+ // 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.
+ nsresult GetURI(nsIURI** aURL, bool aGetInnermostURI = false);
+ nsresult GetWritableURI(nsIURI** aURL,
+ // If not null, give it the new ref
+ const nsACString* aNewRef = nullptr);
+ nsresult SetURI(nsIURI* aURL, bool aReplace = false);
+ nsresult SetHrefWithBase(const nsAString& aHref, nsIURI* aBase,
+ bool aReplace);
+ nsresult SetHrefWithContext(JSContext* cx, const nsAString& aHref,
+ bool aReplace);
+
+ nsresult GetSourceBaseURL(JSContext* cx, nsIURI** sourceURL);
+ nsresult CheckURL(nsIURI *url, nsIDocShellLoadInfo** aLoadInfo);
+ bool CallerSubsumes(nsIPrincipal* aSubjectPrincipal);
+
+ nsString mCachedHash;
+ nsCOMPtr<nsPIDOMWindowInner> mInnerWindow;
+ nsWeakPtr mDocShell;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_Location_h
diff --git a/dom/base/MultipartBlobImpl.cpp b/dom/base/MultipartBlobImpl.cpp
new file mode 100644
index 000000000..ba26d07f9
--- /dev/null
+++ b/dom/base/MultipartBlobImpl.cpp
@@ -0,0 +1,455 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "MultipartBlobImpl.h"
+#include "jsfriendapi.h"
+#include "mozilla/dom/BlobSet.h"
+#include "mozilla/dom/FileBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "nsDOMClassInfoID.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+#include "nsJSUtils.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#include "nsIXPConnect.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS_INHERITED0(MultipartBlobImpl, BlobImpl)
+
+/* static */ already_AddRefed<MultipartBlobImpl>
+MultipartBlobImpl::Create(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
+ const nsAString& aName,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ RefPtr<MultipartBlobImpl> blobImpl =
+ new MultipartBlobImpl(Move(aBlobImpls), aName, aContentType);
+ blobImpl->SetLengthAndModifiedDate(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return blobImpl.forget();
+}
+
+/* static */ already_AddRefed<MultipartBlobImpl>
+MultipartBlobImpl::Create(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ RefPtr<MultipartBlobImpl> blobImpl =
+ new MultipartBlobImpl(Move(aBlobImpls), aContentType);
+ blobImpl->SetLengthAndModifiedDate(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return blobImpl.forget();
+}
+
+void
+MultipartBlobImpl::GetInternalStream(nsIInputStream** aStream,
+ ErrorResult& aRv)
+{
+ *aStream = nullptr;
+
+ nsCOMPtr<nsIMultiplexInputStream> stream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ if (NS_WARN_IF(!stream)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ uint32_t i;
+ for (i = 0; i < mBlobImpls.Length(); i++) {
+ nsCOMPtr<nsIInputStream> scratchStream;
+ BlobImpl* blobImpl = mBlobImpls.ElementAt(i).get();
+
+ blobImpl->GetInternalStream(getter_AddRefs(scratchStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aRv = stream->AppendStream(scratchStream);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ stream.forget(aStream);
+}
+
+already_AddRefed<BlobImpl>
+MultipartBlobImpl::CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType,
+ ErrorResult& aRv)
+{
+ // If we clamped to nothing we create an empty blob
+ nsTArray<RefPtr<BlobImpl>> blobImpls;
+
+ uint64_t length = aLength;
+ uint64_t skipStart = aStart;
+
+ // Prune the list of blobs if we can
+ uint32_t i;
+ for (i = 0; length && skipStart && i < mBlobImpls.Length(); i++) {
+ BlobImpl* blobImpl = mBlobImpls[i].get();
+
+ uint64_t l = blobImpl->GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (skipStart < l) {
+ uint64_t upperBound = std::min<uint64_t>(l - skipStart, length);
+
+ RefPtr<BlobImpl> firstBlobImpl =
+ blobImpl->CreateSlice(skipStart, upperBound,
+ aContentType, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Avoid wrapping a single blob inside an MultipartBlobImpl
+ if (length == upperBound) {
+ return firstBlobImpl.forget();
+ }
+
+ blobImpls.AppendElement(firstBlobImpl);
+ length -= upperBound;
+ i++;
+ break;
+ }
+ skipStart -= l;
+ }
+
+ // Now append enough blobs until we're done
+ for (; length && i < mBlobImpls.Length(); i++) {
+ BlobImpl* blobImpl = mBlobImpls[i].get();
+
+ uint64_t l = blobImpl->GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (length < l) {
+ RefPtr<BlobImpl> lastBlobImpl =
+ blobImpl->CreateSlice(0, length, aContentType, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ blobImpls.AppendElement(lastBlobImpl);
+ } else {
+ blobImpls.AppendElement(blobImpl);
+ }
+ length -= std::min<uint64_t>(l, length);
+ }
+
+ // we can create our blob now
+ RefPtr<BlobImpl> impl = Create(Move(blobImpls), aContentType, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return impl.forget();
+}
+
+void
+MultipartBlobImpl::InitializeBlob(ErrorResult& aRv)
+{
+ SetLengthAndModifiedDate(aRv);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
+}
+
+void
+MultipartBlobImpl::InitializeBlob(JSContext* aCx,
+ const Sequence<Blob::BlobPart>& aData,
+ const nsAString& aContentType,
+ bool aNativeEOL,
+ ErrorResult& aRv)
+{
+ mContentType = aContentType;
+ BlobSet blobSet;
+
+ for (uint32_t i = 0, len = aData.Length(); i < len; ++i) {
+ const Blob::BlobPart& data = aData[i];
+
+ if (data.IsBlob()) {
+ RefPtr<Blob> blob = data.GetAsBlob().get();
+ blobSet.AppendBlobImpl(blob->Impl());
+ }
+
+ else if (data.IsUSVString()) {
+ aRv = blobSet.AppendString(data.GetAsUSVString(), aNativeEOL, aCx);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ else if (data.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = data.GetAsArrayBuffer();
+ buffer.ComputeLengthAndData();
+ aRv = blobSet.AppendVoidPtr(buffer.Data(), buffer.Length());
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ else if (data.IsArrayBufferView()) {
+ const ArrayBufferView& buffer = data.GetAsArrayBufferView();
+ buffer.ComputeLengthAndData();
+ aRv = blobSet.AppendVoidPtr(buffer.Data(), buffer.Length());
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ else {
+ MOZ_CRASH("Impossible blob data type.");
+ }
+ }
+
+
+ mBlobImpls = blobSet.GetBlobImpls();
+ SetLengthAndModifiedDate(aRv);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
+}
+
+void
+MultipartBlobImpl::SetLengthAndModifiedDate(ErrorResult& aRv)
+{
+ MOZ_ASSERT(mLength == UINT64_MAX);
+ MOZ_ASSERT(mLastModificationDate == INT64_MAX);
+
+ uint64_t totalLength = 0;
+ int64_t lastModified = 0;
+ bool lastModifiedSet = false;
+
+ for (uint32_t index = 0, count = mBlobImpls.Length(); index < count; index++) {
+ RefPtr<BlobImpl>& blob = mBlobImpls[index];
+
+#ifdef DEBUG
+ MOZ_ASSERT(!blob->IsSizeUnknown());
+ MOZ_ASSERT(!blob->IsDateUnknown());
+#endif
+
+ uint64_t subBlobLength = blob->GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(UINT64_MAX - subBlobLength >= totalLength);
+ totalLength += subBlobLength;
+
+ if (blob->IsFile()) {
+ int64_t partLastModified = blob->GetLastModified(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (lastModified < partLastModified) {
+ lastModified = partLastModified;
+ lastModifiedSet = true;
+ }
+ }
+ }
+
+ mLength = totalLength;
+
+ if (mIsFile) {
+ // We cannot use PR_Now() because bug 493756 and, for this reason:
+ // var x = new Date(); var f = new File(...);
+ // x.getTime() < f.dateModified.getTime()
+ // could fail.
+ mLastModificationDate =
+ lastModifiedSet ? lastModified * PR_USEC_PER_MSEC : JS_Now();
+ }
+}
+
+void
+MultipartBlobImpl::GetMozFullPathInternal(nsAString& aFilename,
+ ErrorResult& aRv) const
+{
+ if (!mIsFromNsIFile || mBlobImpls.Length() == 0) {
+ BlobImplBase::GetMozFullPathInternal(aFilename, aRv);
+ return;
+ }
+
+ BlobImpl* blobImpl = mBlobImpls.ElementAt(0).get();
+ if (!blobImpl) {
+ BlobImplBase::GetMozFullPathInternal(aFilename, aRv);
+ return;
+ }
+
+ blobImpl->GetMozFullPathInternal(aFilename, aRv);
+}
+
+nsresult
+MultipartBlobImpl::SetMutable(bool aMutable)
+{
+ nsresult rv;
+
+ // This looks a little sketchy since BlobImpl objects are supposed to be
+ // threadsafe. However, we try to enforce that all BlobImpl objects must be
+ // set to immutable *before* being passed to another thread, so this should
+ // be safe.
+ if (!aMutable && !mImmutable && !mBlobImpls.IsEmpty()) {
+ for (uint32_t index = 0, count = mBlobImpls.Length();
+ index < count;
+ index++) {
+ rv = mBlobImpls[index]->SetMutable(aMutable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ rv = BlobImplBase::SetMutable(aMutable);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT_IF(!aMutable, mImmutable);
+
+ return NS_OK;
+}
+
+void
+MultipartBlobImpl::InitializeChromeFile(Blob& aBlob,
+ const ChromeFilePropertyBag& aBag,
+ ErrorResult& aRv)
+{
+ NS_ASSERTION(!mImmutable, "Something went wrong ...");
+
+ if (mImmutable) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ MOZ_ASSERT(nsContentUtils::ThreadsafeIsCallerChrome());
+
+ mName = aBag.mName;
+ mContentType = aBag.mType;
+ mIsFromNsIFile = true;
+
+ // XXXkhuey this is terrible
+ if (mContentType.IsEmpty()) {
+ aBlob.GetType(mContentType);
+ }
+
+
+ BlobSet blobSet;
+ blobSet.AppendBlobImpl(aBlob.Impl());
+ mBlobImpls = blobSet.GetBlobImpls();
+
+ SetLengthAndModifiedDate(aRv);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
+}
+
+void
+MultipartBlobImpl::InitializeChromeFile(nsPIDOMWindowInner* aWindow,
+ nsIFile* aFile,
+ const ChromeFilePropertyBag& aBag,
+ bool aIsFromNsIFile,
+ ErrorResult& aRv)
+{
+ NS_ASSERTION(!mImmutable, "Something went wrong ...");
+ if (mImmutable) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ MOZ_ASSERT(nsContentUtils::IsCallerChrome());
+
+ mName = aBag.mName;
+ mContentType = aBag.mType;
+ mIsFromNsIFile = aIsFromNsIFile;
+
+ bool exists;
+ aRv = aFile->Exists(&exists);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!exists) {
+ aRv.Throw(NS_ERROR_FILE_NOT_FOUND);
+ return;
+ }
+
+ bool isDir;
+ aRv = aFile->IsDirectory(&isDir);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (isDir) {
+ aRv.Throw(NS_ERROR_FILE_IS_DIRECTORY);
+ return;
+ }
+
+ if (mName.IsEmpty()) {
+ aFile->GetLeafName(mName);
+ }
+
+ RefPtr<File> blob = File::CreateFromFile(aWindow, aFile, aBag.mTemporary);
+
+ // Pre-cache size.
+ blob->GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // Pre-cache modified date.
+ blob->GetLastModified(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // XXXkhuey this is terrible
+ if (mContentType.IsEmpty()) {
+ blob->GetType(mContentType);
+ }
+
+ BlobSet blobSet;
+ blobSet.AppendBlobImpl(static_cast<File*>(blob.get())->Impl());
+ mBlobImpls = blobSet.GetBlobImpls();
+
+ SetLengthAndModifiedDate(aRv);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
+}
+
+void
+MultipartBlobImpl::InitializeChromeFile(nsPIDOMWindowInner* aWindow,
+ const nsAString& aData,
+ const ChromeFilePropertyBag& aBag,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIFile> file;
+ aRv = NS_NewLocalFile(aData, false, getter_AddRefs(file));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ InitializeChromeFile(aWindow, file, aBag, false, aRv);
+}
+
+bool
+MultipartBlobImpl::MayBeClonedToOtherThreads() const
+{
+ for (uint32_t i = 0; i < mBlobImpls.Length(); ++i) {
+ if (!mBlobImpls[i]->MayBeClonedToOtherThreads()) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/dom/base/MultipartBlobImpl.h b/dom/base/MultipartBlobImpl.h
new file mode 100644
index 000000000..9fb88183b
--- /dev/null
+++ b/dom/base/MultipartBlobImpl.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/. */
+
+#ifndef mozilla_dom_MultipartBlobImpl_h
+#define mozilla_dom_MultipartBlobImpl_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Move.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/FileBinding.h"
+#include <algorithm>
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+class MultipartBlobImpl final : public BlobImplBase
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Create as a file
+ static already_AddRefed<MultipartBlobImpl>
+ Create(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
+ const nsAString& aName,
+ const nsAString& aContentType,
+ ErrorResult& aRv);
+
+ // Create as a blob
+ static already_AddRefed<MultipartBlobImpl>
+ Create(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
+ const nsAString& aContentType,
+ ErrorResult& aRv);
+
+ // Create as a file to be later initialized
+ explicit MultipartBlobImpl(const nsAString& aName)
+ : BlobImplBase(aName, EmptyString(), UINT64_MAX),
+ mIsFromNsIFile(false)
+ {
+ }
+
+ // Create as a blob to be later initialized
+ MultipartBlobImpl()
+ : BlobImplBase(EmptyString(), UINT64_MAX),
+ mIsFromNsIFile(false)
+ {
+ }
+
+ void InitializeBlob(ErrorResult& aRv);
+
+ void InitializeBlob(JSContext* aCx,
+ const Sequence<Blob::BlobPart>& aData,
+ const nsAString& aContentType,
+ bool aNativeEOL,
+ ErrorResult& aRv);
+
+ void InitializeChromeFile(Blob& aData,
+ const ChromeFilePropertyBag& aBag,
+ ErrorResult& aRv);
+
+ void InitializeChromeFile(nsPIDOMWindowInner* aWindow,
+ const nsAString& aData,
+ const ChromeFilePropertyBag& aBag,
+ ErrorResult& aRv);
+
+ void InitializeChromeFile(nsPIDOMWindowInner* aWindow,
+ nsIFile* aData,
+ const ChromeFilePropertyBag& aBag,
+ bool aIsFromNsIFile,
+ ErrorResult& aRv);
+
+ virtual already_AddRefed<BlobImpl>
+ CreateSlice(uint64_t aStart, uint64_t aLength,
+ const nsAString& aContentType,
+ ErrorResult& aRv) override;
+
+ virtual uint64_t GetSize(ErrorResult& aRv) override
+ {
+ return mLength;
+ }
+
+ virtual void GetInternalStream(nsIInputStream** aInputStream,
+ ErrorResult& aRv) override;
+
+ virtual const nsTArray<RefPtr<BlobImpl>>* GetSubBlobImpls() const override
+ {
+ return mBlobImpls.Length() ? &mBlobImpls : nullptr;
+ }
+
+ virtual void GetMozFullPathInternal(nsAString& aFullPath,
+ ErrorResult& aRv) const override;
+
+ virtual nsresult
+ SetMutable(bool aMutable) override;
+
+ void SetName(const nsAString& aName)
+ {
+ mName = aName;
+ }
+
+ virtual bool MayBeClonedToOtherThreads() const override;
+
+protected:
+ MultipartBlobImpl(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
+ const nsAString& aName,
+ const nsAString& aContentType)
+ : BlobImplBase(aName, aContentType, UINT64_MAX),
+ mBlobImpls(Move(aBlobImpls)),
+ mIsFromNsIFile(false)
+ {
+ }
+
+ MultipartBlobImpl(nsTArray<RefPtr<BlobImpl>>&& aBlobImpls,
+ const nsAString& aContentType)
+ : BlobImplBase(aContentType, UINT64_MAX),
+ mBlobImpls(Move(aBlobImpls)),
+ mIsFromNsIFile(false)
+ {
+ }
+
+ virtual ~MultipartBlobImpl() {}
+
+ void SetLengthAndModifiedDate(ErrorResult& aRv);
+
+ nsTArray<RefPtr<BlobImpl>> mBlobImpls;
+ bool mIsFromNsIFile;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_MultipartBlobImpl_h
diff --git a/dom/base/MutableBlobStorage.cpp b/dom/base/MutableBlobStorage.cpp
new file mode 100644
index 000000000..54ca4fe39
--- /dev/null
+++ b/dom/base/MutableBlobStorage.cpp
@@ -0,0 +1,643 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/MutableBlobStorage.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/TaskQueue.h"
+#include "nsAnonymousTemporaryFile.h"
+#include "nsNetCID.h"
+#include "WorkerPrivate.h"
+
+#define BLOB_MEMORY_TEMPORARY_FILE 1048576
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+// This class uses the callback to inform when the Blob is created or when the
+// error must be propagated.
+class BlobCreationDoneRunnable final : public Runnable
+{
+public:
+ BlobCreationDoneRunnable(MutableBlobStorage* aBlobStorage,
+ MutableBlobStorageCallback* aCallback,
+ Blob* aBlob,
+ nsresult aRv)
+ : mBlobStorage(aBlobStorage)
+ , mCallback(aCallback)
+ , mBlob(aBlob)
+ , mRv(aRv)
+ {
+ MOZ_ASSERT(aBlobStorage);
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT((NS_FAILED(aRv) && !aBlob) ||
+ (NS_SUCCEEDED(aRv) && aBlob));
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mCallback->BlobStoreCompleted(mBlobStorage, mBlob, mRv);
+ mCallback = nullptr;
+ mBlob = nullptr;
+ return NS_OK;
+ }
+
+private:
+ ~BlobCreationDoneRunnable()
+ {
+ // If something when wrong, we still have to release these objects in the
+ // correct thread.
+ NS_ReleaseOnMainThread(mCallback.forget());
+ NS_ReleaseOnMainThread(mBlob.forget());
+ }
+
+ RefPtr<MutableBlobStorage> mBlobStorage;
+ RefPtr<MutableBlobStorageCallback> mCallback;
+ RefPtr<Blob> mBlob;
+ nsresult mRv;
+};
+
+// This runnable goes back to the main-thread and informs the BlobStorage about
+// the temporary file.
+class FileCreatedRunnable final : public Runnable
+{
+public:
+ FileCreatedRunnable(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD)
+ : mBlobStorage(aBlobStorage)
+ , mFD(aFD)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aBlobStorage);
+ MOZ_ASSERT(aFD);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mBlobStorage->TemporaryFileCreated(mFD);
+ mFD = nullptr;
+ return NS_OK;
+ }
+
+private:
+ ~FileCreatedRunnable()
+ {
+ // If something when wrong, we still have to close the FileDescriptor.
+ if (mFD) {
+ PR_Close(mFD);
+ }
+ }
+
+ RefPtr<MutableBlobStorage> mBlobStorage;
+ PRFileDesc* mFD;
+};
+
+// This runnable creates the temporary file. When done, FileCreatedRunnable is
+// dispatched back to the main-thread.
+class CreateTemporaryFileRunnable final : public Runnable
+{
+public:
+ explicit CreateTemporaryFileRunnable(MutableBlobStorage* aBlobStorage)
+ : mBlobStorage(aBlobStorage)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBlobStorage);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ PRFileDesc* tempFD = nullptr;
+ nsresult rv = NS_OpenAnonymousTemporaryFile(&tempFD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // In sandboxed context we are not allowed to create temporary files, but
+ // this doesn't mean that BlobStorage should fail. We can continue to
+ // store data in memory. We don't change the storageType so that we don't
+ // try to create a temporary file again.
+ return NS_OK;
+ }
+
+ // The ownership of the tempFD is moved to the FileCreatedRunnable.
+ return NS_DispatchToMainThread(new FileCreatedRunnable(mBlobStorage, tempFD));
+ }
+
+private:
+ RefPtr<MutableBlobStorage> mBlobStorage;
+};
+
+// Simple runnable to propagate the error to the BlobStorage.
+class ErrorPropagationRunnable final : public Runnable
+{
+public:
+ ErrorPropagationRunnable(MutableBlobStorage* aBlobStorage, nsresult aRv)
+ : mBlobStorage(aBlobStorage)
+ , mRv(aRv)
+ {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ mBlobStorage->ErrorPropagated(mRv);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<MutableBlobStorage> mBlobStorage;
+ nsresult mRv;
+};
+
+// This runnable moves a buffer to the IO thread and there, it writes it into
+// the temporary file.
+class WriteRunnable final : public Runnable
+{
+public:
+ static WriteRunnable*
+ CopyBuffer(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD,
+ const void* aData, uint32_t aLength)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBlobStorage);
+ MOZ_ASSERT(aFD);
+ MOZ_ASSERT(aData);
+
+ // We have to take a copy of this buffer.
+ void* data = malloc(aLength);
+ if (!data) {
+ return nullptr;
+ }
+
+ memcpy((char*)data, aData, aLength);
+ return new WriteRunnable(aBlobStorage, aFD, data, aLength);
+ }
+
+ static WriteRunnable*
+ AdoptBuffer(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD,
+ void* aData, uint32_t aLength)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBlobStorage);
+ MOZ_ASSERT(aFD);
+ MOZ_ASSERT(aData);
+
+ return new WriteRunnable(aBlobStorage, aFD, aData, aLength);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ int32_t written = PR_Write(mFD, mData, mLength);
+ if (NS_WARN_IF(written < 0 || uint32_t(written) != mLength)) {
+ return NS_DispatchToMainThread(
+ new ErrorPropagationRunnable(mBlobStorage, NS_ERROR_FAILURE));
+ }
+
+ return NS_OK;
+ }
+
+private:
+ WriteRunnable(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD,
+ void* aData, uint32_t aLength)
+ : mBlobStorage(aBlobStorage)
+ , mFD(aFD)
+ , mData(aData)
+ , mLength(aLength)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mBlobStorage);
+ MOZ_ASSERT(aFD);
+ MOZ_ASSERT(aData);
+ }
+
+ ~WriteRunnable()
+ {
+ free(mData);
+ }
+
+ RefPtr<MutableBlobStorage> mBlobStorage;
+ PRFileDesc* mFD;
+ void* mData;
+ uint32_t mLength;
+};
+
+// This runnable closes the FD in case something goes wrong or the temporary
+// file is not needed anymore.
+class CloseFileRunnable final : public Runnable
+{
+public:
+ explicit CloseFileRunnable(PRFileDesc* aFD)
+ : mFD(aFD)
+ {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ PR_Close(mFD);
+ mFD = nullptr;
+ return NS_OK;
+ }
+
+private:
+ ~CloseFileRunnable()
+ {
+ if (mFD) {
+ PR_Close(mFD);
+ }
+ }
+
+ PRFileDesc* mFD;
+};
+
+// This runnable is dispatched to the main-thread from the IO thread and its
+// task is to create the blob and inform the callback.
+class CreateBlobRunnable final : public Runnable
+{
+public:
+ CreateBlobRunnable(MutableBlobStorage* aBlobStorage,
+ already_AddRefed<nsISupports> aParent,
+ const nsACString& aContentType,
+ already_AddRefed<MutableBlobStorageCallback> aCallback)
+ : mBlobStorage(aBlobStorage)
+ , mParent(aParent)
+ , mContentType(aContentType)
+ , mCallback(aCallback)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mBlobStorage->CreateBlobAndRespond(mParent.forget(), mContentType,
+ mCallback.forget());
+ return NS_OK;
+ }
+
+private:
+ ~CreateBlobRunnable()
+ {
+ // If something when wrong, we still have to release data in the correct
+ // thread.
+ NS_ReleaseOnMainThread(mParent.forget());
+ NS_ReleaseOnMainThread(mCallback.forget());
+ }
+
+ RefPtr<MutableBlobStorage> mBlobStorage;
+ nsCOMPtr<nsISupports> mParent;
+ nsCString mContentType;
+ RefPtr<MutableBlobStorageCallback> mCallback;
+};
+
+// This task is used to know when the writing is completed. From the IO thread
+// it dispatches a CreateBlobRunnable to the main-thread.
+class LastRunnable final : public Runnable
+{
+public:
+ LastRunnable(MutableBlobStorage* aBlobStorage,
+ nsISupports* aParent,
+ const nsACString& aContentType,
+ MutableBlobStorageCallback* aCallback)
+ : mBlobStorage(aBlobStorage)
+ , mParent(aParent)
+ , mContentType(aContentType)
+ , mCallback(aCallback)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mBlobStorage);
+ MOZ_ASSERT(aCallback);
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ RefPtr<Runnable> runnable =
+ new CreateBlobRunnable(mBlobStorage, mParent.forget(),
+ mContentType, mCallback.forget());
+ return NS_DispatchToMainThread(runnable);
+ }
+
+private:
+ ~LastRunnable()
+ {
+ // If something when wrong, we still have to release data in the correct
+ // thread.
+ NS_ReleaseOnMainThread(mParent.forget());
+ NS_ReleaseOnMainThread(mCallback.forget());
+ }
+
+ RefPtr<MutableBlobStorage> mBlobStorage;
+ nsCOMPtr<nsISupports> mParent;
+ nsCString mContentType;
+ RefPtr<MutableBlobStorageCallback> mCallback;
+};
+
+} // anonymous namespace
+
+MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType)
+ : mData(nullptr)
+ , mDataLen(0)
+ , mDataBufferLen(0)
+ , mStorageState(aType == eOnlyInMemory ? eKeepInMemory : eInMemory)
+ , mFD(nullptr)
+ , mErrorResult(NS_OK)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+MutableBlobStorage::~MutableBlobStorage()
+{
+ free(mData);
+
+ if (mFD) {
+ RefPtr<Runnable> runnable = new CloseFileRunnable(mFD);
+ DispatchToIOThread(runnable.forget());
+ }
+
+ if (mTaskQueue) {
+ mTaskQueue->BeginShutdown();
+ }
+}
+
+uint64_t
+MutableBlobStorage::GetBlobWhenReady(nsISupports* aParent,
+ const nsACString& aContentType,
+ MutableBlobStorageCallback* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCallback);
+
+ // GetBlob can be called just once.
+ MOZ_ASSERT(mStorageState != eClosed);
+ StorageState previousState = mStorageState;
+ mStorageState = eClosed;
+
+ if (previousState == eInTemporaryFile) {
+ MOZ_ASSERT(mFD);
+
+ if (NS_FAILED(mErrorResult)) {
+ RefPtr<Runnable> runnable =
+ new BlobCreationDoneRunnable(this, aCallback, nullptr, mErrorResult);
+ NS_DispatchToMainThread(runnable.forget());
+ return 0;
+ }
+
+ // We want to wait until all the WriteRunnable are completed. The way we do
+ // this is to go to the I/O thread and then we come back: the runnables are
+ // executed in order and this LastRunnable will be... the last one.
+ RefPtr<Runnable> runnable =
+ new LastRunnable(this, aParent, aContentType, aCallback);
+ DispatchToIOThread(runnable.forget());
+ return mDataLen;
+ }
+
+ // If we are waiting for the temporary file, it's better to wait...
+ if (previousState == eWaitingForTemporaryFile) {
+ mPendingParent = aParent;
+ mPendingContentType = aContentType;
+ mPendingCallback = aCallback;
+ return mDataLen;
+ }
+
+ RefPtr<BlobImpl> blobImpl;
+
+ if (mData) {
+ blobImpl = new BlobImplMemory(mData, mDataLen,
+ NS_ConvertUTF8toUTF16(aContentType));
+
+ mData = nullptr; // The BlobImplMemory takes ownership of the buffer
+ mDataLen = 0;
+ mDataBufferLen = 0;
+ } else {
+ blobImpl = new EmptyBlobImpl(NS_ConvertUTF8toUTF16(aContentType));
+ }
+
+ RefPtr<Blob> blob = Blob::Create(aParent, blobImpl);
+ RefPtr<BlobCreationDoneRunnable> runnable =
+ new BlobCreationDoneRunnable(this, aCallback, blob, NS_OK);
+
+ nsresult error = NS_DispatchToMainThread(runnable);
+ if (NS_WARN_IF(NS_FAILED(error))) {
+ return 0;
+ }
+
+ return mDataLen;
+}
+
+nsresult
+MutableBlobStorage::Append(const void* aData, uint32_t aLength)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mStorageState != eClosed);
+ NS_ENSURE_ARG_POINTER(aData);
+
+ if (!aLength) {
+ return NS_OK;
+ }
+
+ // If eInMemory is the current Storage state, we could maybe migrate to
+ // a temporary file.
+ if (mStorageState == eInMemory && ShouldBeTemporaryStorage(aLength)) {
+ nsresult rv = MaybeCreateTemporaryFile();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // If we are already in the temporaryFile mode, we have to dispatch a
+ // runnable.
+ if (mStorageState == eInTemporaryFile) {
+ MOZ_ASSERT(mFD);
+
+ RefPtr<WriteRunnable> runnable =
+ WriteRunnable::CopyBuffer(this, mFD, aData, aLength);
+ if (NS_WARN_IF(!runnable)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ DispatchToIOThread(runnable.forget());
+
+ mDataLen += aLength;
+ return NS_OK;
+ }
+
+ // By default, we store in memory.
+
+ uint64_t offset = mDataLen;
+
+ if (!ExpandBufferSize(aLength)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy((char*)mData + offset, aData, aLength);
+ return NS_OK;
+}
+
+bool
+MutableBlobStorage::ExpandBufferSize(uint64_t aSize)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mStorageState < eInTemporaryFile);
+
+ if (mDataBufferLen >= mDataLen + aSize) {
+ mDataLen += aSize;
+ return true;
+ }
+
+ // Start at 1 or we'll loop forever.
+ CheckedUint32 bufferLen =
+ std::max<uint32_t>(static_cast<uint32_t>(mDataBufferLen), 1);
+ while (bufferLen.isValid() && bufferLen.value() < mDataLen + aSize) {
+ bufferLen *= 2;
+ }
+
+ if (!bufferLen.isValid()) {
+ return false;
+ }
+
+ void* data = realloc(mData, bufferLen.value());
+ if (!data) {
+ return false;
+ }
+
+ mData = data;
+ mDataBufferLen = bufferLen.value();
+ mDataLen += aSize;
+ return true;
+}
+
+bool
+MutableBlobStorage::ShouldBeTemporaryStorage(uint64_t aSize) const
+{
+ MOZ_ASSERT(mStorageState == eInMemory);
+
+ CheckedUint32 bufferSize = mDataLen;
+ bufferSize += aSize;
+
+ if (!bufferSize.isValid()) {
+ return false;
+ }
+
+ return bufferSize.value() >= Preferences::GetUint("dom.blob.memoryToTemporaryFile",
+ BLOB_MEMORY_TEMPORARY_FILE);
+}
+
+nsresult
+MutableBlobStorage::MaybeCreateTemporaryFile()
+{
+ RefPtr<Runnable> runnable = new CreateTemporaryFileRunnable(this);
+ DispatchToIOThread(runnable.forget());
+
+ mStorageState = eWaitingForTemporaryFile;
+ return NS_OK;
+}
+
+void
+MutableBlobStorage::TemporaryFileCreated(PRFileDesc* aFD)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mStorageState == eWaitingForTemporaryFile ||
+ mStorageState == eClosed);
+ MOZ_ASSERT_IF(mPendingCallback, mStorageState == eClosed);
+
+ // If the object has been already closed and we don't need to execute a
+ // callback, we need just to close the file descriptor in the correct thread.
+ if (mStorageState == eClosed && !mPendingCallback) {
+ RefPtr<Runnable> runnable = new CloseFileRunnable(aFD);
+ DispatchToIOThread(runnable.forget());
+ return;
+ }
+
+ // If we still receiving data, we can proceed in temporary-file mode.
+ if (mStorageState == eWaitingForTemporaryFile) {
+ mStorageState = eInTemporaryFile;
+ }
+
+ mFD = aFD;
+
+ // This runnable takes the ownership of mData and it will write this buffer
+ // into the temporary file.
+ RefPtr<WriteRunnable> runnable =
+ WriteRunnable::AdoptBuffer(this, mFD, mData, mDataLen);
+ MOZ_ASSERT(runnable);
+
+ mData = nullptr;
+
+ DispatchToIOThread(runnable.forget());
+
+ // If we are closed, it means that GetBlobWhenReady() has been called when we
+ // were already waiting for a temporary file-descriptor. Finally we are here,
+ // AdoptBuffer runnable is going to write the current buffer into this file.
+ // After that, there is nothing else to write, and we dispatch LastRunnable
+ // which ends up calling mPendingCallback via CreateBlobRunnable.
+ if (mStorageState == eClosed) {
+ MOZ_ASSERT(mPendingCallback);
+
+ RefPtr<Runnable> runnable =
+ new LastRunnable(this, mPendingParent, mPendingContentType,
+ mPendingCallback);
+ DispatchToIOThread(runnable.forget());
+
+ mPendingParent = nullptr;
+ mPendingCallback = nullptr;
+ }
+}
+
+void
+MutableBlobStorage::CreateBlobAndRespond(already_AddRefed<nsISupports> aParent,
+ const nsACString& aContentType,
+ already_AddRefed<MutableBlobStorageCallback> aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mStorageState == eClosed);
+ MOZ_ASSERT(mFD);
+
+ nsCOMPtr<nsISupports> parent(aParent);
+ RefPtr<MutableBlobStorageCallback> callback(aCallback);
+
+ RefPtr<Blob> blob =
+ File::CreateTemporaryBlob(parent, mFD, 0, mDataLen,
+ NS_ConvertUTF8toUTF16(aContentType));
+ callback->BlobStoreCompleted(this, blob, NS_OK);
+
+ // ownership of this FD is moved to the BlobImpl.
+ mFD = nullptr;
+}
+
+void
+MutableBlobStorage::ErrorPropagated(nsresult aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mErrorResult = aRv;
+}
+
+void
+MutableBlobStorage::DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable)
+{
+ if (!mTaskQueue) {
+ nsCOMPtr<nsIEventTarget> target
+ = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+
+ mTaskQueue = new TaskQueue(target.forget());
+ }
+
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ mTaskQueue->Dispatch(runnable.forget());
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/base/MutableBlobStorage.h b/dom/base/MutableBlobStorage.h
new file mode 100644
index 000000000..fad391b16
--- /dev/null
+++ b/dom/base/MutableBlobStorage.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_dom_MutableBlobStorage_h
+#define mozilla_dom_MutableBlobStorage_h
+
+#include "mozilla/RefPtr.h"
+#include "prio.h"
+
+namespace mozilla {
+
+class TaskQueue;
+
+namespace dom {
+
+class Blob;
+class BlobImpl;
+class MutableBlobStorage;
+
+class MutableBlobStorageCallback
+{
+public:
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
+
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
+
+ virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
+ Blob* aBlob,
+ nsresult aRv) = 0;
+};
+
+// This class is main-thread only.
+class MutableBlobStorage final
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MutableBlobStorage);
+
+ enum MutableBlobStorageType
+ {
+ eOnlyInMemory,
+ eCouldBeInTemporaryFile,
+ };
+
+ explicit MutableBlobStorage(MutableBlobStorageType aType);
+
+ nsresult Append(const void* aData, uint32_t aLength);
+
+ // This method can be called just once.
+ // The callback will be called when the Blob is ready.
+ // The return value is the total size of the blob, when created.
+ uint64_t GetBlobWhenReady(nsISupports* aParent,
+ const nsACString& aContentType,
+ MutableBlobStorageCallback* aCallback);
+
+ void TemporaryFileCreated(PRFileDesc* aFD);
+
+ void CreateBlobAndRespond(already_AddRefed<nsISupports> aParent,
+ const nsACString& aContentType,
+ already_AddRefed<MutableBlobStorageCallback> aCallback);
+
+ void ErrorPropagated(nsresult aRv);
+
+private:
+ ~MutableBlobStorage();
+
+ bool ExpandBufferSize(uint64_t aSize);
+
+ bool ShouldBeTemporaryStorage(uint64_t aSize) const;
+
+ nsresult MaybeCreateTemporaryFile();
+
+ void DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable);
+
+ // All these variables are touched on the main thread only.
+
+ void* mData;
+ uint64_t mDataLen;
+ uint64_t mDataBufferLen;
+
+ enum StorageState {
+ eKeepInMemory,
+ eInMemory,
+ eWaitingForTemporaryFile,
+ eInTemporaryFile,
+ eClosed
+ };
+
+ StorageState mStorageState;
+
+ PRFileDesc* mFD;
+
+ nsresult mErrorResult;
+
+ RefPtr<TaskQueue> mTaskQueue;
+
+ nsCOMPtr<nsISupports> mPendingParent;
+ nsCString mPendingContentType;
+ RefPtr<MutableBlobStorageCallback> mPendingCallback;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MutableBlobStorage_h
diff --git a/dom/base/MutableBlobStreamListener.cpp b/dom/base/MutableBlobStreamListener.cpp
new file mode 100644
index 000000000..6769ad3e6
--- /dev/null
+++ b/dom/base/MutableBlobStreamListener.cpp
@@ -0,0 +1,102 @@
+/* -*- 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 "MutableBlobStreamListener.h"
+#include "MutableBlobStorage.h"
+
+namespace mozilla {
+namespace dom {
+
+MutableBlobStreamListener::MutableBlobStreamListener(MutableBlobStorage::MutableBlobStorageType aStorageType,
+ nsISupports* aParent,
+ const nsACString& aContentType,
+ MutableBlobStorageCallback* aCallback)
+ : mCallback(aCallback)
+ , mParent(aParent)
+ , mStorageType(aStorageType)
+ , mContentType(aContentType)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCallback);
+}
+
+MutableBlobStreamListener::~MutableBlobStreamListener()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+NS_IMPL_ISUPPORTS(MutableBlobStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+MutableBlobStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mStorage);
+
+ mStorage = new MutableBlobStorage(mStorageType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MutableBlobStreamListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mStorage);
+
+ // Resetting mStorage to nullptr.
+ RefPtr<MutableBlobStorage> storage;
+ storage.swap(mStorage);
+
+ // Let's propagate the error simulating a failure of the storage.
+ if (NS_FAILED(aStatus)) {
+ mCallback->BlobStoreCompleted(storage, nullptr, aStatus);
+ return NS_OK;
+ }
+
+ storage->GetBlobWhenReady(mParent, mContentType, mCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MutableBlobStreamListener::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mStorage);
+
+ uint32_t countRead;
+ return aStream->ReadSegments(WriteSegmentFun, this, aCount, &countRead);
+}
+
+nsresult
+MutableBlobStreamListener::WriteSegmentFun(nsIInputStream* aWriterStream,
+ void* aClosure,
+ const char* aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MutableBlobStreamListener* self = static_cast<MutableBlobStreamListener*>(aClosure);
+ MOZ_ASSERT(self->mStorage);
+
+ nsresult rv = self->mStorage->Append(aFromSegment, aCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/dom/base/MutableBlobStreamListener.h b/dom/base/MutableBlobStreamListener.h
new file mode 100644
index 000000000..1dc044c61
--- /dev/null
+++ b/dom/base/MutableBlobStreamListener.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 mozilla_dom_MutableBlobStreamListener_h
+#define mozilla_dom_MutableBlobStreamListener_h
+
+#include "nsIStreamListener.h"
+#include "mozilla/dom/MutableBlobStorage.h"
+
+namespace mozilla {
+namespace dom {
+
+// This class is main-thread only.
+class MutableBlobStreamListener final : public nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ MutableBlobStreamListener(MutableBlobStorage::MutableBlobStorageType aType,
+ nsISupports* aParent,
+ const nsACString& aContentType,
+ MutableBlobStorageCallback* aCallback);
+
+private:
+ ~MutableBlobStreamListener();
+
+ static nsresult
+ WriteSegmentFun(nsIInputStream* aWriter, void* aClosure,
+ const char* aFromSegment, uint32_t aOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+ RefPtr<MutableBlobStorage> mStorage;
+ RefPtr<MutableBlobStorageCallback> mCallback;
+
+ nsCOMPtr<nsISupports> mParent;
+ MutableBlobStorage::MutableBlobStorageType mStorageType;
+ nsCString mContentType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MutableBlobStreamListener_h
diff --git a/dom/base/NameSpaceConstants.h b/dom/base/NameSpaceConstants.h
new file mode 100644
index 000000000..d6c3f079f
--- /dev/null
+++ b/dom/base/NameSpaceConstants.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_NameSpaceConstants_h__
+#define mozilla_dom_NameSpaceConstants_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_XBL 6
+#define kNameSpaceID_MathML 7
+#define kNameSpaceID_RDF 8
+#define kNameSpaceID_XUL 9
+#define kNameSpaceID_SVG 10
+#define kNameSpaceID_disabled_MathML 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 000000000..290af152b
--- /dev/null
+++ b/dom/base/Navigator.cpp
@@ -0,0 +1,2038 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/MemoryReporting.h"
+#include "mozilla/dom/DesktopNotification.h"
+#include "mozilla/dom/File.h"
+#include "nsGeolocation.h"
+#include "nsIClassOfService.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsContentPolicyUtils.h"
+#include "nsISupportsPriority.h"
+#include "nsICachingChannel.h"
+#include "nsIWebContentHandlerRegistrar.h"
+#include "nsICookiePermission.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "BatteryManager.h"
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/GamepadServiceTest.h"
+#endif
+#include "mozilla/dom/PowerManager.h"
+#include "mozilla/dom/WakeLock.h"
+#include "mozilla/dom/power/PowerManagerService.h"
+#include "mozilla/dom/FlyWebPublishedServer.h"
+#include "mozilla/dom/FlyWebService.h"
+#include "mozilla/dom/Permissions.h"
+#include "mozilla/dom/Presentation.h"
+#include "mozilla/dom/ServiceWorkerContainer.h"
+#include "mozilla/dom/StorageManager.h"
+#include "mozilla/dom/TCPSocket.h"
+#include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/workers/RuntimeService.h"
+#include "mozilla/Hal.h"
+#include "nsISiteSpecificUserAgent.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SSE.h"
+#include "mozilla/StaticPtr.h"
+#include "Connection.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "nsGlobalWindow.h"
+#include "nsIIdleObserver.h"
+#include "nsIPermissionManager.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIStringStream.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "TimeManager.h"
+#include "nsStreamUtils.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
+#include "WidgetUtils.h"
+#include "nsIPresentationService.h"
+
+#include "mozilla/dom/MediaDevices.h"
+#include "MediaManager.h"
+
+#ifdef MOZ_AUDIO_CHANNEL_MANAGER
+#include "AudioChannelManager.h"
+#endif
+
+#include "nsIDOMGlobalPropertyInitializer.h"
+#include "nsJSUtils.h"
+
+#include "nsScriptNameSpaceManager.h"
+
+#include "mozilla/dom/NavigatorBinding.h"
+#include "mozilla/dom/Promise.h"
+
+#include "nsIUploadChannel2.h"
+#include "mozilla/dom/FormData.h"
+#include "nsIDocShell.h"
+
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+
+#if defined(XP_LINUX)
+#include "mozilla/Hal.h"
+#endif
+#include "mozilla/dom/ContentChild.h"
+
+#include "mozilla/EMEUtils.h"
+#include "mozilla/DetailedPromise.h"
+
+namespace mozilla {
+namespace dom {
+
+static bool sVibratorEnabled = false;
+static uint32_t sMaxVibrateMS = 0;
+static uint32_t sMaxVibrateListLen = 0;
+static const char* kVibrationPermissionType = "vibration";
+
+static void
+AddPermission(nsIPrincipal* aPrincipal, const char* aType, uint32_t aPermission,
+ uint32_t aExpireType, int64_t aExpireTime)
+{
+ MOZ_ASSERT(aType);
+ MOZ_ASSERT(aPrincipal);
+
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ if (!permMgr) {
+ return;
+ }
+ permMgr->AddFromPrincipal(aPrincipal, aType, aPermission, aExpireType,
+ aExpireTime);
+}
+
+static uint32_t
+GetPermission(nsPIDOMWindowInner* aWindow, const char* aType)
+{
+ MOZ_ASSERT(aType);
+
+ uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
+
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ if (!permMgr) {
+ return permission;
+ }
+ permMgr->TestPermissionFromWindow(aWindow, aType, &permission);
+ return permission;
+}
+
+static uint32_t
+GetPermission(nsIPrincipal* aPrincipal, const char* aType)
+{
+ MOZ_ASSERT(aType);
+ MOZ_ASSERT(aPrincipal);
+
+ uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
+
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ if (!permMgr) {
+ return permission;
+ }
+ permMgr->TestPermissionFromPrincipal(aPrincipal, aType, &permission);
+ return permission;
+}
+
+/* static */
+void
+Navigator::Init()
+{
+ Preferences::AddBoolVarCache(&sVibratorEnabled,
+ "dom.vibrator.enabled", true);
+ Preferences::AddUintVarCache(&sMaxVibrateMS,
+ "dom.vibrator.max_vibrate_ms", 10000);
+ Preferences::AddUintVarCache(&sMaxVibrateListLen,
+ "dom.vibrator.max_vibrate_list_len", 128);
+}
+
+Navigator::Navigator(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+{
+ MOZ_ASSERT(aWindow->IsInnerWindow(), "Navigator must get an inner window!");
+}
+
+Navigator::~Navigator()
+{
+ Invalidate();
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Navigator)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMNavigator)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMNavigator)
+ NS_INTERFACE_MAP_ENTRY(nsIMozNavigatorNetwork)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Navigator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Navigator)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Navigator)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Navigator)
+ tmp->Invalidate();
+ 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(Navigator)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMimeTypes)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlugins)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissions)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotification)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPowerManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager)
+#ifdef MOZ_AUDIO_CHANNEL_MANAGER
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelManager)
+#endif
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimeManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation)
+#ifdef MOZ_GAMEPAD
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
+#endif
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRGetDisplaysPromises)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Navigator)
+
+void
+Navigator::Invalidate()
+{
+ // Don't clear mWindow here so we know we've got a non-null mWindow
+ // until we're unlinked.
+
+ mMimeTypes = nullptr;
+
+ if (mPlugins) {
+ mPlugins->Invalidate();
+ mPlugins = nullptr;
+ }
+
+ mPermissions = nullptr;
+
+ mStorageManager = nullptr;
+
+ // If there is a page transition, make sure delete the geolocation object.
+ if (mGeolocation) {
+ mGeolocation->Shutdown();
+ mGeolocation = nullptr;
+ }
+
+ if (mNotification) {
+ mNotification->Shutdown();
+ mNotification = nullptr;
+ }
+
+ if (mBatteryManager) {
+ mBatteryManager->Shutdown();
+ mBatteryManager = nullptr;
+ }
+
+ mBatteryPromise = nullptr;
+
+ if (mPowerManager) {
+ mPowerManager->Shutdown();
+ mPowerManager = nullptr;
+ }
+
+ if (mConnection) {
+ mConnection->Shutdown();
+ mConnection = nullptr;
+ }
+
+ mMediaDevices = nullptr;
+
+#ifdef MOZ_AUDIO_CHANNEL_MANAGER
+ if (mAudioChannelManager) {
+ mAudioChannelManager = nullptr;
+ }
+#endif
+
+ if (mTimeManager) {
+ mTimeManager = nullptr;
+ }
+
+ if (mPresentation) {
+ mPresentation = nullptr;
+ }
+
+ mServiceWorkerContainer = nullptr;
+
+ if (mMediaKeySystemAccessManager) {
+ mMediaKeySystemAccessManager->Shutdown();
+ mMediaKeySystemAccessManager = nullptr;
+ }
+
+#ifdef MOZ_GAMEPAD
+ if (mGamepadServiceTest) {
+ mGamepadServiceTest->Shutdown();
+ mGamepadServiceTest = nullptr;
+ }
+#endif
+
+ mVRGetDisplaysPromises.Clear();
+}
+
+//*****************************************************************************
+// Navigator::nsIDOMNavigator
+//*****************************************************************************
+
+NS_IMETHODIMP
+Navigator::GetUserAgent(nsAString& aUserAgent)
+{
+ nsCOMPtr<nsIURI> codebaseURI;
+ nsCOMPtr<nsPIDOMWindowInner> window;
+
+ if (mWindow) {
+ window = mWindow;
+ nsIDocShell* docshell = window->GetDocShell();
+ nsString customUserAgent;
+ if (docshell) {
+ docshell->GetCustomUserAgent(customUserAgent);
+
+ if (!customUserAgent.IsEmpty()) {
+ aUserAgent = customUserAgent;
+ return NS_OK;
+ }
+
+ nsIDocument* doc = mWindow->GetExtantDoc();
+ if (doc) {
+ doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
+ }
+ }
+ }
+
+ return GetUserAgent(window, codebaseURI, nsContentUtils::IsCallerChrome(),
+ aUserAgent);
+}
+
+NS_IMETHODIMP
+Navigator::GetAppCodeName(nsAString& aAppCodeName)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpProtocolHandler>
+ service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString appName;
+ rv = service->GetAppName(appName);
+ CopyASCIItoUTF16(appName, aAppCodeName);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+Navigator::GetAppVersion(nsAString& aAppVersion)
+{
+ return GetAppVersion(aAppVersion, /* aUsePrefOverriddenValue */ true);
+}
+
+NS_IMETHODIMP
+Navigator::GetAppName(nsAString& aAppName)
+{
+ AppName(aAppName, /* aUsePrefOverriddenValue */ true);
+ return NS_OK;
+}
+
+/**
+ * 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.
+ *
+ * An empty array will be returned if there is no valid languages.
+ */
+/* static */ void
+Navigator::GetAcceptLanguages(nsTArray<nsString>& aLanguages)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ aLanguages.Clear();
+
+ // E.g. "de-de, en-us,en".
+ const nsAdoptingString& acceptLang =
+ Preferences::GetLocalizedString("intl.accept_languages");
+
+ // Split values on commas.
+ nsCharSeparatedTokenizer langTokenizer(acceptLang, ',');
+ while (langTokenizer.hasMoreTokens()) {
+ nsDependentSubstring lang = langTokenizer.nextToken();
+
+ // 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) {
+ nsCharSeparatedTokenizer localeTokenizer(lang, '-');
+ int32_t pos = 0;
+ bool first = true;
+ while (localeTokenizer.hasMoreTokens()) {
+ const nsSubstring& code = localeTokenizer.nextToken();
+
+ 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);
+ }
+}
+
+/**
+ * Do not use UI language (chosen app locale) here but the first value set in
+ * the Accept Languages header, see ::GetAcceptLanguages().
+ *
+ * See RFC 2616, Section 15.1.4 "Privacy Issues Connected to Accept Headers" for
+ * the reasons why.
+ */
+NS_IMETHODIMP
+Navigator::GetLanguage(nsAString& aLanguage)
+{
+ nsTArray<nsString> languages;
+ GetLanguages(languages);
+ if (languages.Length() >= 1) {
+ aLanguage.Assign(languages[0]);
+ } else {
+ aLanguage.Truncate();
+ }
+
+ return NS_OK;
+}
+
+void
+Navigator::GetLanguages(nsTArray<nsString>& aLanguages)
+{
+ GetAcceptLanguages(aLanguages);
+
+ // The returned value is cached by the binding code. The window listen 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.
+}
+
+NS_IMETHODIMP
+Navigator::GetPlatform(nsAString& aPlatform)
+{
+ return GetPlatform(aPlatform, /* aUsePrefOverriddenValue */ true);
+}
+
+NS_IMETHODIMP
+Navigator::GetOscpu(nsAString& aOSCPU)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ const nsAdoptingString& override =
+ Preferences::GetString("general.oscpu.override");
+
+ if (override) {
+ aOSCPU = override;
+ return NS_OK;
+ }
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpProtocolHandler>
+ service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString oscpu;
+ rv = service->GetOscpu(oscpu);
+ CopyASCIItoUTF16(oscpu, aOSCPU);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+Navigator::GetVendor(nsAString& aVendor)
+{
+ aVendor.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Navigator::GetVendorSub(nsAString& aVendorSub)
+{
+ aVendorSub.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Navigator::GetProduct(nsAString& aProduct)
+{
+ aProduct.AssignLiteral("Gecko");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Navigator::GetProductSub(nsAString& aProductSub)
+{
+ // Legacy build ID hardcoded for backward compatibility (bug 776376)
+ aProductSub.AssignLiteral("20100101");
+ return NS_OK;
+}
+
+nsMimeTypeArray*
+Navigator::GetMimeTypes(ErrorResult& aRv)
+{
+ if (!mMimeTypes) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mMimeTypes = new nsMimeTypeArray(mWindow);
+ }
+
+ return mMimeTypes;
+}
+
+nsPluginArray*
+Navigator::GetPlugins(ErrorResult& aRv)
+{
+ if (!mPlugins) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mPlugins = new nsPluginArray(mWindow);
+ mPlugins->Init();
+ }
+
+ return mPlugins;
+}
+
+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) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ MOZ_ASSERT(global);
+
+ mStorageManager = new StorageManager(global);
+ }
+
+ return mStorageManager;
+}
+
+// Values for the network.cookie.cookieBehavior pref are documented in
+// nsCookieService.cpp.
+#define COOKIE_BEHAVIOR_REJECT 2
+
+bool
+Navigator::CookieEnabled()
+{
+ bool cookieEnabled =
+ (Preferences::GetInt("network.cookie.cookieBehavior",
+ COOKIE_BEHAVIOR_REJECT) != COOKIE_BEHAVIOR_REJECT);
+
+ // 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 cookieEnabled;
+ }
+
+ nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ return cookieEnabled;
+ }
+
+ nsCOMPtr<nsIURI> codebaseURI;
+ doc->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
+
+ if (!codebaseURI) {
+ // Not a codebase, so technically can't set cookies, but let's
+ // just return the default value.
+ return cookieEnabled;
+ }
+
+ nsCOMPtr<nsICookiePermission> permMgr =
+ do_GetService(NS_COOKIEPERMISSION_CONTRACTID);
+ NS_ENSURE_TRUE(permMgr, cookieEnabled);
+
+ // Pass null for the channel, just like the cookie service does.
+ nsCookieAccess access;
+ nsresult rv = permMgr->CanAccess(codebaseURI, nullptr, &access);
+ NS_ENSURE_SUCCESS(rv, cookieEnabled);
+
+ if (access != nsICookiePermission::ACCESS_DEFAULT) {
+ cookieEnabled = access != nsICookiePermission::ACCESS_DENY;
+ }
+
+ return cookieEnabled;
+}
+
+bool
+Navigator::OnLine()
+{
+ return !NS_IsOffline();
+}
+
+NS_IMETHODIMP
+Navigator::GetBuildID(nsAString& aBuildID)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ const nsAdoptingString& override =
+ Preferences::GetString("general.buildID.override");
+
+ if (override) {
+ aBuildID = override;
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (!appInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsAutoCString buildID;
+ nsresult rv = appInfo->GetAppBuildID(buildID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ aBuildID.Truncate();
+ AppendASCIItoUTF16(buildID, aBuildID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Navigator::GetDoNotTrack(nsAString &aResult)
+{
+ bool doNotTrack = nsContentUtils::DoNotTrackEnabled();
+ if (!doNotTrack) {
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(mWindow);
+ doNotTrack = loadContext && loadContext->UseTrackingProtection();
+ }
+
+ if (doNotTrack) {
+ aResult.AssignLiteral("1");
+ } else {
+ aResult.AssignLiteral("unspecified");
+ }
+
+ return NS_OK;
+}
+
+bool
+Navigator::JavaEnabled(ErrorResult& aRv)
+{
+ Telemetry::AutoTimer<Telemetry::CHECK_JAVA_ENABLED> telemetryTimer;
+
+ // Return true if we have a handler for the java mime
+ nsAdoptingString javaMIME = Preferences::GetString("plugin.java.mime");
+ NS_ENSURE_TRUE(!javaMIME.IsEmpty(), false);
+
+ if (!mMimeTypes) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+ mMimeTypes = new nsMimeTypeArray(mWindow);
+ }
+
+ RefreshMIMEArray();
+
+ nsMimeType *mimeType = mMimeTypes->NamedItem(javaMIME);
+
+ return mimeType && mimeType->GetEnabledPlugin();
+}
+
+uint64_t
+Navigator::HardwareConcurrency()
+{
+ workers::RuntimeService* rts = workers::RuntimeService::GetOrCreateService();
+ if (!rts) {
+ return 1;
+ }
+
+ return rts->ClampedHardwareConcurrency();
+}
+
+bool
+Navigator::CpuHasSSE2()
+{
+ return mozilla::supports_sse2();
+}
+
+void
+Navigator::RefreshMIMEArray()
+{
+ if (mMimeTypes) {
+ mMimeTypes->Refresh();
+ }
+}
+
+namespace {
+
+class VibrateWindowListener : public nsIDOMEventListener
+{
+public:
+ VibrateWindowListener(nsPIDOMWindowInner* aWindow, nsIDocument* aDocument)
+ {
+ mWindow = do_GetWeakReference(aWindow);
+ mDocument = do_GetWeakReference(aDocument);
+
+ NS_NAMED_LITERAL_STRING(visibilitychange, "visibilitychange");
+ aDocument->AddSystemEventListener(visibilitychange,
+ this, /* listener */
+ true, /* use capture */
+ false /* wants untrusted */);
+ }
+
+ void RemoveListener();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+private:
+ virtual ~VibrateWindowListener()
+ {
+ }
+
+ nsWeakPtr mWindow;
+ nsWeakPtr mDocument;
+};
+
+NS_IMPL_ISUPPORTS(VibrateWindowListener, nsIDOMEventListener)
+
+StaticRefPtr<VibrateWindowListener> gVibrateWindowListener;
+
+static bool
+MayVibrate(nsIDocument* doc) {
+#if MOZ_WIDGET_GONK
+ if (XRE_IsParentProcess()) {
+ return true; // The system app can always vibrate
+ }
+#endif // MOZ_WIDGET_GONK
+
+ // Hidden documents cannot start or stop a vibration.
+ return (doc && !doc->Hidden());
+}
+
+NS_IMETHODIMP
+VibrateWindowListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIDocument> doc =
+ do_QueryInterface(aEvent->InternalDOMEvent()->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;
+ }
+ NS_NAMED_LITERAL_STRING(visibilitychange, "visibilitychange");
+ target->RemoveSystemEventListener(visibilitychange, this,
+ true /* use capture */);
+}
+
+} // namespace
+
+void
+Navigator::AddIdleObserver(MozIdleObserver& aIdleObserver, ErrorResult& aRv)
+{
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ CallbackObjectHolder<MozIdleObserver, nsIIdleObserver> holder(&aIdleObserver);
+ nsCOMPtr<nsIIdleObserver> obs = holder.ToXPCOMCallback();
+ if (NS_FAILED(mWindow->RegisterIdleObserver(obs))) {
+ NS_WARNING("Failed to add idle observer.");
+ }
+}
+
+void
+Navigator::RemoveIdleObserver(MozIdleObserver& aIdleObserver, ErrorResult& aRv)
+{
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ CallbackObjectHolder<MozIdleObserver, nsIIdleObserver> holder(&aIdleObserver);
+ nsCOMPtr<nsIIdleObserver> obs = holder.ToXPCOMCallback();
+ if (NS_FAILED(mWindow->UnregisterIdleObserver(obs))) {
+ NS_WARNING("Failed to remove idle observer.");
+ }
+}
+
+void
+Navigator::SetVibrationPermission(bool aPermitted, bool aPersistent)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsTArray<uint32_t> pattern;
+ pattern.SwapElements(mRequestedVibrationPattern);
+
+ if (!mWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> 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) {
+ AddPermission(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);
+}
+
+bool
+Navigator::Vibrate(const nsTArray<uint32_t>& aPattern)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mWindow) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
+
+ if (!MayVibrate(doc)) {
+ return false;
+ }
+
+ nsTArray<uint32_t> pattern(aPattern);
+
+ if (pattern.Length() > sMaxVibrateListLen) {
+ pattern.SetLength(sMaxVibrateListLen);
+ }
+
+ for (size_t i = 0; i < pattern.Length(); ++i) {
+ pattern[i] = std::min(sMaxVibrateMS, pattern[i]);
+ }
+
+ // The spec says we check sVibratorEnabled after we've done the sanity
+ // checking on the pattern.
+ if (!sVibratorEnabled) {
+ return true;
+ }
+
+ mRequestedVibrationPattern.SwapElements(pattern);
+ uint32_t permission = GetPermission(mWindow, kVibrationPermissionType);
+
+ 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;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (!obs || permission == nsIPermissionManager::DENY_ACTION) {
+ // Abort without observer service or on denied session permission.
+ SetVibrationPermission(false /* permitted */, false /* persistent */);
+ return true;
+ }
+
+ // Request user permission.
+ obs->NotifyObservers(ToSupports(this), "Vibration:Request", nullptr);
+
+ return true;
+}
+
+//*****************************************************************************
+// Pointer Events interface
+//*****************************************************************************
+
+uint32_t
+Navigator::MaxTouchPoints()
+{
+ nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(mWindow->GetOuterWindow());
+
+ NS_ENSURE_TRUE(widget, 0);
+ return widget->GetMaxTouchPoints();
+}
+
+//*****************************************************************************
+// Navigator::nsIDOMClientInformation
+//*****************************************************************************
+
+void
+Navigator::RegisterContentHandler(const nsAString& aMIMEType,
+ const nsAString& aURI,
+ const nsAString& aTitle,
+ ErrorResult& aRv)
+{
+ if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
+ return;
+ }
+
+ nsCOMPtr<nsIWebContentHandlerRegistrar> registrar =
+ do_GetService(NS_WEBCONTENTHANDLERREGISTRAR_CONTRACTID);
+ if (!registrar) {
+ return;
+ }
+
+ aRv = registrar->RegisterContentHandler(aMIMEType, aURI, aTitle,
+ mWindow->GetOuterWindow());
+}
+
+void
+Navigator::RegisterProtocolHandler(const nsAString& aProtocol,
+ const nsAString& aURI,
+ const nsAString& aTitle,
+ ErrorResult& aRv)
+{
+ if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
+ return;
+ }
+
+ nsCOMPtr<nsIWebContentHandlerRegistrar> registrar =
+ do_GetService(NS_WEBCONTENTHANDLERREGISTRAR_CONTRACTID);
+ if (!registrar) {
+ return;
+ }
+
+ aRv = registrar->RegisterProtocolHandler(aProtocol, aURI, aTitle,
+ 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() {}
+
+ 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,
+ nsISupports *aContext)
+{
+ // release the loadgroup first
+ mLoadGroup = nullptr;
+
+ aRequest->Cancel(NS_ERROR_NET_INTERRUPT);
+ return NS_BINDING_ABORTED;
+}
+
+NS_IMETHODIMP
+BeaconStreamListener::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BeaconStreamListener::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset,
+ uint32_t count)
+{
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+bool
+Navigator::SendBeacon(const nsAString& aUrl,
+ const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
+ ErrorResult& aRv)
+{
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return false;
+ }
+
+ nsCOMPtr<nsIDocument> 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>(aUrl);
+ return false;
+ }
+
+ // Spec disallows any schemes save for HTTP/HTTPs
+ bool isValidScheme;
+ if (!(NS_SUCCEEDED(uri->SchemeIs("http", &isValidScheme)) && isValidScheme) &&
+ !(NS_SUCCEEDED(uri->SchemeIs("https", &isValidScheme)) && isValidScheme)) {
+ aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>( NS_LITERAL_STRING("Beacon"), aUrl);
+ return false;
+ }
+
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
+ nsIChannel::LOAD_CLASSIFY_URI;
+
+ // No need to use CORS for sendBeacon unless it's a BLOB
+ nsSecurityFlags securityFlags = (!aData.IsNull() && aData.Value().IsBlob())
+ ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
+ : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ doc,
+ securityFlags,
+ nsIContentPolicy::TYPE_BEACON,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ loadFlags);
+
+ 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;
+ }
+ httpChannel->SetReferrer(documentURI);
+
+ nsCString mimeType;
+ if (!aData.IsNull()) {
+ nsCOMPtr<nsIInputStream> in;
+
+ if (aData.Value().IsString()) {
+ nsCString stringData = NS_ConvertUTF16toUTF8(aData.Value().GetAsString());
+ nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+ rv = strStream->SetData(stringData.BeginReading(), stringData.Length());
+ if (NS_FAILED(rv)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+ mimeType.AssignLiteral("text/plain;charset=UTF-8");
+ in = strStream;
+
+ } else if (aData.Value().IsArrayBufferView()) {
+
+ nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ const ArrayBufferView& view = aData.Value().GetAsArrayBufferView();
+ view.ComputeLengthAndData();
+ rv = strStream->SetData(reinterpret_cast<char*>(view.Data()),
+ view.Length());
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+ mimeType.AssignLiteral("application/octet-stream");
+ in = strStream;
+
+ } else if (aData.Value().IsBlob()) {
+ Blob& blob = aData.Value().GetAsBlob();
+ blob.GetInternalStream(getter_AddRefs(in), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return false;
+ }
+
+ nsAutoString type;
+ blob.GetType(type);
+ mimeType = NS_ConvertUTF16toUTF8(type);
+
+ } else if (aData.Value().IsFormData()) {
+ FormData& form = aData.Value().GetAsFormData();
+ uint64_t len;
+ nsAutoCString charset;
+ form.GetSendInfo(getter_AddRefs(in),
+ &len,
+ mimeType,
+ charset);
+ } else {
+ MOZ_ASSERT(false, "switch statements not in sync");
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel);
+ if (!uploadChannel) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+ uploadChannel->ExplicitSetUploadStream(in, mimeType, -1,
+ NS_LITERAL_CSTRING("POST"),
+ false);
+ } else {
+ httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
+ }
+
+ 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->AsyncOpen2(beaconListener);
+ // do not throw if security checks fail within asyncOpen2
+ 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,
+ ErrorResult& aRv)
+{
+ CallbackObjectHolder<NavigatorUserMediaSuccessCallback,
+ nsIDOMGetUserMediaSuccessCallback> holder1(&aOnSuccess);
+ nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onsuccess =
+ holder1.ToXPCOMCallback();
+
+ CallbackObjectHolder<NavigatorUserMediaErrorCallback,
+ nsIDOMGetUserMediaErrorCallback> holder2(&aOnError);
+ nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onerror = holder2.ToXPCOMCallback();
+
+ if (!mWindow || !mWindow->GetOuterWindow() ||
+ mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ MediaManager* manager = MediaManager::Get();
+ aRv = manager->GetUserMedia(mWindow, aConstraints, onsuccess, onerror);
+}
+
+void
+Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
+ MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
+ NavigatorUserMediaErrorCallback& aOnError,
+ uint64_t aInnerWindowID,
+ const nsAString& aCallID,
+ ErrorResult& aRv)
+{
+ CallbackObjectHolder<MozGetUserMediaDevicesSuccessCallback,
+ nsIGetUserMediaDevicesSuccessCallback> holder1(&aOnSuccess);
+ nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onsuccess =
+ holder1.ToXPCOMCallback();
+
+ CallbackObjectHolder<NavigatorUserMediaErrorCallback,
+ nsIDOMGetUserMediaErrorCallback> holder2(&aOnError);
+ nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onerror = holder2.ToXPCOMCallback();
+
+ if (!mWindow || !mWindow->GetOuterWindow() ||
+ mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ MediaManager* manager = MediaManager::Get();
+ aRv = manager->GetUserMediaDevices(mWindow, aConstraints, onsuccess, onerror,
+ aInnerWindowID, aCallID);
+}
+
+DesktopNotificationCenter*
+Navigator::GetMozNotification(ErrorResult& aRv)
+{
+ if (mNotification) {
+ return mNotification;
+ }
+
+ if (!mWindow || !mWindow->GetDocShell()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ mNotification = new DesktopNotificationCenter(mWindow);
+ return mNotification;
+}
+
+//*****************************************************************************
+// Navigator::nsINavigatorBattery
+//*****************************************************************************
+
+Promise*
+Navigator::GetBattery(ErrorResult& aRv)
+{
+ if (mBatteryPromise) {
+ return mBatteryPromise;
+ }
+
+ if (!mWindow || !mWindow->GetDocShell()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+ RefPtr<Promise> batteryPromise = Promise::Create(go, 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;
+}
+
+already_AddRefed<Promise>
+Navigator::PublishServer(const nsAString& aName,
+ const FlyWebPublishOptions& aOptions,
+ ErrorResult& aRv)
+{
+ RefPtr<FlyWebService> service = FlyWebService::GetOrCreate();
+ if (!service) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<FlyWebPublishPromise> mozPromise =
+ service->PublishServer(aName, aOptions, mWindow);
+ MOZ_ASSERT(mozPromise);
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ ErrorResult result;
+ RefPtr<Promise> domPromise = Promise::Create(global, result);
+ if (result.Failed()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ mozPromise->Then(AbstractThread::MainThread(),
+ __func__,
+ [domPromise] (FlyWebPublishedServer* aServer) {
+ domPromise->MaybeResolve(aServer);
+ },
+ [domPromise] (nsresult aStatus) {
+ domPromise->MaybeReject(aStatus);
+ });
+
+ return domPromise.forget();
+}
+
+PowerManager*
+Navigator::GetMozPower(ErrorResult& aRv)
+{
+ if (!mPowerManager) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mPowerManager = PowerManager::CreateInstance(mWindow);
+ if (!mPowerManager) {
+ // We failed to get the power manager service?
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ }
+ }
+
+ return mPowerManager;
+}
+
+already_AddRefed<WakeLock>
+Navigator::RequestWakeLock(const nsAString &aTopic, ErrorResult& aRv)
+{
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ RefPtr<power::PowerManagerService> pmService =
+ power::PowerManagerService::GetInstance();
+ // Maybe it went away for some reason... Or maybe we're just called
+ // from our XPCOM method.
+ if (!pmService) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return pmService->NewWakeLock(aTopic, mWindow, aRv);
+}
+
+already_AddRefed<LegacyMozTCPSocket>
+Navigator::MozTCPSocket()
+{
+ RefPtr<LegacyMozTCPSocket> socket = new LegacyMozTCPSocket(GetWindow());
+ return socket.forget();
+}
+
+#ifdef MOZ_GAMEPAD
+void
+Navigator::GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads,
+ ErrorResult& aRv)
+{
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ NS_ENSURE_TRUE_VOID(mWindow->GetDocShell());
+ nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
+ win->SetHasGamepadEventListener(true);
+ win->GetGamepads(aGamepads);
+}
+
+GamepadServiceTest*
+Navigator::RequestGamepadServiceTest()
+{
+ if (!mGamepadServiceTest) {
+ mGamepadServiceTest = GamepadServiceTest::CreateTestService(mWindow);
+ }
+ return mGamepadServiceTest;
+}
+#endif
+
+already_AddRefed<Promise>
+Navigator::GetVRDisplays(ErrorResult& aRv)
+{
+ if (!mWindow || !mWindow->GetDocShell()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
+ win->NotifyVREventListenerAdded();
+
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+ RefPtr<Promise> p = Promise::Create(go, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // We pass mWindow's id to RefreshVRDisplays, so NotifyVRDisplaysUpdated will
+ // be called asynchronously, resolving the promises in mVRGetDisplaysPromises.
+ if (!VRDisplay::RefreshVRDisplays(win->WindowID())) {
+ p->MaybeReject(NS_ERROR_FAILURE);
+ return p.forget();
+ }
+
+ mVRGetDisplaysPromises.AppendElement(p);
+ return p.forget();
+}
+
+void
+Navigator::GetActiveVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays) const
+{
+ /**
+ * Get only the active VR displays.
+ * Callers do not wish to VRDisplay::RefreshVRDisplays, as the enumeration may
+ * activate hardware that is not yet intended to be used.
+ */
+ if (!mWindow || !mWindow->GetDocShell()) {
+ return;
+ }
+ nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
+ win->NotifyVREventListenerAdded();
+ 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
+ nsGlobalWindow* win = nsGlobalWindow::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()
+{
+ NavigatorBinding::ClearCachedActiveVRDisplaysValue(this);
+}
+
+//*****************************************************************************
+// Navigator::nsIMozNavigatorNetwork
+//*****************************************************************************
+
+NS_IMETHODIMP
+Navigator::GetProperties(nsINetworkProperties** aProperties)
+{
+ ErrorResult rv;
+ NS_IF_ADDREF(*aProperties = GetConnection(rv));
+ return NS_OK;
+}
+
+network::Connection*
+Navigator::GetConnection(ErrorResult& aRv)
+{
+ if (!mConnection) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mConnection = new network::Connection(mWindow);
+ }
+
+ return mConnection;
+}
+
+#ifdef MOZ_TIME_MANAGER
+time::TimeManager*
+Navigator::GetMozTime(ErrorResult& aRv)
+{
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ if (!mTimeManager) {
+ mTimeManager = new time::TimeManager(mWindow);
+ }
+
+ return mTimeManager;
+}
+#endif
+
+already_AddRefed<ServiceWorkerContainer>
+Navigator::ServiceWorker()
+{
+ MOZ_ASSERT(mWindow);
+
+ if (!mServiceWorkerContainer) {
+ mServiceWorkerContainer = new ServiceWorkerContainer(mWindow);
+ }
+
+ RefPtr<ServiceWorkerContainer> ref = mServiceWorkerContainer;
+ return ref.forget();
+}
+
+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::SetWindow(nsPIDOMWindowInner *aInnerWindow)
+{
+ NS_ASSERTION(aInnerWindow->IsInnerWindow(),
+ "Navigator must get an inner window!");
+ mWindow = aInnerWindow;
+}
+
+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());
+ }
+}
+
+bool
+Navigator::CheckPermission(const char* type)
+{
+ return CheckPermission(mWindow, type);
+}
+
+/* static */
+bool
+Navigator::CheckPermission(nsPIDOMWindowInner* aWindow, const char* aType)
+{
+ if (!aWindow) {
+ return false;
+ }
+
+ uint32_t permission = GetPermission(aWindow, aType);
+ return permission == nsIPermissionManager::ALLOW_ACTION;
+}
+
+#ifdef MOZ_AUDIO_CHANNEL_MANAGER
+system::AudioChannelManager*
+Navigator::GetMozAudioChannelManager(ErrorResult& aRv)
+{
+ if (!mAudioChannelManager) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mAudioChannelManager = new system::AudioChannelManager();
+ mAudioChannelManager->Init(mWindow);
+ }
+
+ return mAudioChannelManager;
+}
+#endif
+
+JSObject*
+Navigator::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NavigatorBinding::Wrap(cx, this, aGivenProto);
+}
+
+/* static */
+bool
+Navigator::HasWakeLockSupport(JSContext* /* unused*/, JSObject* /*unused */)
+{
+ nsCOMPtr<nsIPowerManagerService> pmService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ // No service means no wake lock support
+ return !!pmService;
+}
+
+/* static */
+bool
+Navigator::HasWifiManagerSupport(JSContext* /* unused */,
+ JSObject* aGlobal)
+{
+ // On XBL scope, the global object is NOT |window|. So we have
+ // to use nsContentUtils::GetObjectPrincipal to get the principal
+ // and test directly with permission manager.
+
+ nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal);
+ uint32_t permission = GetPermission(principal, "wifi-manage");
+
+ return permission == nsIPermissionManager::ALLOW_ACTION;
+}
+
+/* static */
+bool
+Navigator::HasUserMediaSupport(JSContext* /* unused */,
+ JSObject* /* unused */)
+{
+ // Make enabling peerconnection enable getUserMedia() as well
+ return Preferences::GetBool("media.navigator.enabled", false) ||
+ Preferences::GetBool("media.peerconnection.enabled", false);
+}
+
+/* static */
+bool
+Navigator::IsE10sEnabled(JSContext* aCx, JSObject* aGlobal)
+{
+ return XRE_IsContentProcess();
+}
+
+bool
+Navigator::MozE10sEnabled()
+{
+ // This will only be called if IsE10sEnabled() is true.
+ return true;
+}
+
+/* static */
+already_AddRefed<nsPIDOMWindowInner>
+Navigator::GetWindowFromGlobal(JSObject* aGlobal)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(aGlobal));
+ MOZ_ASSERT(!win || win->IsInnerWindow());
+ return win.forget();
+}
+
+nsresult
+Navigator::GetPlatform(nsAString& aPlatform, bool aUsePrefOverriddenValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aUsePrefOverriddenValue && !nsContentUtils::IsCallerChrome()) {
+ const nsAdoptingString& override =
+ mozilla::Preferences::GetString("general.platform.override");
+
+ if (override) {
+ aPlatform = override;
+ return NS_OK;
+ }
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIHttpProtocolHandler>
+ service(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Sorry for the #if platform ugliness, but Communicator is likewise
+ // hardcoded and we are seeking backward compatibility here (bug 47080).
+#if defined(_WIN64)
+ aPlatform.AssignLiteral("Win64");
+#elif defined(WIN32)
+ aPlatform.AssignLiteral("Win32");
+#elif defined(XP_MACOSX) && defined(__ppc__)
+ aPlatform.AssignLiteral("MacPPC");
+#elif defined(XP_MACOSX) && defined(__i386__)
+ aPlatform.AssignLiteral("MacIntel");
+#elif defined(XP_MACOSX) && defined(__x86_64__)
+ aPlatform.AssignLiteral("MacIntel");
+#else
+ // XXX Communicator uses compiled-in build-time string defines
+ // to indicate the platform it was compiled *for*, not what it is
+ // currently running *on* which is what this does.
+ nsAutoCString plat;
+ rv = service->GetOscpu(plat);
+ CopyASCIItoUTF16(plat, aPlatform);
+#endif
+
+ return rv;
+}
+
+/* static */ nsresult
+Navigator::GetAppVersion(nsAString& aAppVersion, bool aUsePrefOverriddenValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aUsePrefOverriddenValue && !nsContentUtils::IsCallerChrome()) {
+ const nsAdoptingString& override =
+ mozilla::Preferences::GetString("general.appversion.override");
+
+ if (override) {
+ 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, bool aUsePrefOverriddenValue)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aUsePrefOverriddenValue && !nsContentUtils::IsCallerChrome()) {
+ const nsAdoptingString& override =
+ mozilla::Preferences::GetString("general.appname.override");
+
+ if (override) {
+ aAppName = override;
+ return;
+ }
+ }
+
+ aAppName.AssignLiteral("Netscape");
+}
+
+void
+Navigator::ClearUserAgentCache()
+{
+ NavigatorBinding::ClearCachedUserAgentValue(this);
+}
+
+nsresult
+Navigator::GetUserAgent(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
+ bool aIsCallerChrome,
+ nsAString& aUserAgent)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aIsCallerChrome) {
+ const nsAdoptingString& override =
+ mozilla::Preferences::GetString("general.useragent.override");
+
+ if (override) {
+ aUserAgent = override;
+ 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 || !aURI) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aWindow->GetDocShell());
+
+ nsCOMPtr<nsISiteSpecificUserAgent> siteSpecificUA =
+ do_GetService("@mozilla.org/dom/site-specific-user-agent;1");
+ if (!siteSpecificUA) {
+ return NS_OK;
+ }
+
+ return siteSpecificUA->GetUserAgentForURIAndWindow(aURI, aWindow, aUserAgent);
+}
+
+static nsCString
+ToCString(const nsString& aString)
+{
+ nsCString str("'");
+ str.Append(NS_ConvertUTF16toUTF8(aString));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeysRequirement aValue)
+{
+ nsCString str("'");
+ str.Append(nsDependentCString(MediaKeysRequirementValues::strings[static_cast<uint32_t>(aValue)].value));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeySystemMediaCapability& aValue)
+{
+ nsCString str;
+ str.AppendLiteral("{contentType=");
+ str.Append(ToCString(aValue.mContentType));
+ str.AppendLiteral(", robustness=");
+ str.Append(ToCString(aValue.mRobustness));
+ str.AppendLiteral("}");
+ return str;
+}
+
+template<class Type>
+static nsCString
+ToCString(const Sequence<Type>& aSequence)
+{
+ nsCString str;
+ str.AppendLiteral("[");
+ for (size_t i = 0; i < aSequence.Length(); i++) {
+ if (i != 0) {
+ str.AppendLiteral(",");
+ }
+ str.Append(ToCString(aSequence[i]));
+ }
+ str.AppendLiteral("]");
+ return str;
+}
+
+template<class Type>
+static nsCString
+ToCString(const Optional<Sequence<Type>>& aOptional)
+{
+ nsCString str;
+ if (aOptional.WasPassed()) {
+ str.Append(ToCString(aOptional.Value()));
+ } else {
+ str.AppendLiteral("[]");
+ }
+ return str;
+}
+
+static nsCString
+ToCString(const MediaKeySystemConfiguration& aConfig)
+{
+ nsCString str;
+ str.AppendLiteral("{label=");
+ str.Append(ToCString(aConfig.mLabel));
+
+ str.AppendLiteral(", initDataTypes=");
+ str.Append(ToCString(aConfig.mInitDataTypes));
+
+ str.AppendLiteral(", audioCapabilities=");
+ str.Append(ToCString(aConfig.mAudioCapabilities));
+
+ str.AppendLiteral(", videoCapabilities=");
+ str.Append(ToCString(aConfig.mVideoCapabilities));
+
+ str.AppendLiteral(", distinctiveIdentifier=");
+ str.Append(ToCString(aConfig.mDistinctiveIdentifier));
+
+ str.AppendLiteral(", persistentState=");
+ str.Append(ToCString(aConfig.mPersistentState));
+
+ str.AppendLiteral(", sessionTypes=");
+ str.Append(ToCString(aConfig.mSessionTypes));
+
+ str.AppendLiteral("}");
+
+ return str;
+}
+
+static nsCString
+RequestKeySystemAccessLogString(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ nsCString str;
+ str.AppendPrintf("Navigator::RequestMediaKeySystemAccess(keySystem='%s' options=",
+ NS_ConvertUTF16toUTF8(aKeySystem).get());
+ str.Append(ToCString(aConfigs));
+ str.AppendLiteral(")");
+ return str;
+}
+
+already_AddRefed<Promise>
+Navigator::RequestMediaKeySystemAccess(const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ ErrorResult& aRv)
+{
+ EME_LOG("%s", RequestKeySystemAccessLogString(aKeySystem, aConfigs).get());
+
+ nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+ RefPtr<DetailedPromise> promise =
+ DetailedPromise::Create(go, aRv,
+ NS_LITERAL_CSTRING("navigator.requestMediaKeySystemAccess"),
+ Telemetry::VIDEO_EME_REQUEST_SUCCESS_LATENCY_MS,
+ Telemetry::VIDEO_EME_REQUEST_FAILURE_LATENCY_MS);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!mMediaKeySystemAccessManager) {
+ mMediaKeySystemAccessManager = new MediaKeySystemAccessManager(mWindow);
+ }
+
+ mMediaKeySystemAccessManager->Request(promise, aKeySystem, aConfigs);
+ return promise.forget();
+}
+
+Presentation*
+Navigator::GetPresentation(ErrorResult& aRv)
+{
+ if (!mPresentation) {
+ if (!mWindow) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ mPresentation = Presentation::Create(mWindow);
+ }
+
+ return mPresentation;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
new file mode 100644
index 000000000..4b4ae6759
--- /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/Nullable.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIDOMNavigator.h"
+#include "nsIMozNavigatorNetwork.h"
+#include "nsWrapperCache.h"
+#include "nsHashKeys.h"
+#include "nsInterfaceHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWeakPtr.h"
+#include "mozilla/dom/MediaKeySystemAccessManager.h"
+
+class nsPluginArray;
+class nsMimeTypeArray;
+class nsPIDOMWindowInner;
+class nsIDOMNavigatorSystemMessages;
+class nsIPrincipal;
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+class Geolocation;
+class systemMessageCallback;
+class MediaDevices;
+struct MediaStreamConstraints;
+class WakeLock;
+class ArrayBufferViewOrBlobOrStringOrFormData;
+class ServiceWorkerContainer;
+class DOMRequest;
+struct FlyWebPublishOptions;
+struct FlyWebFilter;
+} // namespace dom
+} // namespace mozilla
+
+//*****************************************************************************
+// Navigator: Script "navigator" object
+//*****************************************************************************
+
+namespace mozilla {
+namespace dom {
+
+class Permissions;
+
+namespace battery {
+class BatteryManager;
+} // namespace battery
+
+class Promise;
+
+class DesktopNotificationCenter;
+class MozIdleObserver;
+#ifdef MOZ_GAMEPAD
+class Gamepad;
+class GamepadServiceTest;
+#endif // MOZ_GAMEPAD
+class NavigatorUserMediaSuccessCallback;
+class NavigatorUserMediaErrorCallback;
+class MozGetUserMediaDevicesSuccessCallback;
+
+namespace network {
+class Connection;
+} // namespace network
+
+class PowerManager;
+class Presentation;
+class LegacyMozTCPSocket;
+class VRDisplay;
+class StorageManager;
+
+namespace time {
+class TimeManager;
+} // namespace time
+
+namespace system {
+#ifdef MOZ_AUDIO_CHANNEL_MANAGER
+class AudioChannelManager;
+#endif
+} // namespace system
+
+class Navigator final : public nsIDOMNavigator
+ , public nsIMozNavigatorNetwork
+ , public nsWrapperCache
+{
+public:
+ explicit Navigator(nsPIDOMWindowInner* aInnerWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Navigator,
+ nsIDOMNavigator)
+ NS_DECL_NSIDOMNAVIGATOR
+ NS_DECL_NSIMOZNAVIGATORNETWORK
+
+ static void Init();
+
+ void Invalidate();
+ nsPIDOMWindowInner *GetWindow() const
+ {
+ return mWindow;
+ }
+
+ void RefreshMIMEArray();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ /**
+ * For use during document.write where our inner window changes.
+ */
+ void SetWindow(nsPIDOMWindowInner *aInnerWindow);
+
+ /**
+ * Called when the inner window navigates to a new page.
+ */
+ void OnNavigation();
+
+ // The XPCOM GetProduct is OK
+ // The XPCOM GetLanguage is OK
+ void GetUserAgent(nsString& aUserAgent, ErrorResult& /* unused */)
+ {
+ GetUserAgent(aUserAgent);
+ }
+ bool OnLine();
+ void RegisterProtocolHandler(const nsAString& aScheme, const nsAString& aURL,
+ const nsAString& aTitle, ErrorResult& aRv);
+ void RegisterContentHandler(const nsAString& aMIMEType, const nsAString& aURL,
+ const nsAString& aTitle, ErrorResult& aRv);
+ nsMimeTypeArray* GetMimeTypes(ErrorResult& aRv);
+ nsPluginArray* GetPlugins(ErrorResult& aRv);
+ Permissions* GetPermissions(ErrorResult& aRv);
+ // The XPCOM GetDoNotTrack is ok
+ Geolocation* GetGeolocation(ErrorResult& aRv);
+ Promise* GetBattery(ErrorResult& aRv);
+
+ already_AddRefed<Promise> PublishServer(const nsAString& aName,
+ const FlyWebPublishOptions& aOptions,
+ ErrorResult& aRv);
+ static void AppName(nsAString& aAppName, bool aUsePrefOverriddenValue);
+
+ static nsresult GetPlatform(nsAString& aPlatform,
+ bool aUsePrefOverriddenValue);
+
+ static nsresult GetAppVersion(nsAString& aAppVersion,
+ bool aUsePrefOverriddenValue);
+
+ static nsresult GetUserAgent(nsPIDOMWindowInner* aWindow,
+ nsIURI* aURI,
+ bool aIsCallerChrome,
+ nsAString& aUserAgent);
+
+ // Clears the user agent cache by calling:
+ // NavigatorBinding::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();
+ void GetAppCodeName(nsString& aAppCodeName, ErrorResult& aRv)
+ {
+ aRv = GetAppCodeName(aAppCodeName);
+ }
+ void GetOscpu(nsString& aOscpu, ErrorResult& aRv)
+ {
+ aRv = GetOscpu(aOscpu);
+ }
+ // The XPCOM GetVendor is OK
+ // The XPCOM GetVendorSub is OK
+ // The XPCOM GetProductSub is OK
+ bool CookieEnabled();
+ void GetBuildID(nsString& aBuildID, ErrorResult& aRv)
+ {
+ aRv = GetBuildID(aBuildID);
+ }
+ PowerManager* GetMozPower(ErrorResult& aRv);
+ bool JavaEnabled(ErrorResult& aRv);
+ uint64_t HardwareConcurrency();
+ bool CpuHasSSE2();
+ bool TaintEnabled()
+ {
+ return false;
+ }
+ void AddIdleObserver(MozIdleObserver& aObserver, ErrorResult& aRv);
+ void RemoveIdleObserver(MozIdleObserver& aObserver, ErrorResult& aRv);
+ already_AddRefed<WakeLock> RequestWakeLock(const nsAString &aTopic,
+ ErrorResult& aRv);
+
+ DesktopNotificationCenter* GetMozNotification(ErrorResult& aRv);
+ already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
+ network::Connection* GetConnection(ErrorResult& aRv);
+ MediaDevices* GetMediaDevices(ErrorResult& aRv);
+
+#ifdef MOZ_GAMEPAD
+ void GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads, ErrorResult& aRv);
+ GamepadServiceTest* RequestGamepadServiceTest();
+#endif // MOZ_GAMEPAD
+ already_AddRefed<Promise> GetVRDisplays(ErrorResult& aRv);
+ void GetActiveVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays) const;
+#ifdef MOZ_TIME_MANAGER
+ time::TimeManager* GetMozTime(ErrorResult& aRv);
+#endif // MOZ_TIME_MANAGER
+#ifdef MOZ_AUDIO_CHANNEL_MANAGER
+ system::AudioChannelManager* GetMozAudioChannelManager(ErrorResult& aRv);
+#endif // MOZ_AUDIO_CHANNEL_MANAGER
+
+ Presentation* GetPresentation(ErrorResult& aRv);
+
+ bool SendBeacon(const nsAString& aUrl,
+ const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
+ ErrorResult& aRv);
+
+ void MozGetUserMedia(const MediaStreamConstraints& aConstraints,
+ NavigatorUserMediaSuccessCallback& aOnSuccess,
+ NavigatorUserMediaErrorCallback& aOnError,
+ ErrorResult& aRv);
+ void MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
+ MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
+ NavigatorUserMediaErrorCallback& aOnError,
+ uint64_t aInnerWindowID,
+ const nsAString& aCallID,
+ ErrorResult& aRv);
+
+ already_AddRefed<ServiceWorkerContainer> ServiceWorker();
+
+ void GetLanguages(nsTArray<nsString>& aLanguages);
+
+ bool MozE10sEnabled();
+
+ StorageManager* Storage();
+
+ static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
+
+ // WebIDL helper methods
+ static bool HasWakeLockSupport(JSContext* /* unused*/, JSObject* /*unused */);
+ static bool HasWifiManagerSupport(JSContext* /* unused */,
+ JSObject* aGlobal);
+ static bool HasUserMediaSupport(JSContext* /* unused */,
+ JSObject* /* unused */);
+
+ static bool IsE10sEnabled(JSContext* aCx, JSObject* aGlobal);
+
+ 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);
+private:
+ RefPtr<MediaKeySystemAccessManager> mMediaKeySystemAccessManager;
+
+public:
+ void NotifyVRDisplaysUpdated();
+ void NotifyActiveVRDisplaysChanged();
+
+private:
+ virtual ~Navigator();
+
+ bool CheckPermission(const char* type);
+ static bool CheckPermission(nsPIDOMWindowInner* aWindow, const char* aType);
+
+ RefPtr<nsMimeTypeArray> mMimeTypes;
+ RefPtr<nsPluginArray> mPlugins;
+ RefPtr<Permissions> mPermissions;
+ RefPtr<Geolocation> mGeolocation;
+ RefPtr<DesktopNotificationCenter> mNotification;
+ RefPtr<battery::BatteryManager> mBatteryManager;
+ RefPtr<Promise> mBatteryPromise;
+ RefPtr<PowerManager> mPowerManager;
+ RefPtr<network::Connection> mConnection;
+#ifdef MOZ_AUDIO_CHANNEL_MANAGER
+ RefPtr<system::AudioChannelManager> mAudioChannelManager;
+#endif
+ RefPtr<MediaDevices> mMediaDevices;
+ RefPtr<time::TimeManager> mTimeManager;
+ RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<Presentation> mPresentation;
+#ifdef MOZ_GAMEPAD
+ RefPtr<GamepadServiceTest> mGamepadServiceTest;
+#endif
+ nsTArray<RefPtr<Promise> > mVRGetDisplaysPromises;
+ nsTArray<uint32_t> mRequestedVibrationPattern;
+ RefPtr<StorageManager> mStorageManager;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Navigator_h
diff --git a/dom/base/NodeInfo.cpp b/dom/base/NodeInfo.cpp
new file mode 100644
index 000000000..d32a00fd3
--- /dev/null
+++ b/dom/base/NodeInfo.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/. */
+
+/*
+ * 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 "nsIAtom.h"
+#include "nsDOMString.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "mozilla/Sprintf.h"
+#include "nsIDocument.h"
+#include "nsGkAtoms.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsNameSpaceManager.h"
+
+using namespace mozilla;
+using mozilla::dom::NodeInfo;
+
+NodeInfo::~NodeInfo()
+{
+ mOwnerManager->RemoveNodeInfo(this);
+}
+
+NodeInfo::NodeInfo(nsIAtom *aName, nsIAtom *aPrefix, int32_t aNamespaceID,
+ uint16_t aNodeType, nsIAtom* aExtraName,
+ nsNodeInfoManager *aOwnerManager)
+{
+ CheckValidNodeInfo(aNodeType, aName, aNamespaceID, aExtraName);
+ MOZ_ASSERT(aOwnerManager, "Invalid aOwnerManager");
+
+ // Initialize mInner
+ mInner.mName = aName;
+ mInner.mPrefix = aPrefix;
+ mInner.mNamespaceID = aNamespaceID;
+ mInner.mNodeType = aNodeType;
+ mOwnerManager = aOwnerManager;
+ mInner.mExtraName = aExtraName;
+
+ mDocument = aOwnerManager->GetDocument();
+
+ // 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) +
+ NS_LITERAL_STRING(":") +
+ nsDependentAtomString(mInner.mName);
+ } else {
+ mInner.mName->ToString(mQualifiedName);
+ }
+
+ MOZ_ASSERT_IF(aNodeType != nsIDOMNode::ELEMENT_NODE &&
+ aNodeType != nsIDOMNode::ATTRIBUTE_NODE &&
+ aNodeType != UINT16_MAX,
+ aNamespaceID == kNameSpaceID_None && !aPrefix);
+
+ switch (aNodeType) {
+ case nsIDOMNode::ELEMENT_NODE:
+ case nsIDOMNode::ATTRIBUTE_NODE:
+ // Correct the case for HTML
+ if (aNodeType == nsIDOMNode::ELEMENT_NODE &&
+ aNamespaceID == kNameSpaceID_XHTML && GetDocument() &&
+ GetDocument()->IsHTMLDocument()) {
+ nsContentUtils::ASCIIToUpper(mQualifiedName, mNodeName);
+ } else {
+ mNodeName = mQualifiedName;
+ }
+ mInner.mName->ToString(mLocalName);
+ break;
+ case nsIDOMNode::TEXT_NODE:
+ case nsIDOMNode::CDATA_SECTION_NODE:
+ case nsIDOMNode::COMMENT_NODE:
+ case nsIDOMNode::DOCUMENT_NODE:
+ case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
+ mInner.mName->ToString(mNodeName);
+ SetDOMStringToNull(mLocalName);
+ break;
+ case nsIDOMNode::PROCESSING_INSTRUCTION_NODE:
+ case nsIDOMNode::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)
+
+static const char* kNodeInfoNSURIs[] = {
+ " ([none])",
+ " (xmlns)",
+ " (xml)",
+ " (xhtml)",
+ " (XLink)",
+ " (XSLT)",
+ " (XBL)",
+ " (MathML)",
+ " (RDF)",
+ " (XUL)"
+};
+
+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());
+ if (nsid < ArrayLength(kNodeInfoNSURIs)) {
+ SprintfLiteral(name, "NodeInfo%s %s", kNodeInfoNSURIs[nsid],
+ localName.get());
+ }
+ else {
+ SprintfLiteral(name, "NodeInfo %s", 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
+
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NodeInfo, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NodeInfo, Release)
+
+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 =
+ nsContentUtils::NameSpaceManager()->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 =
+ nsContentUtils::NameSpaceManager()->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 000000000..bdf0074e7
--- /dev/null
+++ b/dom/base/NodeInfo.h
@@ -0,0 +1,303 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsStringGlue.h"
+#include "mozilla/Attributes.h"
+#include "nsIAtom.h"
+
+class nsIDocument;
+class nsNodeInfoManager;
+
+namespace mozilla {
+namespace dom {
+
+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.
+ */
+ nsIAtom* 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.
+ */
+ nsIAtom* 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 nsIDOMNode
+ * for nsIDOMNode.nodeType
+ */
+ uint16_t NodeType() const
+ {
+ return mInner.mNodeType;
+ }
+
+ /*
+ * Get the extra name, used by PIs and DocTypes, for the node.
+ */
+ nsIAtom* 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(nsIAtom* aNameAtom) const
+ {
+ return mInner.mName == aNameAtom;
+ }
+
+ bool Equals(nsIAtom* aNameAtom, nsIAtom* aPrefixAtom) const
+ {
+ return (mInner.mName == aNameAtom) && (mInner.mPrefix == aPrefixAtom);
+ }
+
+ bool Equals(nsIAtom* aNameAtom, int32_t aNamespaceID) const
+ {
+ return ((mInner.mName == aNameAtom) &&
+ (mInner.mNamespaceID == aNamespaceID));
+ }
+
+ bool Equals(nsIAtom* aNameAtom, nsIAtom* 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(nsIAtom* aNameAtom) const;
+
+ bool QualifiedNameEquals(const nsAString& aQualifiedName) const
+ {
+ return mQualifiedName == aQualifiedName;
+ }
+
+ /*
+ * Retrieve a pointer to the document that owns this node info.
+ */
+ nsIDocument* 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(nsIAtom* aName, nsIAtom* aPrefix, int32_t aNamespaceID,
+ uint16_t aNodeType, nsIAtom* 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)
+ {
+ }
+ NodeInfoInner(nsIAtom *aName, nsIAtom *aPrefix, int32_t aNamespaceID,
+ uint16_t aNodeType, nsIAtom* aExtraName)
+ : mName(aName), mPrefix(aPrefix), mNamespaceID(aNamespaceID),
+ mNodeType(aNodeType), mNameString(nullptr), mExtraName(aExtraName)
+ {
+ }
+ NodeInfoInner(const nsAString& aTmpName, nsIAtom *aPrefix,
+ int32_t aNamespaceID, uint16_t aNodeType)
+ : mName(nullptr), mPrefix(aPrefix), mNamespaceID(aNamespaceID),
+ mNodeType(aNodeType), mNameString(&aTmpName), mExtraName(nullptr)
+ {
+ }
+
+ nsCOMPtr<nsIAtom> mName;
+ nsCOMPtr<nsIAtom> mPrefix;
+ int32_t mNamespaceID;
+ uint16_t mNodeType; // As defined by nsIDOMNode.nodeType
+ const nsAString* mNameString;
+ nsCOMPtr<nsIAtom> mExtraName; // Only used by PIs and DocTypes
+ };
+
+ // 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.
+ nsIDocument* 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 dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_NodeInfo_h___ */
diff --git a/dom/base/NodeInfoInlines.h b/dom/base/NodeInfoInlines.h
new file mode 100644
index 000000000..970b6e53d
--- /dev/null
+++ b/dom/base/NodeInfoInlines.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIAtom.h"
+#include "nsIDOMNode.h"
+#include "nsDOMString.h"
+#include "nsGkAtoms.h"
+
+namespace mozilla {
+namespace 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(nsIAtom* aNameAtom) const
+{
+ MOZ_ASSERT(aNameAtom, "Must have name atom");
+ if (!GetPrefixAtom()) {
+ return Equals(aNameAtom);
+ }
+
+ return aNameAtom->Equals(mQualifiedName);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+inline void
+CheckValidNodeInfo(uint16_t aNodeType, nsIAtom *aName, int32_t aNamespaceID,
+ nsIAtom* aExtraName)
+{
+ MOZ_ASSERT(aNodeType == nsIDOMNode::ELEMENT_NODE ||
+ aNodeType == nsIDOMNode::ATTRIBUTE_NODE ||
+ aNodeType == nsIDOMNode::TEXT_NODE ||
+ aNodeType == nsIDOMNode::CDATA_SECTION_NODE ||
+ aNodeType == nsIDOMNode::PROCESSING_INSTRUCTION_NODE ||
+ aNodeType == nsIDOMNode::COMMENT_NODE ||
+ aNodeType == nsIDOMNode::DOCUMENT_NODE ||
+ aNodeType == nsIDOMNode::DOCUMENT_TYPE_NODE ||
+ aNodeType == nsIDOMNode::DOCUMENT_FRAGMENT_NODE ||
+ aNodeType == UINT16_MAX,
+ "Invalid nodeType");
+ MOZ_ASSERT((aNodeType == nsIDOMNode::PROCESSING_INSTRUCTION_NODE ||
+ aNodeType == nsIDOMNode::DOCUMENT_TYPE_NODE) ==
+ !!aExtraName,
+ "Supply aExtraName for and only for PIs and doctypes");
+ MOZ_ASSERT(aNodeType == nsIDOMNode::ELEMENT_NODE ||
+ aNodeType == nsIDOMNode::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 == nsIDOMNode::TEXT_NODE) ==
+ (aName == nsGkAtoms::textTagName)) &&
+ ((aNodeType == nsIDOMNode::CDATA_SECTION_NODE) ==
+ (aName == nsGkAtoms::cdataTagName)) &&
+ ((aNodeType == nsIDOMNode::COMMENT_NODE) ==
+ (aName == nsGkAtoms::commentTagName)) &&
+ ((aNodeType == nsIDOMNode::DOCUMENT_NODE) ==
+ (aName == nsGkAtoms::documentNodeName)) &&
+ ((aNodeType == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) ==
+ (aName == nsGkAtoms::documentFragmentNodeName)) &&
+ ((aNodeType == nsIDOMNode::DOCUMENT_TYPE_NODE) ==
+ (aName == nsGkAtoms::documentTypeNodeName)) &&
+ ((aNodeType == nsIDOMNode::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 000000000..0973528ca
--- /dev/null
+++ b/dom/base/NodeIterator.cpp
@@ -0,0 +1,291 @@
+/* -*- 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/. */
+
+/*
+ * Implementation of DOM Traversal's nsIDOMNodeIterator
+ */
+
+#include "mozilla/dom/NodeIterator.h"
+
+#include "nsIDOMNode.h"
+#include "nsError.h"
+
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/NodeIteratorBinding.h"
+
+namespace mozilla {
+namespace 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 (!nsContentUtils::ContentIsDescendantOf(mNode, 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,
+ NodeFilterHolder aFilter) :
+ nsTraversal(aRoot, aWhatToShow, Move(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(nsIDOMNodeIterator)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMNodeIterator)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(NodeIterator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(NodeIterator)
+
+NS_IMETHODIMP NodeIterator::GetRoot(nsIDOMNode * *aRoot)
+{
+ nsCOMPtr<nsIDOMNode> root = Root()->AsDOMNode();
+ root.forget(aRoot);
+ return NS_OK;
+}
+
+NS_IMETHODIMP NodeIterator::GetWhatToShow(uint32_t *aWhatToShow)
+{
+ *aWhatToShow = WhatToShow();
+ return NS_OK;
+}
+
+NS_IMETHODIMP NodeIterator::GetFilter(nsIDOMNodeFilter **aFilter)
+{
+ NS_ENSURE_ARG_POINTER(aFilter);
+
+ *aFilter = mFilter.ToXPCOMCallback().take();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP NodeIterator::NextNode(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&NodeIterator::NextNode, _retval);
+}
+
+NS_IMETHODIMP NodeIterator::PreviousNode(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&NodeIterator::PreviousNode, _retval);
+}
+
+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 = mWorkingPointer.mNode;
+ int16_t filtered = TestNode(testNode, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ if (filtered == nsIDOMNodeFilter::FILTER_ACCEPT) {
+ mPointer = mWorkingPointer;
+ return testNode.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP NodeIterator::Detach(void)
+{
+ if (mRoot) {
+ mRoot->OwnerDoc()->WarnOnceAbout(nsIDocument::eNodeIteratorDetach);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP NodeIterator::GetReferenceNode(nsIDOMNode * *aRefNode)
+{
+ nsCOMPtr<nsIDOMNode> node(do_QueryInterface(GetReferenceNode()));
+ node.forget(aRefNode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP NodeIterator::GetPointerBeforeReferenceNode(bool *aBeforeNode)
+{
+ *aBeforeNode = PointerBeforeReferenceNode();
+ return NS_OK;
+}
+
+/*
+ * nsIMutationObserver interface
+ */
+
+void NodeIterator::ContentRemoved(nsIDocument *aDocument,
+ nsIContent *aContainer,
+ nsIContent *aChild,
+ int32_t aIndexInContainer,
+ nsIContent *aPreviousSibling)
+{
+ nsINode *container = NODE_FROM(aContainer, aDocument);
+
+ 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 NodeIteratorBinding::Wrap(cx, this, aGivenProto, aReflector);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/NodeIterator.h b/dom/base/NodeIterator.h
new file mode 100644
index 000000000..51210982b
--- /dev/null
+++ b/dom/base/NodeIterator.h
@@ -0,0 +1,122 @@
+/* -*- 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/. */
+
+/*
+ * Implementation of DOM Traversal's nsIDOMNodeIterator
+ */
+
+#ifndef mozilla_dom_NodeIterator_h
+#define mozilla_dom_NodeIterator_h
+
+#include "nsIDOMNodeIterator.h"
+#include "nsTraversal.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsStubMutationObserver.h"
+
+class nsINode;
+class nsIDOMNode;
+
+namespace mozilla {
+namespace dom {
+
+class NodeIterator final : public nsIDOMNodeIterator,
+ public nsTraversal,
+ public nsStubMutationObserver
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIDOMNODEITERATOR
+
+ NodeIterator(nsINode *aRoot,
+ uint32_t aWhatToShow,
+ NodeFilterHolder aFilter);
+
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(NodeIterator, nsIDOMNodeIterator)
+
+ // 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;
+ }
+ already_AddRefed<NodeFilter> GetFilter()
+ {
+ return mFilter.ToWebIDLCallback();
+ }
+ already_AddRefed<nsINode> NextNode(ErrorResult& aResult)
+ {
+ return NextOrPrevNode(&NodePointer::MoveToNext, aResult);
+ }
+ already_AddRefed<nsINode> PreviousNode(ErrorResult& aResult)
+ {
+ return NextOrPrevNode(&NodePointer::MoveToPrevious, aResult);
+ }
+ // The XPCOM Detach() is fine for our purposes
+
+ bool WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector);
+
+private:
+ virtual ~NodeIterator();
+
+ struct NodePointer {
+ NodePointer() : mNode(nullptr) {}
+ 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;
+ };
+
+ // Implementation for some of our XPCOM getters
+ typedef already_AddRefed<nsINode> (NodeIterator::*NodeGetter)(ErrorResult&);
+ inline nsresult ImplNodeGetter(NodeGetter aGetter, nsIDOMNode** aRetval)
+ {
+ mozilla::ErrorResult rv;
+ nsCOMPtr<nsINode> node = (this->*aGetter)(rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ *aRetval = node ? node.forget().take()->AsDOMNode() : nullptr;
+ return NS_OK;
+ }
+
+ // 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 dom
+
+} // namespace mozilla
+
+#endif // mozilla_dom_NodeIterator_h
diff --git a/dom/base/PartialSHistory.cpp b/dom/base/PartialSHistory.cpp
new file mode 100644
index 000000000..2da5ca536
--- /dev/null
+++ b/dom/base/PartialSHistory.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 "PartialSHistory.h"
+
+#include "nsIWebNavigation.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION(PartialSHistory, mOwnerFrameLoader)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PartialSHistory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PartialSHistory)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PartialSHistory)
+ NS_INTERFACE_MAP_ENTRY(nsIPartialSHistory)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPartialSHistory)
+ NS_INTERFACE_MAP_ENTRY(nsISHistoryListener)
+ NS_INTERFACE_MAP_ENTRY(nsIPartialSHistoryListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+PartialSHistory::PartialSHistory(nsIFrameLoader* aOwnerFrameLoader)
+ : mCount(0),
+ mGlobalIndexOffset(0),
+ mOwnerFrameLoader(aOwnerFrameLoader)
+{
+ MOZ_ASSERT(aOwnerFrameLoader);
+}
+
+already_AddRefed<nsISHistory>
+PartialSHistory::GetSessionHistory()
+{
+ if (!mOwnerFrameLoader) {
+ // Cycle collected?
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell;
+ mOwnerFrameLoader->GetDocShell(getter_AddRefs(docShell));
+ if (!docShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
+ nsCOMPtr<nsISHistory> shistory;
+ webNav->GetSessionHistory(getter_AddRefs(shistory));
+ return shistory.forget();
+}
+
+already_AddRefed<TabParent>
+PartialSHistory::GetTabParent()
+{
+ if (!mOwnerFrameLoader) {
+ // Cycle collected?
+ return nullptr;
+ }
+
+ nsCOMPtr<nsITabParent> tabParent;
+ mOwnerFrameLoader->GetTabParent(getter_AddRefs(tabParent));
+ return RefPtr<TabParent>(static_cast<TabParent*>(tabParent.get())).forget();
+}
+
+NS_IMETHODIMP
+PartialSHistory::GetCount(uint32_t* aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ // If we have direct reference to nsISHistory, simply pass through.
+ nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+ if (shistory) {
+ int32_t count;
+ nsresult rv = shistory->GetCount(&count);
+ if (NS_FAILED(rv) || count < 0) {
+ *aResult = 0;
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = count;
+ return NS_OK;
+ }
+
+ // Otherwise use the cached value.
+ *aResult = mCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::GetGlobalIndexOffset(uint32_t* aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ // If we have direct reference to nsISHistory, simply pass through.
+ nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+ if (shistory) {
+ int32_t offset;
+ nsresult rv = shistory->GetGlobalIndexOffset(&offset);
+ if (NS_FAILED(rv) || offset < 0) {
+ *aResult = 0;
+ return NS_ERROR_FAILURE;
+ }
+ *aResult = offset;
+ return NS_OK;
+ }
+
+ // Otherwise use the cached value.
+ *aResult = mGlobalIndexOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::GetOwnerFrameLoader(nsIFrameLoader** aResult)
+{
+ nsCOMPtr<nsIFrameLoader> loader(mOwnerFrameLoader);
+ loader.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnAttachGroupedSessionHistory(uint32_t aOffset)
+{
+ mGlobalIndexOffset = aOffset;
+
+ // If we have direct reference to nsISHistory, simply pass through.
+ nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+ if (shistory) {
+ // nsISHistory uses int32_t
+ if (aOffset > INT32_MAX) {
+ return NS_ERROR_FAILURE;
+ }
+ return shistory->OnAttachGroupedSessionHistory(aOffset);
+ }
+
+ // Otherwise notify through TabParent.
+ RefPtr<TabParent> tabParent(GetTabParent());
+ if (!tabParent) {
+ // We have neither shistory nor tabParent?
+ NS_WARNING("Unable to get shitory nor tabParent!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ Unused << tabParent->SendNotifyAttachGroupedSessionHistory(aOffset);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnSessionHistoryChange(uint32_t aCount)
+{
+ mCount = aCount;
+ return OnLengthChange(aCount);
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnActive(uint32_t aGlobalLength, uint32_t aTargetLocalIndex)
+{
+ // In-process case.
+ nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+ if (shistory) {
+ // nsISHistory uses int32_t
+ if (aGlobalLength > INT32_MAX || aTargetLocalIndex > INT32_MAX) {
+ return NS_ERROR_FAILURE;
+ }
+ return shistory->OnPartialSessionHistoryActive(aGlobalLength,
+ aTargetLocalIndex);
+ }
+
+ // Cross-process case.
+ RefPtr<TabParent> tabParent(GetTabParent());
+ if (!tabParent) {
+ // We have neither shistory nor tabParent?
+ NS_WARNING("Unable to get shitory nor tabParent!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ Unused << tabParent->SendNotifyPartialSessionHistoryActive(aGlobalLength,
+ aTargetLocalIndex);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnDeactive()
+{
+ // In-process case.
+ nsCOMPtr<nsISHistory> shistory(GetSessionHistory());
+ if (shistory) {
+ if (NS_FAILED(shistory->OnPartialSessionHistoryDeactive())) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ // Cross-process case.
+ RefPtr<TabParent> tabParent(GetTabParent());
+ if (!tabParent) {
+ // We have neither shistory nor tabParent?
+ NS_WARNING("Unable to get shitory nor tabParent!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ Unused << tabParent->SendNotifyPartialSessionHistoryDeactive();
+ return NS_OK;
+}
+
+/*******************************************************************************
+ * nsIPartialSHistoryListener
+ ******************************************************************************/
+
+NS_IMETHODIMP
+PartialSHistory::OnRequestCrossBrowserNavigation(uint32_t aIndex)
+{
+ if (!mOwnerFrameLoader) {
+ // Cycle collected?
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mOwnerFrameLoader->RequestGroupedHistoryNavigation(aIndex);
+}
+
+/*******************************************************************************
+ * nsISHistoryListener
+ ******************************************************************************/
+
+NS_IMETHODIMP
+PartialSHistory::OnLengthChange(int32_t aCount)
+{
+ if (!mOwnerFrameLoader) {
+ // Cycle collected?
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (aCount < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIGroupedSHistory> groupedHistory;
+ mOwnerFrameLoader->GetGroupedSessionHistory(getter_AddRefs(groupedHistory));
+ if (!groupedHistory) {
+ // Maybe we're not the active partial history, but in this case we shouldn't
+ // receive any update from session history object either.
+ return NS_ERROR_FAILURE;
+ }
+
+ groupedHistory->OnPartialSessionHistoryChange(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryNewEntry(nsIURI *aNewURI, int32_t aOldIndex)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryGoBack(nsIURI *aBackURI, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryGoForward(nsIURI *aForwardURI, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryReload(nsIURI *aReloadURI, uint32_t aReloadFlags, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryGotoIndex(int32_t aIndex, nsIURI *aGotoURI, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryPurge(int32_t aNumEntries, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartialSHistory::OnHistoryReplaceEntry(int32_t aIndex)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/PartialSHistory.h b/dom/base/PartialSHistory.h
new file mode 100644
index 000000000..5e8a435e6
--- /dev/null
+++ b/dom/base/PartialSHistory.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 PartialSHistory_h
+#define PartialSHistory_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsFrameLoader.h"
+#include "nsIGroupedSHistory.h"
+#include "nsIPartialSHistoryListener.h"
+#include "nsIPartialSHistory.h"
+#include "nsISHistory.h"
+#include "nsISHistoryListener.h"
+#include "TabParent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PartialSHistory final : public nsIPartialSHistory,
+ public nsISHistoryListener,
+ public nsIPartialSHistoryListener,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PartialSHistory, nsIPartialSHistory)
+ NS_DECL_NSIPARTIALSHISTORY
+ NS_DECL_NSIPARTIALSHISTORYLISTENER
+ NS_DECL_NSISHISTORYLISTENER
+
+ /**
+ * Note that PartialSHistory must be constructed after frameloader has
+ * created a valid docshell or tabparent.
+ */
+ explicit PartialSHistory(nsIFrameLoader* aOwnerFrameLoader);
+
+private:
+ ~PartialSHistory() {}
+ already_AddRefed<nsISHistory> GetSessionHistory();
+ already_AddRefed<TabParent> GetTabParent();
+
+ // The cache of number of entries in corresponding nsISHistory. It's only
+ // used for remote process case. If nsISHistory is in-process, mCount will not
+ // be used at all.
+ uint32_t mCount;
+
+ // The cache of globalIndexOffset in corresponding nsISHistory. It's only
+ // used for remote process case.
+ uint32_t mGlobalIndexOffset;
+
+ // The frameloader which owns this PartialSHistory.
+ nsCOMPtr<nsIFrameLoader> mOwnerFrameLoader;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* PartialSHistory_h */
diff --git a/dom/base/Pose.cpp b/dom/base/Pose.cpp
new file mode 100644
index 000000000..1eab4c173
--- /dev/null
+++ b/dom/base/Pose.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 "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/Pose.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Pose)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Pose)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->mPosition = nullptr;
+ tmp->mLinearVelocity = nullptr;
+ tmp->mLinearAcceleration = nullptr;
+ tmp->mOrientation = nullptr;
+ tmp->mAngularVelocity = nullptr;
+ tmp->mAngularAcceleration = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Pose)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Pose)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPosition)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLinearVelocity)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLinearAcceleration)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOrientation)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAngularVelocity)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAngularAcceleration)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Pose, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Pose, Release)
+
+
+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, JS::MutableHandle<JSObject*> aRetVal,
+ JS::Heap<JSObject*>& aObj, float* aVal, uint32_t sizeOfVal,
+ bool bCreate, ErrorResult& aRv)
+{
+ if (bCreate) {
+ aObj = Float32Array::Create(aJSContext, this,
+ sizeOfVal, aVal);
+ if (!aObj) {
+ aRv.NoteJSContextException(aJSContext);
+ return;
+ }
+ }
+
+ aRetVal.set(aObj);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/Pose.h b/dom/base/Pose.h
new file mode 100644
index 000000000..c7ef1b381
--- /dev/null
+++ b/dom/base/Pose.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsWrapperCache.h"
+
+namespace mozilla {
+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;
+
+protected:
+ virtual ~Pose();
+
+ void SetFloat32Array(JSContext* aJSContext, JS::MutableHandle<JSObject*> aRetVal,
+ JS::Heap<JSObject*>& aObj, float* aVal, uint32_t sizeOfVal,
+ bool bCreate, ErrorResult& aRv);
+
+ 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 000000000..c1c5cdad4
--- /dev/null
+++ b/dom/base/PostMessageEvent.cpp
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/BlobBinding.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileList.h"
+#include "mozilla/dom/FileListBinding.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/PMessagePort.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/UnionConversions.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsIPresShell.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsPresContext.h"
+#include "nsQueryObject.h"
+
+namespace mozilla {
+namespace dom {
+
+PostMessageEvent::PostMessageEvent(nsGlobalWindow* aSource,
+ const nsAString& aCallerOrigin,
+ nsGlobalWindow* aTargetWindow,
+ nsIPrincipal* aProvidedPrincipal,
+ nsIDocument* aSourceDocument,
+ bool aTrustedCaller)
+: StructuredCloneHolder(CloningSupported, TransferringSupported,
+ StructuredCloneScope::SameProcessSameThread),
+ mSource(aSource),
+ mCallerOrigin(aCallerOrigin),
+ mTargetWindow(aTargetWindow),
+ mProvidedPrincipal(aProvidedPrincipal),
+ mSourceDocument(aSourceDocument),
+ mTrustedCaller(aTrustedCaller)
+{
+ MOZ_COUNT_CTOR(PostMessageEvent);
+}
+
+PostMessageEvent::~PostMessageEvent()
+{
+ MOZ_COUNT_DTOR(PostMessageEvent);
+}
+
+NS_IMETHODIMP
+PostMessageEvent::Run()
+{
+ MOZ_ASSERT(mTargetWindow->IsOuterWindow(),
+ "should have been passed an outer window!");
+ MOZ_ASSERT(!mSource || mSource->IsOuterWindow(),
+ "should have been passed an outer window!");
+
+ // 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 is just used for the principal mismatch error message below.
+ // Use a stack variable so mSourceDocument is not held onto after this method
+ // finishes, regardless of the method outcome.
+ nsCOMPtr<nsIDocument> sourceDocument;
+ sourceDocument.swap(mSourceDocument);
+
+ // If we bailed before this point we're going to leak mMessage, but
+ // that's probably better than crashing.
+
+ RefPtr<nsGlobalWindow> targetWindow;
+ if (mTargetWindow->IsClosedOrClosing() ||
+ !(targetWindow = mTargetWindow->GetCurrentInnerWindowInternal()) ||
+ targetWindow->IsClosedOrClosing())
+ return NS_OK;
+
+ MOZ_ASSERT(targetWindow->IsInnerWindow(),
+ "we ordered an inner window!");
+ JSAutoCompartment ac(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 (!BasePrincipal::Cast(targetPrin)->EqualsIgnoringAddonId(mProvidedPrincipal)) {
+ nsAutoString providedOrigin, targetOrigin;
+ nsresult rv = nsContentUtils::GetUTFOrigin(targetPrin, targetOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsContentUtils::GetUTFOrigin(mProvidedPrincipal, providedOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_DIAGNOSTIC_ASSERT(providedOrigin != targetOrigin ||
+ (BasePrincipal::Cast(mProvidedPrincipal)->OriginAttributesRef() ==
+ BasePrincipal::Cast(targetPrin)->OriginAttributesRef()),
+ "Unexpected postMessage call to a window with mismatched "
+ "origin attributes");
+
+ const char16_t* params[] = { providedOrigin.get(), targetOrigin.get() };
+
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("DOM Window"), sourceDocument,
+ nsContentUtils::eDOM_PROPERTIES,
+ "TargetPrincipalDoesNotMatch",
+ params, ArrayLength(params));
+
+ return NS_OK;
+ }
+ }
+
+ ErrorResult rv;
+ JS::Rooted<JS::Value> messageData(cx);
+ nsCOMPtr<nsPIDOMWindowInner> window = targetWindow->AsInner();
+
+ Read(window, cx, &messageData, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ // Create the event
+ nsCOMPtr<mozilla::dom::EventTarget> eventTarget = do_QueryObject(targetWindow);
+ RefPtr<MessageEvent> event =
+ new MessageEvent(eventTarget, nullptr, nullptr);
+
+
+ Nullable<WindowProxyOrMessagePort> source;
+ source.SetValue().SetAsWindowProxy() = mSource ? mSource->AsOuter() : nullptr;
+
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!TakeTransferredPortsAsSequence(ports)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"),
+ false /*non-bubbling */, false /*cancelable */,
+ messageData, mCallerOrigin,
+ EmptyString(), source, ports);
+
+ // 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.
+
+ nsIPresShell *shell = targetWindow->GetExtantDoc()->GetShell();
+ RefPtr<nsPresContext> presContext;
+ if (shell)
+ presContext = shell->GetPresContext();
+
+ event->SetTrusted(mTrustedCaller);
+ WidgetEvent* internalEvent = event->WidgetEventPtr();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(window,
+ presContext,
+ internalEvent,
+ static_cast<dom::Event*>(event.get()),
+ &status);
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/PostMessageEvent.h b/dom/base/PostMessageEvent.h
new file mode 100644
index 000000000..6b78d38ca
--- /dev/null
+++ b/dom/base/PostMessageEvent.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_PostMessageEvent_h
+#define mozilla_dom_PostMessageEvent_h
+
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+class nsGlobalWindow;
+class nsIDocument;
+class nsIPrincipal;
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Class used to represent events generated by calls to Window.postMessage,
+ * which asynchronously creates and dispatches events.
+ */
+class PostMessageEvent final : public Runnable
+ , public StructuredCloneHolder
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ PostMessageEvent(nsGlobalWindow* aSource,
+ const nsAString& aCallerOrigin,
+ nsGlobalWindow* aTargetWindow,
+ nsIPrincipal* aProvidedPrincipal,
+ nsIDocument* aSourceDocument,
+ bool aTrustedCaller);
+
+private:
+ ~PostMessageEvent();
+
+ RefPtr<nsGlobalWindow> mSource;
+ nsString mCallerOrigin;
+ RefPtr<nsGlobalWindow> mTargetWindow;
+ nsCOMPtr<nsIPrincipal> mProvidedPrincipal;
+ nsCOMPtr<nsIDocument> mSourceDocument;
+ bool mTrustedCaller;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PostMessageEvent_h
diff --git a/dom/base/ProcessGlobal.cpp b/dom/base/ProcessGlobal.cpp
new file mode 100644
index 000000000..641f49f98
--- /dev/null
+++ b/dom/base/ProcessGlobal.cpp
@@ -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/. */
+
+#include "ProcessGlobal.h"
+
+#include "nsContentCID.h"
+#include "nsDOMClassInfoID.h"
+#include "mozilla/HoldDropJSObjects.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+ProcessGlobal::ProcessGlobal(nsFrameMessageManager* aMessageManager)
+ : mInitialized(false),
+ mMessageManager(aMessageManager)
+{
+ SetIsNotDOMBinding();
+ mozilla::HoldJSObjects(this);
+}
+
+ProcessGlobal::~ProcessGlobal()
+{
+ mAnonymousGlobalScopes.Clear();
+ mozilla::DropJSObjects(this);
+}
+
+ProcessGlobal*
+ProcessGlobal::Get()
+{
+ nsCOMPtr<nsISyncMessageSender> service = do_GetService(NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID);
+ if (!service) {
+ return nullptr;
+ }
+ return static_cast<ProcessGlobal*>(service.get());
+}
+
+// This method isn't automatically forwarded safely because it's notxpcom, so
+// the IDL binding doesn't know what value to return.
+NS_IMETHODIMP_(bool)
+ProcessGlobal::MarkForCC()
+{
+ MarkScopesForCC();
+ return mMessageManager ? mMessageManager->MarkForCC() : false;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ProcessGlobal)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ProcessGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+ tmp->TraverseHostObjectURIs(cb);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ProcessGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ tmp->nsMessageManagerScriptExecutor::Trace(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ProcessGlobal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousGlobalScopes)
+ tmp->UnlinkHostObjectURIs();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ProcessGlobal)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentProcessMessageManager)
+ NS_INTERFACE_MAP_ENTRY(nsIMessageListenerManager)
+ NS_INTERFACE_MAP_ENTRY(nsIMessageSender)
+ NS_INTERFACE_MAP_ENTRY(nsISyncMessageSender)
+ NS_INTERFACE_MAP_ENTRY(nsIContentProcessMessageManager)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(ContentProcessMessageManager)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ProcessGlobal)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ProcessGlobal)
+
+bool
+ProcessGlobal::Init()
+{
+ if (mInitialized) {
+ return true;
+ }
+ mInitialized = true;
+
+ nsISupports* scopeSupports = NS_ISUPPORTS_CAST(nsIContentProcessMessageManager*, this);
+ return InitChildGlobalInternal(scopeSupports, NS_LITERAL_CSTRING("processChildGlobal"));
+}
+
+void
+ProcessGlobal::LoadScript(const nsAString& aURL)
+{
+ Init();
+ LoadScriptInternal(aURL, false);
+}
+
+void
+ProcessGlobal::SetInitialProcessData(JS::HandleValue aInitialData)
+{
+ mMessageManager->SetInitialProcessData(aInitialData);
+}
diff --git a/dom/base/ProcessGlobal.h b/dom/base/ProcessGlobal.h
new file mode 100644
index 000000000..4b93d4351
--- /dev/null
+++ b/dom/base/ProcessGlobal.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_ProcessGlobal_h
+#define mozilla_dom_ProcessGlobal_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsFrameMessageManager.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptContext.h"
+#include "nsIClassInfo.h"
+#include "nsIRunnable.h"
+#include "nsIGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class ProcessGlobal :
+ public nsMessageManagerScriptExecutor,
+ public nsIContentProcessMessageManager,
+ public nsIGlobalObject,
+ public nsIScriptObjectPrincipal,
+ public nsSupportsWeakReference,
+ public mozilla::dom::ipc::MessageManagerCallback,
+ public nsWrapperCache
+{
+public:
+ explicit ProcessGlobal(nsFrameMessageManager* aMessageManager);
+
+ using mozilla::dom::ipc::MessageManagerCallback::GetProcessMessageManager;
+
+ bool Init();
+
+ static ProcessGlobal* Get();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(ProcessGlobal, nsIContentProcessMessageManager)
+
+ NS_FORWARD_SAFE_NSIMESSAGELISTENERMANAGER(mMessageManager)
+ NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
+ NS_FORWARD_SAFE_NSISYNCMESSAGESENDER(mMessageManager)
+ NS_FORWARD_SAFE_NSIMESSAGEMANAGERGLOBAL(mMessageManager)
+ NS_FORWARD_SAFE_NSICONTENTPROCESSMESSAGEMANAGER(mMessageManager)
+
+ virtual void LoadScript(const nsAString& aURL);
+
+ virtual JSObject* GetGlobalJSObject() override
+ {
+ if (!mGlobal) {
+ return nullptr;
+ }
+
+ return mGlobal->GetJSObject();
+ }
+ virtual nsIPrincipal* GetPrincipal() override { return mPrincipal; }
+
+ virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ MOZ_CRASH("ProcessGlobal doesn't use DOM bindings!");
+ }
+
+ void SetInitialProcessData(JS::HandleValue aInitialData);
+
+protected:
+ virtual ~ProcessGlobal();
+
+private:
+ bool mInitialized;
+ RefPtr<nsFrameMessageManager> mMessageManager;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ProcessGlobal_h
diff --git a/dom/base/ResponsiveImageSelector.cpp b/dom/base/ResponsiveImageSelector.cpp
new file mode 100644
index 000000000..84b322f07
--- /dev/null
+++ b/dom/base/ResponsiveImageSelector.cpp
@@ -0,0 +1,779 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIURI.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "nsPresContext.h"
+
+#include "nsCSSParser.h"
+#include "nsCSSProps.h"
+#include "nsIMediaList.h"
+#include "nsRuleNode.h"
+#include "nsRuleData.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION(ResponsiveImageSelector, mOwnerNode)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ResponsiveImageSelector, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ResponsiveImageSelector, Release)
+
+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_IsPercent |
+ 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 (nsCRT::IsAsciiDigit(*iter)) {
+ for (; iter != end && nsCRT::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 || !nsCRT::IsAsciiDigit(*iter)) {
+ // U+002E FULL STOP character (.) must be followed by one or more ASCII digits
+ return false;
+ }
+
+ for (; iter != end && nsCRT::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 || !nsCRT::IsAsciiDigit(*iter)) {
+ // Should have one or more ASCII digits
+ return false;
+ }
+
+ for (; iter != end && nsCRT::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(nsIDocument *aDocument)
+ : mOwnerNode(aDocument),
+ mSelectedCandidateIndex(-1)
+{
+}
+
+ResponsiveImageSelector::~ResponsiveImageSelector()
+{}
+
+// http://www.whatwg.org/specs/web-apps/current-work/#processing-the-image-candidates
+bool
+ResponsiveImageSelector::SetCandidatesFromSourceSet(const nsAString & aSrcSet)
+{
+ ClearSelectedCandidate();
+
+ nsCOMPtr<nsIURI> docBaseURI = mOwnerNode ? mOwnerNode->GetBaseURI() : nullptr;
+
+ if (!docBaseURI) {
+ MOZ_ASSERT(false,
+ "Should not be parsing SourceSet without a document");
+ return false;
+ }
+
+ mCandidates.Clear();
+
+ 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);
+ AppendCandidateIfUnique(candidate);
+ }
+ }
+
+ bool parsedCandidates = mCandidates.Length() > 0;
+
+ // 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[candidates - 1].Type() ==
+ ResponsiveImageCandidate::eCandidateType_Default)) {
+ candidates--;
+ }
+
+ return candidates;
+}
+
+nsIContent*
+ResponsiveImageSelector::Content()
+{
+ return mOwnerNode->IsContent() ? mOwnerNode->AsContent() : nullptr;
+}
+
+nsIDocument*
+ResponsiveImageSelector::Document()
+{
+ return mOwnerNode->OwnerDoc();
+}
+
+void
+ResponsiveImageSelector::SetDefaultSource(const nsAString& aURLString)
+{
+ ClearSelectedCandidate();
+
+ // Check if the last element of our candidates is a default
+ int32_t candidates = mCandidates.Length();
+ if (candidates && (mCandidates[candidates - 1].Type() ==
+ ResponsiveImageCandidate::eCandidateType_Default)) {
+ mCandidates.RemoveElementAt(candidates - 1);
+ }
+
+ mDefaultSourceURL = aURLString;
+
+ // Add new default to end of list
+ MaybeAppendDefaultCandidate();
+}
+
+void
+ResponsiveImageSelector::ClearSelectedCandidate()
+{
+ mSelectedCandidateIndex = -1;
+ mSelectedCandidateURL = nullptr;
+}
+
+bool
+ResponsiveImageSelector::SetSizesFromDescriptor(const nsAString & aSizes)
+{
+ ClearSelectedCandidate();
+ mSizeQueries.Clear();
+ mSizeValues.Clear();
+
+ nsCSSParser cssParser;
+
+ return cssParser.ParseSourceSizeList(aSizes, nullptr, 0,
+ mSizeQueries, mSizeValues, true);
+}
+
+void
+ResponsiveImageSelector::AppendCandidateIfUnique(const 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.Type() == ResponsiveImageCandidate::eCandidateType_Default) {
+ 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(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);
+ // 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(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);
+}
+
+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;
+ }
+
+ nsIDocument* doc = Document();
+ nsIPresShell *shell = doc ? doc->GetShell() : nullptr;
+ nsPresContext *pctx = shell ? shell->GetPresContext() : nullptr;
+ nsCOMPtr<nsIURI> baseURI = mOwnerNode ? mOwnerNode->GetBaseURI() : nullptr;
+
+ if (!pctx || !doc || !baseURI) {
+ return oldBest != -1;
+ }
+
+ double displayDensity = pctx->CSSPixelsToDevPixels(1.0f);
+
+ // 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)
+{
+ unsigned int numSizes = mSizeQueries.Length();
+ nsIDocument* doc = Document();
+ nsIPresShell *presShell = doc ? doc->GetShell() : nullptr;
+ nsPresContext *pctx = presShell ? presShell->GetPresContext() : nullptr;
+
+ if (!pctx) {
+ return false;
+ }
+
+ MOZ_ASSERT(numSizes == mSizeValues.Length(),
+ "mSizeValues length differs from mSizeQueries");
+
+ unsigned int i;
+ for (i = 0; i < numSizes; i++) {
+ if (mSizeQueries[i]->Matches(pctx, nullptr)) {
+ break;
+ }
+ }
+
+ nscoord effectiveWidth;
+ if (i == numSizes) {
+ // No match defaults to 100% viewport
+ nsCSSValue defaultWidth(100.0f, eCSSUnit_ViewportWidth);
+ effectiveWidth = nsRuleNode::CalcLengthWithInitialFont(pctx,
+ defaultWidth);
+ } else {
+ effectiveWidth = nsRuleNode::CalcLengthWithInitialFont(pctx,
+ mSizeValues[i]);
+ }
+
+ *aWidth = nsPresContext::AppUnitsToDoubleCSSPixels(std::max(effectiveWidth, 0));
+ return true;
+}
+
+ResponsiveImageCandidate::ResponsiveImageCandidate()
+{
+ mType = eCandidateType_Invalid;
+ mValue.mDensity = 1.0;
+}
+
+ResponsiveImageCandidate::ResponsiveImageCandidate(const nsAString& aURLString,
+ double aDensity)
+ : mURLString(aURLString)
+{
+ mType = eCandidateType_Density;
+ mValue.mDensity = aDensity;
+}
+
+
+void
+ResponsiveImageCandidate::SetURLSpec(const nsAString& aURLString)
+{
+ mURLString = aURLString;
+}
+
+void
+ResponsiveImageCandidate::SetParameterAsComputedWidth(int32_t aWidth)
+{
+ mType = eCandidateType_ComputedFromWidth;
+ mValue.mWidth = aWidth;
+}
+
+void
+ResponsiveImageCandidate::SetParameterDefault()
+{
+ MOZ_ASSERT(mType == eCandidateType_Invalid, "double setting candidate type");
+
+ mType = eCandidateType_Default;
+ // mValue shouldn't actually be used for this type, but set it to default
+ // anyway
+ mValue.mDensity = 1.0;
+}
+
+void
+ResponsiveImageCandidate::SetParameterInvalid()
+{
+ mType = eCandidateType_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(mType == eCandidateType_Invalid, "double setting candidate type");
+
+ mType = eCandidateType_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)
+ 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;
+ } else 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 Type() != eCandidateType_Invalid;
+}
+
+bool
+ResponsiveImageCandidate::HasSameParameter(const ResponsiveImageCandidate & aOther) const
+{
+ if (aOther.mType != mType) {
+ return false;
+ }
+
+ if (mType == eCandidateType_Default) {
+ return true;
+ }
+
+ if (mType == eCandidateType_Density) {
+ return aOther.mValue.mDensity == mValue.mDensity;
+ }
+
+ if (mType == eCandidateType_Invalid) {
+ MOZ_ASSERT(false, "Comparing invalid candidates?");
+ return true;
+ } else if (mType == eCandidateType_ComputedFromWidth) {
+ return aOther.mValue.mWidth == mValue.mWidth;
+ }
+
+ MOZ_ASSERT(false, "Somebody forgot to check for all uses of this enum");
+ return false;
+}
+
+const nsAString&
+ResponsiveImageCandidate::URLString() const
+{
+ return mURLString;
+}
+
+double
+ResponsiveImageCandidate::Density(ResponsiveImageSelector *aSelector) const
+{
+ if (mType == eCandidateType_ComputedFromWidth) {
+ double width;
+ if (!aSelector->ComputeFinalWidthForCurrentViewport(&width)) {
+ return 1.0;
+ }
+ return Density(width);
+ }
+
+ // Other types don't need matching width
+ MOZ_ASSERT(mType == eCandidateType_Default || mType == eCandidateType_Density,
+ "unhandled candidate type");
+ return Density(-1);
+}
+
+double
+ResponsiveImageCandidate::Density(double aMatchingWidth) const
+{
+ if (mType == eCandidateType_Invalid) {
+ MOZ_ASSERT(false, "Getting density for uninitialized candidate");
+ return 1.0;
+ }
+
+ if (mType == eCandidateType_Default) {
+ return 1.0;
+ }
+
+ if (mType == eCandidateType_Density) {
+ return mValue.mDensity;
+ } else if (mType == eCandidateType_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;
+}
+
+bool
+ResponsiveImageCandidate::IsComputedFromWidth() const
+{
+ if (mType == eCandidateType_ComputedFromWidth) {
+ return true;
+ }
+
+ MOZ_ASSERT(mType == eCandidateType_Default || mType == eCandidateType_Density,
+ "Unknown candidate type");
+ return false;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/ResponsiveImageSelector.h b/dom/base/ResponsiveImageSelector.h
new file mode 100644
index 000000000..c722e8784
--- /dev/null
+++ b/dom/base/ResponsiveImageSelector.h
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsAutoPtr.h"
+#include "nsISupports.h"
+#include "nsIContent.h"
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsMediaQuery;
+class nsCSSValue;
+
+namespace mozilla {
+namespace 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(nsIDocument* aDocument);
+
+ // 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);
+
+ // 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);
+
+ 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.
+ nsIDocument *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();
+
+ // 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(const 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;
+ // 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;
+
+ nsTArray< nsAutoPtr<nsMediaQuery> > mSizeQueries;
+ nsTArray<nsCSSValue> mSizeValues;
+};
+
+class ResponsiveImageCandidate {
+public:
+ ResponsiveImageCandidate();
+ ResponsiveImageCandidate(const nsAString& aURLString, double aDensity);
+
+ void SetURLSpec(const nsAString& aURLString);
+ // 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;
+
+ // 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;
+
+ // If this selector is computed from the selector's matching width.
+ bool IsComputedFromWidth() const;
+
+ enum eCandidateType {
+ eCandidateType_Invalid,
+ eCandidateType_Density,
+ // Treated as 1.0 density, but a separate type so we can update the
+ // responsive candidates and default separately
+ eCandidateType_Default,
+ eCandidateType_ComputedFromWidth
+ };
+
+ eCandidateType Type() const { return mType; }
+
+private:
+
+ nsString mURLString;
+ eCandidateType mType;
+ union {
+ double mDensity;
+ int32_t mWidth;
+ } mValue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_responsiveimageselector_h__
diff --git a/dom/base/SameProcessMessageQueue.cpp b/dom/base/SameProcessMessageQueue.cpp
new file mode 100644
index 000000000..61b540617
--- /dev/null
+++ b/dom/base/SameProcessMessageQueue.cpp
@@ -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/. */
+
+#include "SameProcessMessageQueue.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+SameProcessMessageQueue* SameProcessMessageQueue::sSingleton;
+
+SameProcessMessageQueue::SameProcessMessageQueue()
+{
+}
+
+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()
+{
+ nsTArray<RefPtr<Runnable>> queue;
+ mQueue.SwapElements(queue);
+ 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 000000000..64eb78a4b
--- /dev/null
+++ b/dom/base/SameProcessMessageQueue.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_SameProcessMessageQueue_h
+#define mozilla_dom_SameProcessMessageQueue_h
+
+#include "nsIRunnable.h"
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace 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() {}
+
+ private:
+ bool mDispatched;
+ };
+
+ void Push(Runnable* aRunnable);
+ void Flush();
+
+ static SameProcessMessageQueue* Get();
+
+private:
+ nsTArray<RefPtr<Runnable>> mQueue;
+ static SameProcessMessageQueue* sSingleton;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SameProcessMessageQueue_h
diff --git a/dom/base/ScreenOrientation.cpp b/dom/base/ScreenOrientation.cpp
new file mode 100644
index 000000000..bb3ccf5c3
--- /dev/null
+++ b/dom/base/ScreenOrientation.cpp
@@ -0,0 +1,688 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDeviceSensors.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.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/Promise.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_INHERITED(ScreenOrientation)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(ScreenOrientation, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(ScreenOrientation, DOMEventTargetHelper)
+
+static OrientationType
+InternalOrientationToType(ScreenOrientationInternal aOrientation)
+{
+ switch (aOrientation) {
+ case eScreenOrientation_PortraitPrimary:
+ return OrientationType::Portrait_primary;
+ case eScreenOrientation_PortraitSecondary:
+ return OrientationType::Portrait_secondary;
+ case eScreenOrientation_LandscapePrimary:
+ return OrientationType::Landscape_primary;
+ case eScreenOrientation_LandscapeSecondary:
+ return OrientationType::Landscape_secondary;
+ default:
+ MOZ_CRASH("Bad aOrientation value");
+ }
+}
+
+static ScreenOrientationInternal
+OrientationTypeToInternal(OrientationType aOrientation)
+{
+ switch (aOrientation) {
+ case OrientationType::Portrait_primary:
+ return eScreenOrientation_PortraitPrimary;
+ case OrientationType::Portrait_secondary:
+ return eScreenOrientation_PortraitSecondary;
+ case OrientationType::Landscape_primary:
+ return eScreenOrientation_LandscapePrimary;
+ case OrientationType::Landscape_secondary:
+ return eScreenOrientation_LandscapeSecondary;
+ default:
+ MOZ_CRASH("Bad aOrientation value");
+ }
+}
+
+ScreenOrientation::ScreenOrientation(nsPIDOMWindowInner* aWindow, nsScreen* aScreen)
+ : DOMEventTargetHelper(aWindow), mScreen(aScreen)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aScreen);
+
+ hal::RegisterScreenConfigurationObserver(this);
+
+ hal::ScreenConfiguration config;
+ hal::GetCurrentScreenConfiguration(&config);
+ mType = InternalOrientationToType(config.orientation());
+ mAngle = config.angle();
+
+ nsIDocument* doc = GetResponsibleDocument();
+ if (doc) {
+ doc->SetCurrentOrientation(mType, mAngle);
+ }
+}
+
+ScreenOrientation::~ScreenOrientation()
+{
+ hal::UnregisterScreenConfigurationObserver(this);
+ MOZ_ASSERT(!mFullScreenListener);
+}
+
+class ScreenOrientation::FullScreenEventListener final : public nsIDOMEventListener
+{
+ ~FullScreenEventListener() {}
+public:
+ FullScreenEventListener() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+};
+
+class ScreenOrientation::VisibleEventListener final : public nsIDOMEventListener
+{
+ ~VisibleEventListener() {}
+public:
+ VisibleEventListener() {}
+
+ 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,
+ ScreenOrientationInternal aOrientationLock,
+ nsIDocument* aDocument,
+ bool aIsFullScreen);
+protected:
+ bool OrientationLockContains(OrientationType aOrientationType);
+
+ RefPtr<ScreenOrientation> mScreenOrientation;
+ RefPtr<Promise> mPromise;
+ ScreenOrientationInternal mOrientationLock;
+ nsCOMPtr<nsIDocument> mDocument;
+ bool mIsFullScreen;
+};
+
+NS_IMPL_ISUPPORTS(ScreenOrientation::LockOrientationTask, nsIRunnable)
+
+ScreenOrientation::LockOrientationTask::LockOrientationTask(
+ ScreenOrientation* aScreenOrientation, Promise* aPromise,
+ ScreenOrientationInternal aOrientationLock,
+ nsIDocument* 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()
+{
+}
+
+bool
+ScreenOrientation::LockOrientationTask::OrientationLockContains(
+ OrientationType aOrientationType)
+{
+ return mOrientationLock & OrientationTypeToInternal(aOrientationType);
+}
+
+NS_IMETHODIMP
+ScreenOrientation::LockOrientationTask::Run()
+{
+ // 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->SetOrientationPendingPromise(nullptr);
+ return NS_OK;
+ }
+
+ if (mOrientationLock == eScreenOrientation_None) {
+ mScreenOrientation->UnlockDeviceOrientation();
+ mPromise->MaybeResolveWithUndefined();
+ mDocument->SetOrientationPendingPromise(nullptr);
+ return NS_OK;
+ }
+
+ ErrorResult rv;
+ bool result = mScreenOrientation->LockDeviceOrientation(mOrientationLock,
+ mIsFullScreen, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ if (NS_WARN_IF(!result)) {
+ mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ mDocument->SetOrientationPendingPromise(nullptr);
+ return NS_OK;
+ }
+
+ if (OrientationLockContains(mDocument->CurrentOrientationType()) ||
+ (mOrientationLock == eScreenOrientation_Default &&
+ mDocument->CurrentOrientationAngle() == 0)) {
+ // Orientation lock will not cause an orientation change.
+ mPromise->MaybeResolveWithUndefined();
+ mDocument->SetOrientationPendingPromise(nullptr);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<Promise>
+ScreenOrientation::Lock(OrientationLockType aOrientation, ErrorResult& aRv)
+{
+ ScreenOrientationInternal orientation = eScreenOrientation_None;
+
+ switch (aOrientation) {
+ case OrientationLockType::Any:
+ orientation = eScreenOrientation_PortraitPrimary |
+ eScreenOrientation_PortraitSecondary |
+ eScreenOrientation_LandscapePrimary |
+ eScreenOrientation_LandscapeSecondary;
+ break;
+ case OrientationLockType::Natural:
+ orientation |= eScreenOrientation_Default;
+ break;
+ case OrientationLockType::Landscape:
+ orientation = eScreenOrientation_LandscapePrimary |
+ eScreenOrientation_LandscapeSecondary;
+ break;
+ case OrientationLockType::Portrait:
+ orientation = eScreenOrientation_PortraitPrimary |
+ eScreenOrientation_PortraitSecondary;
+ break;
+ case OrientationLockType::Portrait_primary:
+ orientation = eScreenOrientation_PortraitPrimary;
+ break;
+ case OrientationLockType::Portrait_secondary:
+ orientation = eScreenOrientation_PortraitSecondary;
+ break;
+ case OrientationLockType::Landscape_primary:
+ orientation = eScreenOrientation_LandscapePrimary;
+ break;
+ case OrientationLockType::Landscape_secondary:
+ orientation = eScreenOrientation_LandscapeSecondary;
+ break;
+ default:
+ NS_WARNING("Unexpected orientation type");
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return LockInternal(orientation, aRv);
+}
+
+static inline void
+AbortOrientationPromises(nsIDocShell* aDocShell)
+{
+ MOZ_ASSERT(aDocShell);
+
+ nsIDocument* doc = aDocShell->GetDocument();
+ if (doc) {
+ Promise* promise = doc->GetOrientationPendingPromise();
+ if (promise) {
+ promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+ doc->SetOrientationPendingPromise(nullptr);
+ }
+ }
+
+ int32_t childCount;
+ aDocShell->GetChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ if (NS_SUCCEEDED(aDocShell->GetChildAt(i, getter_AddRefs(child)))) {
+ nsCOMPtr<nsIDocShell> childShell(do_QueryInterface(child));
+ if (childShell) {
+ AbortOrientationPromises(childShell);
+ }
+ }
+ }
+}
+
+already_AddRefed<Promise>
+ScreenOrientation::LockInternal(ScreenOrientationInternal aOrientation, ErrorResult& aRv)
+{
+ // Steps to apply an orientation lock as defined in spec.
+
+ nsIDocument* doc = GetResponsibleDocument();
+ if (NS_WARN_IF(!doc)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ if (NS_WARN_IF(!owner)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = owner->GetDocShell();
+ if (NS_WARN_IF(!docShell)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ 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 !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_GONK)
+ // User agent does not support locking the screen orientation.
+ p->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return p.forget();
+#else
+ LockPermission perm = GetLockOrientationPermission(true);
+ if (perm == LOCK_DENIED) {
+ p->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return p.forget();
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
+ nsCOMPtr<nsIDocShell> rootShell(do_QueryInterface(root));
+ if (!rootShell) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ rootShell->SetOrientationLock(aOrientation);
+ AbortOrientationPromises(rootShell);
+
+ doc->SetOrientationPendingPromise(p);
+
+ 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();
+#endif
+}
+
+bool
+ScreenOrientation::LockDeviceOrientation(ScreenOrientationInternal aOrientation,
+ bool aIsFullScreen, ErrorResult& aRv)
+{
+ if (!GetOwner()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(GetOwner()->GetDoc());
+ // We need to register a listener so we learn when we leave full-screen
+ // 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 false;
+ }
+
+ if (NS_WARN_IF(!hal::LockScreenOrientation(aOrientation))) {
+ return false;
+ }
+
+ // We are fullscreen and lock has been accepted.
+ if (aIsFullScreen && !mFullScreenListener) {
+ mFullScreenListener = new FullScreenEventListener();
+ aRv = target->AddSystemEventListener(NS_LITERAL_STRING("fullscreenchange"),
+ mFullScreenListener, /* useCapture = */ true);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+ScreenOrientation::Unlock(ErrorResult& aRv)
+{
+ RefPtr<Promise> p = LockInternal(eScreenOrientation_None, aRv);
+}
+
+void
+ScreenOrientation::UnlockDeviceOrientation()
+{
+ hal::UnlockScreenOrientation();
+
+ if (!mFullScreenListener || !GetOwner()) {
+ mFullScreenListener = nullptr;
+ return;
+ }
+
+ // Remove event listener in case of fullscreen lock.
+ nsCOMPtr<EventTarget> target = do_QueryInterface(GetOwner()->GetDoc());
+ if (target) {
+ DebugOnly<nsresult> rv =
+ target->RemoveSystemEventListener(NS_LITERAL_STRING("fullscreenchange"),
+ mFullScreenListener,
+ /* useCapture */ true);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveSystemEventListener failed");
+ }
+
+ mFullScreenListener = nullptr;
+}
+
+OrientationType
+ScreenOrientation::DeviceType() const
+{
+ return ShouldResistFingerprinting() ? OrientationType::Landscape_primary
+ : mType;
+}
+
+uint16_t
+ScreenOrientation::DeviceAngle() const
+{
+ return ShouldResistFingerprinting() ? 0 : mAngle;
+}
+
+OrientationType
+ScreenOrientation::GetType(ErrorResult& aRv) const
+{
+ if (ShouldResistFingerprinting()) {
+ return OrientationType::Landscape_primary;
+ }
+
+ nsIDocument* doc = GetResponsibleDocument();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return OrientationType::Portrait_primary;
+ }
+
+ return doc->CurrentOrientationType();
+}
+
+uint16_t
+ScreenOrientation::GetAngle(ErrorResult& aRv) const
+{
+ if (ShouldResistFingerprinting()) {
+ return 0;
+ }
+
+ nsIDocument* doc = GetResponsibleDocument();
+ if (!doc) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return 0;
+ }
+
+ return doc->CurrentOrientationAngle();
+}
+
+ScreenOrientation::LockPermission
+ScreenOrientation::GetLockOrientationPermission(bool aCheckSandbox) const
+{
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ if (!owner) {
+ return LOCK_DENIED;
+ }
+
+ // Chrome can always lock the screen orientation.
+ nsIDocShell* docShell = owner->GetDocShell();
+ if (docShell && docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ return LOCK_ALLOWED;
+ }
+
+ nsCOMPtr<nsIDocument> 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;
+ }
+
+ // Apps can always lock the screen orientation.
+ if (doc->NodePrincipal()->GetAppStatus() >=
+ nsIPrincipal::APP_STATUS_INSTALLED) {
+ return LOCK_ALLOWED;
+ }
+
+ if (Preferences::GetBool("dom.screenorientation.testing.non_fullscreen_lock_allow",
+ false)) {
+ return LOCK_ALLOWED;
+ }
+
+ // Other content must be full-screen in order to lock orientation.
+ return doc->Fullscreen() ? FULLSCREEN_LOCK_ALLOWED : LOCK_DENIED;
+}
+
+nsIDocument*
+ScreenOrientation::GetResponsibleDocument() const
+{
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ if (!owner) {
+ return nullptr;
+ }
+
+ return owner->GetDoc();
+}
+
+void
+ScreenOrientation::Notify(const hal::ScreenConfiguration& aConfiguration)
+{
+ if (ShouldResistFingerprinting()) {
+ return;
+ }
+
+ nsIDocument* doc = GetResponsibleDocument();
+ if (!doc) {
+ return;
+ }
+
+ ScreenOrientationInternal orientation = aConfiguration.orientation();
+ if (orientation != eScreenOrientation_PortraitPrimary &&
+ orientation != eScreenOrientation_PortraitSecondary &&
+ orientation != eScreenOrientation_LandscapePrimary &&
+ orientation != eScreenOrientation_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 = aConfiguration.angle();
+ mType = InternalOrientationToType(orientation);
+
+ DebugOnly<nsresult> rv;
+ if (mScreen && mType != previousOrientation) {
+ // Use of mozorientationchange is deprecated.
+ rv = mScreen->DispatchTrustedEvent(NS_LITERAL_STRING("mozorientationchange"));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
+ }
+
+ if (doc->Hidden() && !mVisibleListener) {
+ mVisibleListener = new VisibleEventListener();
+ rv = doc->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+ mVisibleListener, /* useCapture = */ true);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddSystemEventListener failed");
+ return;
+ }
+
+ if (mType != doc->CurrentOrientationType()) {
+ doc->SetCurrentOrientation(mType, mAngle);
+
+ Promise* pendingPromise = doc->GetOrientationPendingPromise();
+ if (pendingPromise) {
+ pendingPromise->MaybeResolveWithUndefined();
+ doc->SetOrientationPendingPromise(nullptr);
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(this,
+ &ScreenOrientation::DispatchChangeEvent);
+ rv = NS_DispatchToMainThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+ }
+}
+
+void
+ScreenOrientation::UpdateActiveOrientationLock(ScreenOrientationInternal aOrientation)
+{
+ if (aOrientation == eScreenOrientation_None) {
+ hal::UnlockScreenOrientation();
+ } else {
+ DebugOnly<bool> ok = hal::LockScreenOrientation(aOrientation);
+ NS_WARNING_ASSERTION(ok, "hal::LockScreenOrientation failed");
+ }
+}
+
+void
+ScreenOrientation::DispatchChangeEvent()
+{
+ DebugOnly<nsresult> rv = DispatchTrustedEvent(NS_LITERAL_STRING("change"));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
+}
+
+JSObject*
+ScreenOrientation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ScreenOrientationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+bool
+ScreenOrientation::ShouldResistFingerprinting() const
+{
+ bool resist = false;
+ if (nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner()) {
+ resist = nsContentUtils::ShouldResistFingerprinting(owner->GetDocShell());
+ }
+ return resist;
+}
+
+NS_IMPL_ISUPPORTS(ScreenOrientation::VisibleEventListener, nsIDOMEventListener)
+
+NS_IMETHODIMP
+ScreenOrientation::VisibleEventListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ // Document may have become visible, if the page is visible, run the steps
+ // following the "now visible algorithm" as specified.
+ nsCOMPtr<EventTarget> target = aEvent->InternalDOMEvent()->GetCurrentTarget();
+ MOZ_ASSERT(target);
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(target);
+ if (!doc || doc->Hidden()) {
+ return NS_OK;
+ }
+
+ auto* win = nsGlobalWindow::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);
+
+ rv = target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+ this, true);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ if (doc->CurrentOrientationType() != orientation->DeviceType()) {
+ doc->SetCurrentOrientation(orientation->DeviceType(), orientation->DeviceAngle());
+
+ Promise* pendingPromise = doc->GetOrientationPendingPromise();
+ if (pendingPromise) {
+ pendingPromise->MaybeResolveWithUndefined();
+ doc->SetOrientationPendingPromise(nullptr);
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(orientation,
+ &ScreenOrientation::DispatchChangeEvent);
+ 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(nsIDOMEvent* aEvent)
+{
+#ifdef DEBUG
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ MOZ_ASSERT(eventType.EqualsLiteral("fullscreenchange"));
+#endif
+
+ nsCOMPtr<EventTarget> target = aEvent->InternalDOMEvent()->GetCurrentTarget();
+ MOZ_ASSERT(target);
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(target);
+ 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;
+ }
+
+ hal::UnlockScreenOrientation();
+
+ nsresult rv = target->RemoveSystemEventListener(NS_LITERAL_STRING("fullscreenchange"),
+ this, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
diff --git a/dom/base/ScreenOrientation.h b/dom/base/ScreenOrientation.h
new file mode 100644
index 000000000..f836f688a
--- /dev/null
+++ b/dom/base/ScreenOrientation.h
@@ -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/. */
+
+#ifndef mozilla_dom_ScreenOrientation_h
+#define mozilla_dom_ScreenOrientation_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/ScreenOrientationBinding.h"
+#include "mozilla/HalScreenConfiguration.h"
+
+class nsScreen;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+// Make sure that any change to ScreenOrientationInternal values are
+// also made in mobile/android/base/GeckoScreenOrientation.java
+typedef uint32_t ScreenOrientationInternal;
+
+static const ScreenOrientationInternal eScreenOrientation_None = 0;
+static const ScreenOrientationInternal eScreenOrientation_PortraitPrimary = 1u << 0;
+static const ScreenOrientationInternal eScreenOrientation_PortraitSecondary = 1u << 1;
+static const ScreenOrientationInternal eScreenOrientation_LandscapePrimary = 1u << 2;
+static const ScreenOrientationInternal eScreenOrientation_LandscapeSecondary = 1u << 3;
+//eScreenOrientation_Default will use the natural orientation for the deivce,
+//it could be PortraitPrimary or LandscapePrimary depends on display resolution
+static const ScreenOrientationInternal eScreenOrientation_Default = 1u << 4;
+
+class ScreenOrientation final : public DOMEventTargetHelper,
+ public mozilla::hal::ScreenConfigurationObserver
+{
+ // nsScreen has deprecated API that shares implementation.
+ friend class ::nsScreen;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ScreenOrientation, mozilla::DOMEventTargetHelper)
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(mozilla::DOMEventTargetHelper)
+
+ IMPL_EVENT_HANDLER(change)
+
+ 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() const;
+ uint16_t DeviceAngle() const;
+
+ // GetType and GetAngle gets the type and angle of the responsible document
+ // (as defined in specification).
+ OrientationType GetType(ErrorResult& aRv) const;
+ uint16_t GetAngle(ErrorResult& aRv) const;
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void Notify(const mozilla::hal::ScreenConfiguration& aConfiguration) override;
+
+ static void UpdateActiveOrientationLock(ScreenOrientationInternal aOrientation);
+
+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.
+ bool LockDeviceOrientation(ScreenOrientationInternal aOrientation,
+ bool aIsFullscreen, ErrorResult& aRv);
+
+ // This method calls in to the HAL to unlock the device and removes
+ // full screen change listener.
+ void UnlockDeviceOrientation();
+
+ // This method performs the same function as |Lock| except it takes
+ // a ScreenOrientationInternal argument instead of an OrientationType.
+ // This method exists in order to share implementation with nsScreen that
+ // uses ScreenOrientationInternal.
+ already_AddRefed<Promise> LockInternal(ScreenOrientationInternal aOrientation,
+ ErrorResult& aRv);
+
+ void DispatchChangeEvent();
+
+ bool ShouldResistFingerprinting() const;
+
+ LockPermission GetLockOrientationPermission(bool aCheckSandbox) const;
+
+ // Gets the responsible document as defined in the spec.
+ nsIDocument* GetResponsibleDocument() const;
+
+ RefPtr<nsScreen> mScreen;
+ RefPtr<FullScreenEventListener> mFullScreenListener;
+ RefPtr<VisibleEventListener> mVisibleListener;
+ OrientationType mType;
+ uint16_t mAngle;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ScreenOrientation_h
diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp
new file mode 100644
index 000000000..d67f2167a
--- /dev/null
+++ b/dom/base/ScriptSettings.cpp
@@ -0,0 +1,833 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ScriptSettings.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CycleCollectedJSContext.h"
+
+#include "jsapi.h"
+#include "xpcpublic.h"
+#include "nsIGlobalObject.h"
+#include "nsIDocShell.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsTArray.h"
+#include "nsJSUtils.h"
+#include "nsDOMJSUtils.h"
+#include "WorkerPrivate.h"
+
+namespace mozilla {
+namespace dom {
+
+static MOZ_THREAD_LOCAL(ScriptSettingsStackEntry*) sScriptSettingsTLS;
+static bool sScriptSettingsTLSInitialized;
+
+class ScriptSettingsStack {
+public:
+ static ScriptSettingsStackEntry* Top() {
+ return sScriptSettingsTLS.get();
+ }
+
+ static void Push(ScriptSettingsStackEntry *aEntry) {
+ MOZ_ASSERT(!aEntry->mOlder);
+ // Whenever JSAPI use is disabled, the next stack entry pushed must
+ // not be an AutoIncumbentScript.
+ MOZ_ASSERT_IF(!Top() || Top()->NoJSAPI(),
+ !aEntry->IsIncumbentScript());
+ // Whenever the top entry is not an incumbent canidate, the next stack entry
+ // pushed must not be an AutoIncumbentScript.
+ MOZ_ASSERT_IF(Top() && !Top()->IsIncumbentCandidate(),
+ !aEntry->IsIncumbentScript());
+
+ aEntry->mOlder = Top();
+ sScriptSettingsTLS.set(aEntry);
+ }
+
+ static void Pop(ScriptSettingsStackEntry *aEntry) {
+ MOZ_ASSERT(aEntry == Top());
+ sScriptSettingsTLS.set(aEntry->mOlder);
+ }
+
+ static nsIGlobalObject* IncumbentGlobal() {
+ ScriptSettingsStackEntry *entry = Top();
+ while (entry) {
+ if (entry->IsIncumbentCandidate()) {
+ return entry->mGlobalObject;
+ }
+ entry = entry->mOlder;
+ }
+ return nullptr;
+ }
+
+ static ScriptSettingsStackEntry* EntryPoint() {
+ ScriptSettingsStackEntry *entry = Top();
+ while (entry) {
+ if (entry->IsEntryCandidate()) {
+ return entry;
+ }
+ entry = entry->mOlder;
+ }
+ return nullptr;
+ }
+
+ static nsIGlobalObject* EntryGlobal() {
+ ScriptSettingsStackEntry *entry = EntryPoint();
+ if (!entry) {
+ return nullptr;
+ }
+ return entry->mGlobalObject;
+ }
+
+#ifdef DEBUG
+ static ScriptSettingsStackEntry* TopNonIncumbentScript() {
+ ScriptSettingsStackEntry *entry = Top();
+ while (entry) {
+ if (!entry->IsIncumbentScript()) {
+ return entry;
+ }
+ entry = entry->mOlder;
+ }
+ return nullptr;
+ }
+#endif // DEBUG
+
+};
+
+static unsigned long gRunToCompletionListeners = 0;
+
+void
+UseEntryScriptProfiling()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ ++gRunToCompletionListeners;
+}
+
+void
+UnuseEntryScriptProfiling()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(gRunToCompletionListeners > 0);
+ --gRunToCompletionListeners;
+}
+
+void
+InitScriptSettings()
+{
+ bool success = sScriptSettingsTLS.init();
+ if (!success) {
+ MOZ_CRASH();
+ }
+
+ sScriptSettingsTLS.set(nullptr);
+ sScriptSettingsTLSInitialized = true;
+}
+
+void
+DestroyScriptSettings()
+{
+ MOZ_ASSERT(sScriptSettingsTLS.get() == nullptr);
+}
+
+bool
+ScriptSettingsInitialized()
+{
+ return sScriptSettingsTLSInitialized;
+}
+
+ScriptSettingsStackEntry::ScriptSettingsStackEntry(nsIGlobalObject *aGlobal,
+ Type aType)
+ : mGlobalObject(aGlobal)
+ , mType(aType)
+ , mOlder(nullptr)
+{
+ MOZ_ASSERT_IF(IsIncumbentCandidate() && !NoJSAPI(), mGlobalObject);
+ MOZ_ASSERT(!mGlobalObject || mGlobalObject->GetGlobalJSObject(),
+ "Must have an actual JS global for the duration on the stack");
+ MOZ_ASSERT(!mGlobalObject ||
+ JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()),
+ "No outer windows allowed");
+}
+
+ScriptSettingsStackEntry::~ScriptSettingsStackEntry()
+{
+ // We must have an actual JS global for the entire time this is on the stack.
+ MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject());
+}
+
+// If the entry or incumbent global ends up being something that the subject
+// principal doesn't subsume, we don't want to use it. This never happens on
+// the web, but can happen with asymmetric privilege relationships (i.e.
+// nsExpandedPrincipal and System Principal).
+//
+// The most correct thing to use instead would be the topmost global on the
+// callstack whose principal is subsumed by the subject principal. But that's
+// hard to compute, so we just substitute the global of the current
+// compartment. In practice, this is fine.
+//
+// Note that in particular things like:
+//
+// |SpecialPowers.wrap(crossOriginWindow).eval(open())|
+//
+// trigger this case. Although both the entry global and the current global
+// have normal principals, the use of Gecko-specific System-Principaled JS
+// puts the code from two different origins on the callstack at once, which
+// doesn't happen normally on the web.
+static nsIGlobalObject*
+ClampToSubject(nsIGlobalObject* aGlobalOrNull)
+{
+ if (!aGlobalOrNull || !NS_IsMainThread()) {
+ return aGlobalOrNull;
+ }
+
+ nsIPrincipal* globalPrin = aGlobalOrNull->PrincipalOrNull();
+ NS_ENSURE_TRUE(globalPrin, GetCurrentGlobal());
+ if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->SubsumesConsideringDomain(globalPrin)) {
+ return GetCurrentGlobal();
+ }
+
+ return aGlobalOrNull;
+}
+
+nsIGlobalObject*
+GetEntryGlobal()
+{
+ return ClampToSubject(ScriptSettingsStack::EntryGlobal());
+}
+
+nsIDocument*
+GetEntryDocument()
+{
+ nsIGlobalObject* global = GetEntryGlobal();
+ nsCOMPtr<nsPIDOMWindowInner> entryWin = do_QueryInterface(global);
+
+ // If our entry global isn't a window, see if it's an addon scope associated
+ // with a window. If it is, the caller almost certainly wants that rather
+ // than null.
+ if (!entryWin && global) {
+ if (auto* win = xpc::AddonWindowOrNull(global->GetGlobalJSObject())) {
+ entryWin = win->AsInner();
+ }
+ }
+
+ return entryWin ? entryWin->GetExtantDoc() : nullptr;
+}
+
+nsIGlobalObject*
+GetIncumbentGlobal()
+{
+ // We need the current JSContext in order to check the JS for
+ // scripted frames that may have appeared since anyone last
+ // manipulated the stack. If it's null, that means that there
+ // must be no entry global on the stack, and therefore no incumbent
+ // global either.
+ JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
+ if (!cx) {
+ MOZ_ASSERT(ScriptSettingsStack::EntryGlobal() == nullptr);
+ return nullptr;
+ }
+
+ // See what the JS engine has to say. If we've got a scripted caller
+ // override in place, the JS engine will lie to us and pretend that
+ // there's nothing on the JS stack, which will cause us to check the
+ // incumbent script stack below.
+ if (JSObject *global = JS::GetScriptedCallerGlobal(cx)) {
+ return ClampToSubject(xpc::NativeGlobal(global));
+ }
+
+ // Ok, nothing from the JS engine. Let's use whatever's on the
+ // explicit stack.
+ return ClampToSubject(ScriptSettingsStack::IncumbentGlobal());
+}
+
+nsIGlobalObject*
+GetCurrentGlobal()
+{
+ JSContext *cx = nsContentUtils::GetCurrentJSContextForThread();
+ if (!cx) {
+ return nullptr;
+ }
+
+ JSObject *global = JS::CurrentGlobalOrNull(cx);
+ if (!global) {
+ return nullptr;
+ }
+
+ return xpc::NativeGlobal(global);
+}
+
+nsIPrincipal*
+GetWebIDLCallerPrincipal()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ ScriptSettingsStackEntry *entry = ScriptSettingsStack::EntryPoint();
+
+ // If we have an entry point that is not NoJSAPI, we know it must be an
+ // AutoEntryScript.
+ if (!entry || entry->NoJSAPI()) {
+ return nullptr;
+ }
+ AutoEntryScript* aes = static_cast<AutoEntryScript*>(entry);
+
+ return aes->mWebIDLCallerPrincipal;
+}
+
+bool
+IsJSAPIActive()
+{
+ ScriptSettingsStackEntry* topEntry = ScriptSettingsStack::Top();
+ return topEntry && !topEntry->NoJSAPI();
+}
+
+namespace danger {
+JSContext*
+GetJSContext()
+{
+ return CycleCollectedJSContext::Get()->Context();
+}
+} // namespace danger
+
+JS::RootingContext*
+RootingCx()
+{
+ return CycleCollectedJSContext::Get()->RootingCx();
+}
+
+AutoJSAPI::AutoJSAPI()
+ : ScriptSettingsStackEntry(nullptr, eJSAPI)
+ , mCx(nullptr)
+ , mIsMainThread(false) // For lack of anything better
+{
+}
+
+AutoJSAPI::~AutoJSAPI()
+{
+ if (!mCx) {
+ // No need to do anything here: we never managed to Init, so can't have an
+ // exception on our (nonexistent) JSContext. We also don't need to restore
+ // any state on it. Finally, we never made it to pushing outselves onto the
+ // ScriptSettingsStack, so shouldn't pop.
+ MOZ_ASSERT(ScriptSettingsStack::Top() != this);
+ return;
+ }
+
+ ReportException();
+
+ if (mOldWarningReporter.isSome()) {
+ JS::SetWarningReporter(cx(), mOldWarningReporter.value());
+ }
+
+ // Leave the request before popping.
+ if (mIsMainThread) {
+ mAutoRequest.reset();
+ }
+
+ ScriptSettingsStack::Pop(this);
+}
+
+void
+WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep);
+
+void
+AutoJSAPI::InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
+ JSContext* aCx, bool aIsMainThread)
+{
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aCx == danger::GetJSContext());
+ MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
+ MOZ_ASSERT(bool(aGlobalObject) == bool(aGlobal));
+ MOZ_ASSERT_IF(aGlobalObject, aGlobalObject->GetGlobalJSObject() == aGlobal);
+#ifdef DEBUG
+ bool haveException = JS_IsExceptionPending(aCx);
+#endif // DEBUG
+
+ mCx = aCx;
+ mIsMainThread = aIsMainThread;
+ mGlobalObject = aGlobalObject;
+ if (aIsMainThread) {
+ // We _could_ just unconditionally emplace mAutoRequest here. It's just not
+ // needed on worker threads, and we're hoping to kill it on the main thread
+ // too.
+ mAutoRequest.emplace(mCx);
+ }
+ if (aGlobal) {
+ JS::ExposeObjectToActiveJS(aGlobal);
+ }
+ mAutoNullableCompartment.emplace(mCx, aGlobal);
+
+ ScriptSettingsStack::Push(this);
+
+ mOldWarningReporter.emplace(JS::GetWarningReporter(aCx));
+
+ JS::SetWarningReporter(aCx, WarningOnlyErrorReporter);
+
+#ifdef DEBUG
+ if (haveException) {
+ JS::Rooted<JS::Value> exn(aCx);
+ JS_GetPendingException(aCx, &exn);
+
+ JS_ClearPendingException(aCx);
+ if (exn.isObject()) {
+ JS::Rooted<JSObject*> exnObj(aCx, &exn.toObject());
+
+ nsAutoJSString stack, filename, name, message;
+ int32_t line;
+
+ JS::Rooted<JS::Value> tmp(aCx);
+ if (!JS_GetProperty(aCx, exnObj, "filename", &tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+ if (tmp.isUndefined()) {
+ if (!JS_GetProperty(aCx, exnObj, "fileName", &tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+ }
+
+ if (!filename.init(aCx, tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ if (!JS_GetProperty(aCx, exnObj, "stack", &tmp) ||
+ !stack.init(aCx, tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ if (!JS_GetProperty(aCx, exnObj, "name", &tmp) ||
+ !name.init(aCx, tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ if (!JS_GetProperty(aCx, exnObj, "message", &tmp) ||
+ !message.init(aCx, tmp)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ if (!JS_GetProperty(aCx, exnObj, "lineNumber", &tmp) ||
+ !JS::ToInt32(aCx, tmp, &line)) {
+ JS_ClearPendingException(aCx);
+ line = 0;
+ }
+
+ printf_stderr("PREEXISTING EXCEPTION OBJECT: '%s: %s'\n%s:%d\n%s\n",
+ NS_ConvertUTF16toUTF8(name).get(),
+ NS_ConvertUTF16toUTF8(message).get(),
+ NS_ConvertUTF16toUTF8(filename).get(), line,
+ NS_ConvertUTF16toUTF8(stack).get());
+ } else {
+ // It's a primitive... not much we can do other than stringify it.
+ nsAutoJSString exnStr;
+ if (!exnStr.init(aCx, exn)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ printf_stderr("PREEXISTING EXCEPTION PRIMITIVE: %s\n",
+ NS_ConvertUTF16toUTF8(exnStr).get());
+ }
+ MOZ_ASSERT(false, "We had an exception; we should not have");
+ }
+#endif // DEBUG
+}
+
+AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject,
+ bool aIsMainThread,
+ Type aType)
+ : ScriptSettingsStackEntry(aGlobalObject, aType)
+ , mIsMainThread(aIsMainThread)
+{
+ MOZ_ASSERT(aGlobalObject);
+ MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global");
+ MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
+
+ InitInternal(aGlobalObject, aGlobalObject->GetGlobalJSObject(),
+ danger::GetJSContext(), aIsMainThread);
+}
+
+void
+AutoJSAPI::Init()
+{
+ MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
+
+ InitInternal(/* aGlobalObject */ nullptr, /* aGlobal */ nullptr,
+ danger::GetJSContext(), NS_IsMainThread());
+}
+
+bool
+AutoJSAPI::Init(nsIGlobalObject* aGlobalObject, JSContext* aCx)
+{
+ MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
+ MOZ_ASSERT(aCx);
+
+ if (NS_WARN_IF(!aGlobalObject)) {
+ return false;
+ }
+
+ JSObject* global = aGlobalObject->GetGlobalJSObject();
+ if (NS_WARN_IF(!global)) {
+ return false;
+ }
+
+ InitInternal(aGlobalObject, global, aCx, NS_IsMainThread());
+ return true;
+}
+
+bool
+AutoJSAPI::Init(nsIGlobalObject* aGlobalObject)
+{
+ return Init(aGlobalObject, danger::GetJSContext());
+}
+
+bool
+AutoJSAPI::Init(JSObject* aObject)
+{
+ return Init(xpc::NativeGlobal(aObject));
+}
+
+bool
+AutoJSAPI::Init(nsPIDOMWindowInner* aWindow, JSContext* aCx)
+{
+ return Init(nsGlobalWindow::Cast(aWindow), aCx);
+}
+
+bool
+AutoJSAPI::Init(nsPIDOMWindowInner* aWindow)
+{
+ return Init(nsGlobalWindow::Cast(aWindow));
+}
+
+bool
+AutoJSAPI::Init(nsGlobalWindow* aWindow, JSContext* aCx)
+{
+ return Init(static_cast<nsIGlobalObject*>(aWindow), aCx);
+}
+
+bool
+AutoJSAPI::Init(nsGlobalWindow* aWindow)
+{
+ return Init(static_cast<nsIGlobalObject*>(aWindow));
+}
+
+// Even with autoJSAPIOwnsErrorReporting, the JS engine still sends warning
+// reports to the JSErrorReporter as soon as they are generated. These go
+// directly to the console, so we can handle them easily here.
+//
+// Eventually, SpiderMonkey will have a special-purpose callback for warnings
+// only.
+void
+WarningOnlyErrorReporter(JSContext* aCx, JSErrorReport* aRep)
+{
+ MOZ_ASSERT(JSREPORT_IS_WARNING(aRep->flags));
+ if (!NS_IsMainThread()) {
+ // Reporting a warning on workers is a bit complicated because we have to
+ // climb our parent chain until we get to the main thread. So go ahead and
+ // just go through the worker ReportError codepath here.
+ //
+ // That said, it feels like we should be able to short-circuit things a bit
+ // here by posting an appropriate runnable to the main thread directly...
+ // Worth looking into sometime.
+ workers::WorkerPrivate* worker = workers::GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(worker);
+
+ worker->ReportError(aCx, JS::ConstUTF8CharsZ(), aRep);
+ return;
+ }
+
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+ nsGlobalWindow* win = xpc::CurrentWindowOrNull(aCx);
+ if (!win) {
+ // We run addons in a separate privileged compartment, but if we're in an
+ // addon compartment we should log warnings to the console of the associated
+ // DOM Window.
+ win = xpc::AddonWindowOrNull(JS::CurrentGlobalOrNull(aCx));
+ }
+ xpcReport->Init(aRep, nullptr, nsContentUtils::IsCallerChrome(),
+ win ? win->AsInner()->WindowID() : 0);
+ xpcReport->LogToConsole();
+}
+
+void
+AutoJSAPI::ReportException()
+{
+ if (!HasException()) {
+ return;
+ }
+
+ // AutoJSAPI uses a JSAutoNullableCompartment, and may be in a null
+ // compartment when the destructor is called. However, the JS engine
+ // requires us to be in a compartment when we fetch the pending exception.
+ // In this case, we enter the privileged junk scope and don't dispatch any
+ // error events.
+ JS::Rooted<JSObject*> errorGlobal(cx(), JS::CurrentGlobalOrNull(cx()));
+ if (!errorGlobal) {
+ if (mIsMainThread) {
+ errorGlobal = xpc::PrivilegedJunkScope();
+ } else {
+ errorGlobal = workers::GetCurrentThreadWorkerGlobal();
+ }
+ }
+ JSAutoCompartment ac(cx(), errorGlobal);
+ JS::Rooted<JS::Value> exn(cx());
+ js::ErrorReport jsReport(cx());
+ if (StealException(&exn) &&
+ jsReport.init(cx(), exn, js::ErrorReport::WithSideEffects)) {
+ if (mIsMainThread) {
+ RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+
+ RefPtr<nsGlobalWindow> win = xpc::WindowGlobalOrNull(errorGlobal);
+ if (!win) {
+ // We run addons in a separate privileged compartment, but they still
+ // expect to trigger the onerror handler of their associated DOM Window.
+ win = xpc::AddonWindowOrNull(errorGlobal);
+ }
+ nsPIDOMWindowInner* inner = win ? win->AsInner() : nullptr;
+ xpcReport->Init(jsReport.report(), jsReport.toStringResult().c_str(),
+ nsContentUtils::IsCallerChrome(),
+ inner ? inner->WindowID() : 0);
+ if (inner && jsReport.report()->errorNumber != JSMSG_OUT_OF_MEMORY) {
+ JS::RootingContext* rcx = JS::RootingContext::get(cx());
+ DispatchScriptErrorEvent(inner, rcx, xpcReport, exn);
+ } else {
+ JS::Rooted<JSObject*> stack(cx(),
+ xpc::FindExceptionStackForConsoleReport(inner, exn));
+ xpcReport->LogToConsoleWithStack(stack);
+ }
+ } else {
+ // On a worker, we just use the worker error reporting mechanism and don't
+ // bother with xpc::ErrorReport. This will ensure that all the right
+ // events (which are a lot more complicated than in the window case) get
+ // fired.
+ workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ MOZ_ASSERT(worker->GetJSContext() == cx());
+ // Before invoking ReportError, put the exception back on the context,
+ // because it may want to put it in its error events and has no other way
+ // to get hold of it. After we invoke ReportError, clear the exception on
+ // cx(), just in case ReportError didn't.
+ JS_SetPendingException(cx(), exn);
+ worker->ReportError(cx(), jsReport.toStringResult(), jsReport.report());
+ ClearException();
+ }
+ } else {
+ NS_WARNING("OOMed while acquiring uncaught exception from JSAPI");
+ ClearException();
+ }
+}
+
+bool
+AutoJSAPI::PeekException(JS::MutableHandle<JS::Value> aVal)
+{
+ MOZ_ASSERT_IF(mIsMainThread, IsStackTop());
+ MOZ_ASSERT(HasException());
+ MOZ_ASSERT(js::GetContextCompartment(cx()));
+ if (!JS_GetPendingException(cx(), aVal)) {
+ return false;
+ }
+ return true;
+}
+
+bool
+AutoJSAPI::StealException(JS::MutableHandle<JS::Value> aVal)
+{
+ if (!PeekException(aVal)) {
+ return false;
+ }
+ JS_ClearPendingException(cx());
+ return true;
+}
+
+#ifdef DEBUG
+bool
+AutoJSAPI::IsStackTop() const
+{
+ return ScriptSettingsStack::TopNonIncumbentScript() == this;
+}
+#endif // DEBUG
+
+AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
+ const char *aReason,
+ bool aIsMainThread)
+ : AutoJSAPI(aGlobalObject, aIsMainThread, eEntryScript)
+ , mWebIDLCallerPrincipal(nullptr)
+{
+ MOZ_ASSERT(aGlobalObject);
+
+ if (aIsMainThread && gRunToCompletionListeners > 0) {
+ mDocShellEntryMonitor.emplace(cx(), aReason);
+ }
+}
+
+AutoEntryScript::AutoEntryScript(JSObject* aObject,
+ const char *aReason,
+ bool aIsMainThread)
+ : AutoEntryScript(xpc::NativeGlobal(aObject), aReason, aIsMainThread)
+{
+}
+
+AutoEntryScript::~AutoEntryScript()
+{
+ // GC when we pop a script entry point. This is a useful heuristic that helps
+ // us out on certain (flawed) benchmarks like sunspider, because it lets us
+ // avoid GCing during the timing loop.
+ JS_MaybeGC(cx());
+}
+
+AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx,
+ const char* aReason)
+ : JS::dbg::AutoEntryMonitor(aCx)
+ , mReason(aReason)
+{
+}
+
+void
+AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction,
+ JSScript* aScript, JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause)
+{
+ JS::Rooted<JSFunction*> rootedFunction(aCx);
+ if (aFunction) {
+ rootedFunction = aFunction;
+ }
+ JS::Rooted<JSScript*> rootedScript(aCx);
+ if (aScript) {
+ rootedScript = aScript;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
+ if (!window || !window->GetDocShell() ||
+ !window->GetDocShell()->GetRecordProfileTimelineMarkers()) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell();
+ nsString filename;
+ uint32_t lineNumber = 0;
+
+ js::AutoStableStringChars functionName(aCx);
+ if (rootedFunction) {
+ JS::Rooted<JSString*> displayId(aCx, JS_GetFunctionDisplayId(rootedFunction));
+ if (displayId) {
+ if (!functionName.initTwoByte(aCx, displayId)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+ }
+ }
+
+ if (!rootedScript) {
+ rootedScript = JS_GetFunctionScript(aCx, rootedFunction);
+ }
+ if (rootedScript) {
+ filename = NS_ConvertUTF8toUTF16(JS_GetScriptFilename(rootedScript));
+ lineNumber = JS_GetScriptBaseLineNumber(aCx, rootedScript);
+ }
+
+ if (!filename.IsEmpty() || functionName.isTwoByte()) {
+ const char16_t* functionNameChars = functionName.isTwoByte() ?
+ functionName.twoByteChars() : nullptr;
+
+ docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason,
+ functionNameChars,
+ filename.BeginReading(),
+ lineNumber, aAsyncStack,
+ aAsyncCause);
+ }
+}
+
+void
+AutoEntryScript::DocshellEntryMonitor::Exit(JSContext* aCx)
+{
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
+ // Not really worth checking GetRecordProfileTimelineMarkers here.
+ if (window && window->GetDocShell()) {
+ nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell();
+ docShellForJSRunToCompletion->NotifyJSRunToCompletionStop();
+ }
+}
+
+AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
+ : ScriptSettingsStackEntry(aGlobalObject, eIncumbentScript)
+ , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())
+{
+ ScriptSettingsStack::Push(this);
+}
+
+AutoIncumbentScript::~AutoIncumbentScript()
+{
+ ScriptSettingsStack::Pop(this);
+}
+
+AutoNoJSAPI::AutoNoJSAPI()
+ : ScriptSettingsStackEntry(nullptr, eNoJSAPI)
+{
+ ScriptSettingsStack::Push(this);
+}
+
+AutoNoJSAPI::~AutoNoJSAPI()
+{
+ ScriptSettingsStack::Pop(this);
+}
+
+} // namespace dom
+
+AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+ : mCx(nullptr)
+{
+ JS::AutoSuppressGCAnalysis nogc;
+ MOZ_ASSERT(!mCx, "mCx should not be initialized!");
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+ if (dom::IsJSAPIActive()) {
+ mCx = dom::danger::GetJSContext();
+ } else {
+ mJSAPI.Init();
+ mCx = mJSAPI.cx();
+ }
+}
+
+AutoJSContext::operator JSContext*() const
+{
+ return mCx;
+}
+
+AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+ : AutoJSAPI()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+ DebugOnly<bool> ok = Init(xpc::UnprivilegedJunkScope());
+ MOZ_ASSERT(ok,
+ "This is quite odd. We should have crashed in the "
+ "xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() "
+ "returned null, and inited correctly otherwise!");
+}
+
+AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+ : AutoJSAPI()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+ Init();
+}
+
+void
+AutoSlowOperation::CheckForInterrupt()
+{
+ // JS_CheckForInterrupt expects us to be in a compartment.
+ JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope());
+ JS_CheckForInterrupt(cx());
+}
+
+} // namespace mozilla
diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h
new file mode 100644
index 000000000..05e62f55e
--- /dev/null
+++ b/dom/base/ScriptSettings.h
@@ -0,0 +1,465 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Utilities for managing the script settings object stack defined in webapps */
+
+#ifndef mozilla_dom_ScriptSettings_h
+#define mozilla_dom_ScriptSettings_h
+
+#include "MainThreadUtils.h"
+#include "nsIGlobalObject.h"
+#include "nsIPrincipal.h"
+
+#include "mozilla/Maybe.h"
+
+#include "jsapi.h"
+#include "js/Debug.h"
+
+class nsPIDOMWindowInner;
+class nsGlobalWindow;
+class nsIScriptContext;
+class nsIDocument;
+class nsIDocShell;
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * System-wide setup/teardown routines. Init and Destroy should be invoked
+ * once each, at startup and shutdown (respectively).
+ */
+void InitScriptSettings();
+void DestroyScriptSettings();
+bool ScriptSettingsInitialized();
+
+/*
+ * Static helpers in ScriptSettings which track the number of listeners
+ * of Javascript RunToCompletion events. These should be used by the code in
+ * nsDocShell::SetRecordProfileTimelineMarkers to indicate to script
+ * settings that script run-to-completion needs to be monitored.
+ * SHOULD BE CALLED ONLY BY MAIN THREAD.
+ */
+void UseEntryScriptProfiling();
+void UnuseEntryScriptProfiling();
+
+// To implement a web-compatible browser, it is often necessary to obtain the
+// global object that is "associated" with the currently-running code. This
+// process is made more complicated by the fact that, historically, different
+// algorithms have operated with different definitions of the "associated"
+// global.
+//
+// HTML5 formalizes this into two concepts: the "incumbent global" and the
+// "entry global". The incumbent global corresponds to the global of the
+// current script being executed, whereas the entry global corresponds to the
+// global of the script where the current JS execution began.
+//
+// There is also a potentially-distinct third global that is determined by the
+// current compartment. This roughly corresponds with the notion of Realms in
+// ECMAScript.
+//
+// Suppose some event triggers an event listener in window |A|, which invokes a
+// scripted function in window |B|, which invokes the |window.location.href|
+// setter in window |C|. The entry global would be |A|, the incumbent global
+// would be |B|, and the current compartment would be that of |C|.
+//
+// In general, it's best to use to use the most-closely-associated global
+// unless the spec says to do otherwise. In 95% of the cases, the global of
+// the current compartment (GetCurrentGlobal()) is the right thing. For
+// example, WebIDL constructors (new C.XMLHttpRequest()) are initialized with
+// the global of the current compartment (i.e. |C|).
+//
+// The incumbent global is very similar, but differs in a few edge cases. For
+// example, if window |B| does |C.location.href = "..."|, the incumbent global
+// used for the navigation algorithm is B, because no script from |C| was ever run.
+//
+// The entry global is used for various things like computing base URIs, mostly
+// for historical reasons.
+//
+// Note that all of these functions return bonafide global objects. This means
+// that, for Windows, they always return the inner.
+
+// Returns the global associated with the top-most Candidate Entry Point on
+// the Script Settings Stack. See the HTML spec. This may be null.
+nsIGlobalObject* GetEntryGlobal();
+
+// If the entry global is a window, returns its extant document. Otherwise,
+// returns null.
+nsIDocument* GetEntryDocument();
+
+// Returns the global associated with the top-most entry of the the Script
+// Settings Stack. See the HTML spec. This may be null.
+nsIGlobalObject* GetIncumbentGlobal();
+
+// Returns the global associated with the current compartment. This may be null.
+nsIGlobalObject* GetCurrentGlobal();
+
+// JS-implemented WebIDL presents an interesting situation with respect to the
+// subject principal. A regular C++-implemented API can simply examine the
+// compartment of the most-recently-executed script, and use that to infer the
+// responsible party. However, JS-implemented APIs are run with system
+// principal, and thus clobber the subject principal of the script that
+// invoked the API. So we have to do some extra work to keep track of this
+// information.
+//
+// We therefore implement the following behavior:
+// * Each Script Settings Object has an optional WebIDL Caller Principal field.
+// This defaults to null.
+// * When we push an Entry Point in preparation to run a JS-implemented WebIDL
+// callback, we grab the subject principal at the time of invocation, and
+// store that as the WebIDL Caller Principal.
+// * When non-null, callers can query this principal from script via an API on
+// Components.utils.
+nsIPrincipal* GetWebIDLCallerPrincipal();
+
+// This may be used by callers that know that their incumbent global is non-
+// null (i.e. they know there have been no System Caller pushes since the
+// inner-most script execution).
+inline JSObject& IncumbentJSGlobal()
+{
+ return *GetIncumbentGlobal()->GetGlobalJSObject();
+}
+
+// Returns whether JSAPI is active right now. If it is not, working with a
+// JSContext you grab from somewhere random is not OK and you should be doing
+// AutoJSAPI or AutoEntryScript to get yourself a properly set up JSContext.
+bool IsJSAPIActive();
+
+namespace danger {
+
+// Get the JSContext for this thread. This is in the "danger" namespace because
+// we generally want people using AutoJSAPI instead, unless they really know
+// what they're doing.
+JSContext* GetJSContext();
+
+} // namespace danger
+
+JS::RootingContext* RootingCx();
+
+class ScriptSettingsStack;
+class ScriptSettingsStackEntry {
+ friend class ScriptSettingsStack;
+
+public:
+ ~ScriptSettingsStackEntry();
+
+ bool NoJSAPI() const { return mType == eNoJSAPI; }
+ bool IsEntryCandidate() const {
+ return mType == eEntryScript || mType == eNoJSAPI;
+ }
+ bool IsIncumbentCandidate() { return mType != eJSAPI; }
+ bool IsIncumbentScript() { return mType == eIncumbentScript; }
+
+protected:
+ enum Type {
+ eEntryScript,
+ eIncumbentScript,
+ eJSAPI,
+ eNoJSAPI
+ };
+
+ ScriptSettingsStackEntry(nsIGlobalObject *aGlobal,
+ Type aEntryType);
+
+ nsCOMPtr<nsIGlobalObject> mGlobalObject;
+ Type mType;
+
+private:
+ ScriptSettingsStackEntry *mOlder;
+};
+
+/*
+ * For any interaction with JSAPI, an AutoJSAPI (or one of its subclasses)
+ * must be on the stack.
+ *
+ * This base class should be instantiated as-is when the caller wants to use
+ * JSAPI but doesn't expect to run script. The caller must then call one of its
+ * Init functions before being able to access the JSContext through cx().
+ * Its current duties are as-follows (see individual Init comments for details):
+ *
+ * * Grabbing an appropriate JSContext, and, on the main thread, pushing it onto
+ * the JSContext stack.
+ * * Entering an initial (possibly null) compartment, to ensure that the
+ * previously entered compartment for that JSContext is not used by mistake.
+ * * Reporting any exceptions left on the JSRuntime, unless the caller steals
+ * or silences them.
+ * * On main thread, entering a JSAutoRequest.
+ *
+ * Additionally, the following duties are planned, but not yet implemented:
+ *
+ * * De-poisoning the JSRuntime to allow manipulation of JSAPI. This requires
+ * implementing the poisoning first. For now, this de-poisoning
+ * effectively corresponds to having a non-null cx on the stack.
+ *
+ * In situations where the consumer expects to run script, AutoEntryScript
+ * should be used, which does additional manipulation of the script settings
+ * stack. In bug 991758, we'll add hard invariants to SpiderMonkey, such that
+ * any attempt to run script without an AutoEntryScript on the stack will
+ * fail. This prevents system code from accidentally triggering script
+ * execution at inopportune moments via surreptitious getters and proxies.
+ */
+class MOZ_STACK_CLASS AutoJSAPI : protected ScriptSettingsStackEntry {
+public:
+ // Trivial constructor. One of the Init functions must be called before
+ // accessing the JSContext through cx().
+ AutoJSAPI();
+
+ ~AutoJSAPI();
+
+ // This uses the SafeJSContext (or worker equivalent), and enters a null
+ // compartment, so that the consumer is forced to select a compartment to
+ // enter before manipulating objects.
+ //
+ // This variant will ensure that any errors reported by this AutoJSAPI as it
+ // comes off the stack will not fire error events or be associated with any
+ // particular web-visible global.
+ void Init();
+
+ // This uses the SafeJSContext (or worker equivalent), and enters the
+ // compartment of aGlobalObject.
+ // If aGlobalObject or its associated JS global are null then it returns
+ // false and use of cx() will cause an assertion.
+ //
+ // If aGlobalObject represents a web-visible global, errors reported by this
+ // AutoJSAPI as it comes off the stack will fire the relevant error events and
+ // show up in the corresponding web console.
+ MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject);
+
+ // This is a helper that grabs the native global associated with aObject and
+ // invokes the above Init() with that.
+ MOZ_MUST_USE bool Init(JSObject* aObject);
+
+ // Unsurprisingly, this uses aCx and enters the compartment of aGlobalObject.
+ // If aGlobalObject or its associated JS global are null then it returns
+ // false and use of cx() will cause an assertion.
+ // If aCx is null it will cause an assertion.
+ //
+ // If aGlobalObject represents a web-visible global, errors reported by this
+ // AutoJSAPI as it comes off the stack will fire the relevant error events and
+ // show up in the corresponding web console.
+ MOZ_MUST_USE bool Init(nsIGlobalObject* aGlobalObject, JSContext* aCx);
+
+ // Convenience functions to take an nsPIDOMWindow* or nsGlobalWindow*,
+ // when it is more easily available than an nsIGlobalObject.
+ MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow);
+ MOZ_MUST_USE bool Init(nsPIDOMWindowInner* aWindow, JSContext* aCx);
+
+ MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow);
+ MOZ_MUST_USE bool Init(nsGlobalWindow* aWindow, JSContext* aCx);
+
+ JSContext* cx() const {
+ MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
+ MOZ_ASSERT(IsStackTop());
+ return mCx;
+ }
+
+#ifdef DEBUG
+ bool IsStackTop() const;
+#endif
+
+ // If HasException, report it. Otherwise, a no-op.
+ void ReportException();
+
+ bool HasException() const {
+ MOZ_ASSERT(IsStackTop());
+ return JS_IsExceptionPending(cx());
+ };
+
+ // Transfers ownership of the current exception from the JS engine to the
+ // caller. Callers must ensure that HasException() is true, and that cx()
+ // is in a non-null compartment.
+ //
+ // Note that this fails if and only if we OOM while wrapping the exception
+ // into the current compartment.
+ MOZ_MUST_USE bool StealException(JS::MutableHandle<JS::Value> aVal);
+
+ // Peek the current exception from the JS engine, without stealing it.
+ // Callers must ensure that HasException() is true, and that cx() is in a
+ // non-null compartment.
+ //
+ // Note that this fails if and only if we OOM while wrapping the exception
+ // into the current compartment.
+ MOZ_MUST_USE bool PeekException(JS::MutableHandle<JS::Value> aVal);
+
+ void ClearException() {
+ MOZ_ASSERT(IsStackTop());
+ JS_ClearPendingException(cx());
+ }
+
+protected:
+ // Protected constructor for subclasses. This constructor initialises the
+ // AutoJSAPI, so Init must NOT be called on subclasses that use this.
+ AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
+
+private:
+ mozilla::Maybe<JSAutoRequest> mAutoRequest;
+ mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
+ JSContext *mCx;
+
+ // Whether we're mainthread or not; set when we're initialized.
+ bool mIsMainThread;
+ Maybe<JS::WarningReporter> mOldWarningReporter;
+
+ void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
+ JSContext* aCx, bool aIsMainThread);
+
+ AutoJSAPI(const AutoJSAPI&) = delete;
+ AutoJSAPI& operator= (const AutoJSAPI&) = delete;
+};
+
+/*
+ * A class that represents a new script entry point.
+ *
+ * |aReason| should be a statically-allocated C string naming the reason we're
+ * invoking JavaScript code: "setTimeout", "event", and so on. The devtools use
+ * these strings to label JS execution in timeline and profiling displays.
+ */
+class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI {
+public:
+ AutoEntryScript(nsIGlobalObject* aGlobalObject,
+ const char *aReason,
+ bool aIsMainThread = NS_IsMainThread());
+
+ AutoEntryScript(JSObject* aObject, // Any object from the relevant global
+ const char *aReason,
+ bool aIsMainThread = NS_IsMainThread());
+
+ ~AutoEntryScript();
+
+ void SetWebIDLCallerPrincipal(nsIPrincipal *aPrincipal) {
+ mWebIDLCallerPrincipal = aPrincipal;
+ }
+
+private:
+ // A subclass of AutoEntryMonitor that notifies the docshell.
+ class DocshellEntryMonitor final : public JS::dbg::AutoEntryMonitor
+ {
+ public:
+ DocshellEntryMonitor(JSContext* aCx, const char* aReason);
+
+ // Please note that |aAsyncCause| here is owned by the caller, and its
+ // lifetime must outlive the lifetime of the DocshellEntryMonitor object.
+ // In practice, |aAsyncCause| is identical to |aReason| passed into
+ // the AutoEntryScript constructor, so the lifetime requirements are
+ // trivially satisfied by |aReason| being a statically allocated string.
+ void Entry(JSContext* aCx, JSFunction* aFunction,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause) override
+ {
+ Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
+ }
+
+ void Entry(JSContext* aCx, JSScript* aScript,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause) override
+ {
+ Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
+ }
+
+ void Exit(JSContext* aCx) override;
+
+ private:
+ void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause);
+
+ const char* mReason;
+ };
+
+ // It's safe to make this a weak pointer, since it's the subject principal
+ // when we go on the stack, so can't go away until after we're gone. In
+ // particular, this is only used from the CallSetup constructor, and only in
+ // the aIsJSImplementedWebIDL case. And in that case, the subject principal
+ // is the principal of the callee function that is part of the CallArgs just a
+ // bit up the stack, and which will outlive us. So we know the principal
+ // can't go away until then either.
+ nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal;
+ friend nsIPrincipal* GetWebIDLCallerPrincipal();
+
+ Maybe<DocshellEntryMonitor> mDocShellEntryMonitor;
+};
+
+/*
+ * A class that can be used to force a particular incumbent script on the stack.
+ */
+class AutoIncumbentScript : protected ScriptSettingsStackEntry {
+public:
+ explicit AutoIncumbentScript(nsIGlobalObject* aGlobalObject);
+ ~AutoIncumbentScript();
+
+private:
+ JS::AutoHideScriptedCaller mCallerOverride;
+};
+
+/*
+ * A class to put the JS engine in an unusable state. The subject principal
+ * will become System, the information on the script settings stack is
+ * rendered inaccessible, and JSAPI may not be manipulated until the class is
+ * either popped or an AutoJSAPI instance is subsequently pushed.
+ *
+ * This class may not be instantiated if an exception is pending.
+ */
+class AutoNoJSAPI : protected ScriptSettingsStackEntry {
+public:
+ explicit AutoNoJSAPI();
+ ~AutoNoJSAPI();
+};
+
+} // namespace dom
+
+/**
+ * Use AutoJSContext when you need a JS context on the stack but don't have one
+ * passed as a parameter. AutoJSContext will take care of finding the most
+ * appropriate JS context and release it when leaving the stack.
+ */
+class MOZ_RAII AutoJSContext {
+public:
+ explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+ operator JSContext*() const;
+
+protected:
+ JSContext* mCx;
+ dom::AutoJSAPI mJSAPI;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+/**
+ * AutoSafeJSContext is similar to AutoJSContext but will only return the safe
+ * JS context. That means it will never call nsContentUtils::GetCurrentJSContext().
+ *
+ * Note - This is deprecated. Please use AutoJSAPI instead.
+ */
+class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI {
+public:
+ explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+ operator JSContext*() const
+ {
+ return cx();
+ }
+
+private:
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+/**
+ * Use AutoSlowOperation when native side calls many JS callbacks in a row
+ * and slow script dialog should be activated if too much time is spent going
+ * through those callbacks.
+ * AutoSlowOperation puts a JSAutoRequest on the stack so that we don't continue
+ * to reset the watchdog and CheckForInterrupt can be then used to check whether
+ * JS execution should be interrupted.
+ */
+class MOZ_RAII AutoSlowOperation : public dom::AutoJSAPI
+{
+public:
+ explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+ void CheckForInterrupt();
+private:
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+} // namespace mozilla
+
+#endif // mozilla_dom_ScriptSettings_h
diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp
new file mode 100644
index 000000000..9540754f7
--- /dev/null
+++ b/dom/base/ShadowRoot.cpp
@@ -0,0 +1,765 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ShadowRoot.h"
+#include "mozilla/dom/ShadowRootBinding.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "ChildIterator.h"
+#include "nsContentUtils.h"
+#include "nsDOMClassInfoID.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIStyleSheetLinkingElement.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLContentElement.h"
+#include "mozilla/dom/HTMLShadowElement.h"
+#include "nsXBLPrototypeBinding.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot,
+ DocumentFragment)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPoolHost)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOlderShadow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mYoungerShadow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAssociatedBinding)
+ for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done();
+ iter.Next()) {
+ iter.Get()->Traverse(&cb);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ShadowRoot,
+ DocumentFragment)
+ if (tmp->mPoolHost) {
+ tmp->mPoolHost->RemoveMutationObserver(tmp);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPoolHost)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mStyleSheetList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOlderShadow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mYoungerShadow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAssociatedBinding)
+ tmp->mIdentifierMap.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ShadowRoot)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)
+
+NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)
+NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment)
+
+ShadowRoot::ShadowRoot(nsIContent* aContent,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ nsXBLPrototypeBinding* aProtoBinding)
+ : DocumentFragment(aNodeInfo), mPoolHost(aContent),
+ mProtoBinding(aProtoBinding), mShadowElement(nullptr),
+ mInsertionPointChanged(false), mIsComposedDocParticipant(false)
+{
+ SetHost(aContent);
+
+ // 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);
+
+ DOMSlots()->mBindingParent = aContent;
+ DOMSlots()->mContainingShadow = this;
+
+ // Add the ShadowRoot as a mutation observer on the host to watch
+ // for mutations because the insertion points in this ShadowRoot
+ // may need to be updated when the host children are modified.
+ mPoolHost->AddMutationObserver(this);
+}
+
+ShadowRoot::~ShadowRoot()
+{
+ if (mPoolHost) {
+ // mPoolHost may have been unlinked or a new ShadowRoot may have been
+ // creating, making this one obsolete.
+ mPoolHost->RemoveMutationObserver(this);
+ }
+
+ UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ // nsINode destructor expects mSubtreeRoot == this.
+ SetSubtreeRootPointer(this);
+
+ SetHost(nullptr);
+}
+
+JSObject*
+ShadowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::ShadowRootBinding::Wrap(aCx, this, aGivenProto);
+}
+
+ShadowRoot*
+ShadowRoot::FromNode(nsINode* aNode)
+{
+ if (aNode->IsInShadowTree() && !aNode->GetParentNode()) {
+ MOZ_ASSERT(aNode->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE,
+ "ShadowRoot is a document fragment.");
+ return static_cast<ShadowRoot*>(aNode);
+ }
+
+ return nullptr;
+}
+
+void
+ShadowRoot::StyleSheetChanged()
+{
+ mProtoBinding->FlushSkinSheets();
+
+ nsIPresShell* shell = OwnerDoc()->GetShell();
+ if (shell) {
+ OwnerDoc()->BeginUpdate(UPDATE_STYLE);
+ shell->RecordShadowStyleChange(this);
+ OwnerDoc()->EndUpdate(UPDATE_STYLE);
+ }
+}
+
+void
+ShadowRoot::InsertSheet(StyleSheet* aSheet,
+ nsIContent* aLinkingContent)
+{
+ nsCOMPtr<nsIStyleSheetLinkingElement>
+ linkingElement = do_QueryInterface(aLinkingContent);
+ MOZ_ASSERT(linkingElement, "The only styles in a ShadowRoot should come "
+ "from <style>.");
+
+ linkingElement->SetStyleSheet(aSheet); // This sets the ownerNode on the sheet
+
+ // Find the correct position to insert into the style sheet list (must
+ // be in tree order).
+ for (size_t i = 0; i <= mProtoBinding->SheetCount(); i++) {
+ if (i == mProtoBinding->SheetCount()) {
+ mProtoBinding->AppendStyleSheet(aSheet);
+ break;
+ }
+
+ nsINode* sheetOwningNode = mProtoBinding->StyleSheetAt(i)->GetOwnerNode();
+ if (nsContentUtils::PositionIsBefore(aLinkingContent, sheetOwningNode)) {
+ mProtoBinding->InsertStyleSheetAt(i, aSheet);
+ break;
+ }
+ }
+
+ if (aSheet->IsApplicable()) {
+ StyleSheetChanged();
+ }
+}
+
+void
+ShadowRoot::RemoveSheet(StyleSheet* aSheet)
+{
+ mProtoBinding->RemoveStyleSheet(aSheet);
+
+ if (aSheet->IsApplicable()) {
+ StyleSheetChanged();
+ }
+}
+
+Element*
+ShadowRoot::GetElementById(const nsAString& aElementId)
+{
+ nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aElementId);
+ return entry ? entry->GetIdElement() : nullptr;
+}
+
+already_AddRefed<nsContentList>
+ShadowRoot::GetElementsByTagName(const nsAString& aTagName)
+{
+ return NS_GetContentList(this, kNameSpaceID_Unknown, aTagName);
+}
+
+already_AddRefed<nsContentList>
+ShadowRoot::GetElementsByTagNameNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName)
+{
+ int32_t nameSpaceId = kNameSpaceID_Wildcard;
+
+ if (!aNamespaceURI.EqualsLiteral("*")) {
+ nsresult rv =
+ nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
+ nameSpaceId);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+
+ NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!");
+
+ return NS_GetContentList(this, nameSpaceId, aLocalName);
+}
+
+void
+ShadowRoot::AddToIdTable(Element* aElement, nsIAtom* aId)
+{
+ nsIdentifierMapEntry *entry =
+ mIdentifierMap.PutEntry(nsDependentAtomString(aId));
+ if (entry) {
+ entry->AddIdElement(aElement);
+ }
+}
+
+void
+ShadowRoot::RemoveFromIdTable(Element* aElement, nsIAtom* aId)
+{
+ nsIdentifierMapEntry *entry =
+ mIdentifierMap.GetEntry(nsDependentAtomString(aId));
+ if (entry) {
+ entry->RemoveIdElement(aElement);
+ if (entry->IsEmpty()) {
+ mIdentifierMap.RemoveEntry(entry);
+ }
+ }
+}
+
+already_AddRefed<nsContentList>
+ShadowRoot::GetElementsByClassName(const nsAString& aClasses)
+{
+ return nsContentUtils::GetElementsByClassName(this, aClasses);
+}
+
+void
+ShadowRoot::AddInsertionPoint(HTMLContentElement* aInsertionPoint)
+{
+ TreeOrderComparator comparator;
+ mInsertionPoints.InsertElementSorted(aInsertionPoint, comparator);
+}
+
+void
+ShadowRoot::RemoveInsertionPoint(HTMLContentElement* aInsertionPoint)
+{
+ mInsertionPoints.RemoveElement(aInsertionPoint);
+}
+
+void
+ShadowRoot::SetYoungerShadow(ShadowRoot* aYoungerShadow)
+{
+ mYoungerShadow = aYoungerShadow;
+ mYoungerShadow->mOlderShadow = this;
+
+ ChangePoolHost(mYoungerShadow->GetShadowElement());
+}
+
+void
+ShadowRoot::RemoveDestInsertionPoint(nsIContent* aInsertionPoint,
+ nsTArray<nsIContent*>& aDestInsertionPoints)
+{
+ // Remove the insertion point from the destination insertion points.
+ // Also remove all succeeding insertion points because it is no longer
+ // possible for the content to be distributed into deeper node trees.
+ int32_t index = aDestInsertionPoints.IndexOf(aInsertionPoint);
+
+ // It's possible that we already removed the insertion point while processing
+ // other insertion point removals.
+ if (index >= 0) {
+ aDestInsertionPoints.SetLength(index);
+ }
+}
+
+void
+ShadowRoot::DistributeSingleNode(nsIContent* aContent)
+{
+ // Find the insertion point to which the content belongs.
+ HTMLContentElement* insertionPoint = nullptr;
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
+ if (mInsertionPoints[i]->Match(aContent)) {
+ if (mInsertionPoints[i]->MatchedNodes().Contains(aContent)) {
+ // Node is already matched into the insertion point. We are done.
+ return;
+ }
+
+ // Matching may cause the insertion point to drop fallback content.
+ if (mInsertionPoints[i]->MatchedNodes().IsEmpty() &&
+ static_cast<nsINode*>(mInsertionPoints[i])->GetFirstChild()) {
+ // This match will cause the insertion point to drop all fallback
+ // content and used matched nodes instead. Give up on the optimization
+ // and just distribute all nodes.
+ DistributeAllNodes();
+ return;
+ }
+ insertionPoint = mInsertionPoints[i];
+ break;
+ }
+ }
+
+ // Find the index into the insertion point.
+ if (insertionPoint) {
+ nsCOMArray<nsIContent>& matchedNodes = insertionPoint->MatchedNodes();
+ // Find the appropriate position in the matched node list for the
+ // newly distributed content.
+ bool isIndexFound = false;
+ MOZ_ASSERT(mPoolHost, "Where did the content come from if there is no pool host?");
+ ExplicitChildIterator childIterator(mPoolHost);
+ for (uint32_t i = 0; i < matchedNodes.Length(); i++) {
+ // Seek through the host's explicit children until the inserted content
+ // is found or when the current matched node is reached.
+ if (childIterator.Seek(aContent, matchedNodes[i])) {
+ // aContent was found before the current matched node.
+ insertionPoint->InsertMatchedNode(i, aContent);
+ isIndexFound = true;
+ break;
+ }
+ }
+
+ if (!isIndexFound) {
+ // We have still not found an index in the insertion point,
+ // thus it must be at the end.
+ MOZ_ASSERT(childIterator.Seek(aContent, nullptr),
+ "Trying to match a node that is not a candidate to be matched");
+ insertionPoint->AppendMatchedNode(aContent);
+ }
+
+ // Handle the case where the parent of the insertion point is a ShadowRoot
+ // that is projected into the younger ShadowRoot's shadow insertion point.
+ // The node distributed into the insertion point must be reprojected
+ // to the shadow insertion point.
+ if (insertionPoint->GetParent() == this &&
+ mYoungerShadow && mYoungerShadow->GetShadowElement()) {
+ mYoungerShadow->GetShadowElement()->DistributeSingleNode(aContent);
+ }
+
+ // Handle the case where the parent of the insertion point has a ShadowRoot.
+ // The node distributed into the insertion point must be reprojected to the
+ // insertion points of the parent's ShadowRoot.
+ ShadowRoot* parentShadow = insertionPoint->GetParent()->GetShadowRoot();
+ if (parentShadow) {
+ parentShadow->DistributeSingleNode(aContent);
+ }
+
+ // Handle the case where the parent of the insertion point is the <shadow>
+ // element. The node distributed into the insertion point must be reprojected
+ // into the older ShadowRoot's insertion points.
+ if (mShadowElement && mShadowElement == insertionPoint->GetParent()) {
+ ShadowRoot* olderShadow = mShadowElement->GetOlderShadowRoot();
+ if (olderShadow) {
+ olderShadow->DistributeSingleNode(aContent);
+ }
+ }
+ }
+}
+
+void
+ShadowRoot::RemoveDistributedNode(nsIContent* aContent)
+{
+ // Find insertion point containing the content and remove the node.
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
+ if (mInsertionPoints[i]->MatchedNodes().Contains(aContent)) {
+ // Removing the matched node may cause the insertion point to use
+ // fallback content.
+ if (mInsertionPoints[i]->MatchedNodes().Length() == 1 &&
+ static_cast<nsINode*>(mInsertionPoints[i])->GetFirstChild()) {
+ // Removing the matched node will cause fallback content to be
+ // used instead. Give up optimization and distribute all nodes.
+ DistributeAllNodes();
+ return;
+ }
+
+ mInsertionPoints[i]->RemoveMatchedNode(aContent);
+
+ // Handle the case where the parent of the insertion point is a ShadowRoot
+ // that is projected into the younger ShadowRoot's shadow insertion point.
+ // The removed node needs to be removed from the shadow insertion point.
+ if (mInsertionPoints[i]->GetParent() == this) {
+ if (mYoungerShadow && mYoungerShadow->GetShadowElement()) {
+ mYoungerShadow->GetShadowElement()->RemoveDistributedNode(aContent);
+ }
+ }
+
+ // Handle the case where the parent of the insertion point has a ShadowRoot.
+ // The removed node needs to be removed from the insertion points of the
+ // parent's ShadowRoot.
+ ShadowRoot* parentShadow = mInsertionPoints[i]->GetParent()->GetShadowRoot();
+ if (parentShadow) {
+ parentShadow->RemoveDistributedNode(aContent);
+ }
+
+ // Handle the case where the parent of the insertion point is the <shadow>
+ // element. The removed node must be removed from the older ShadowRoot's
+ // insertion points.
+ if (mShadowElement && mShadowElement == mInsertionPoints[i]->GetParent()) {
+ ShadowRoot* olderShadow = mShadowElement->GetOlderShadowRoot();
+ if (olderShadow) {
+ olderShadow->RemoveDistributedNode(aContent);
+ }
+ }
+
+ break;
+ }
+ }
+}
+
+void
+ShadowRoot::DistributeAllNodes()
+{
+ // Create node pool.
+ nsTArray<nsIContent*> nodePool;
+
+ // Make sure there is a pool host, an older shadow may not have
+ // one if the younger shadow does not have a <shadow> element.
+ if (mPoolHost) {
+ ExplicitChildIterator childIterator(mPoolHost);
+ for (nsIContent* content = childIterator.GetNextChild();
+ content;
+ content = childIterator.GetNextChild()) {
+ nodePool.AppendElement(content);
+ }
+ }
+
+ nsTArray<ShadowRoot*> shadowsToUpdate;
+
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
+ mInsertionPoints[i]->ClearMatchedNodes();
+ // Assign matching nodes from node pool.
+ for (uint32_t j = 0; j < nodePool.Length(); j++) {
+ if (mInsertionPoints[i]->Match(nodePool[j])) {
+ mInsertionPoints[i]->AppendMatchedNode(nodePool[j]);
+ nodePool.RemoveElementAt(j--);
+ }
+ }
+
+ // Keep track of instances where the content insertion point is distributed
+ // (parent of insertion point has a ShadowRoot).
+ nsIContent* insertionParent = mInsertionPoints[i]->GetParent();
+ MOZ_ASSERT(insertionParent, "The only way for an insertion point to be in the"
+ "mInsertionPoints array is to be a descendant of a"
+ "ShadowRoot, in which case, it should have a parent");
+
+ // If the parent of the insertion point has a ShadowRoot, the nodes distributed
+ // to the insertion point must be reprojected to the insertion points of the
+ // parent's ShadowRoot.
+ ShadowRoot* parentShadow = insertionParent->GetShadowRoot();
+ if (parentShadow && !shadowsToUpdate.Contains(parentShadow)) {
+ shadowsToUpdate.AppendElement(parentShadow);
+ }
+ }
+
+ // If there is a shadow insertion point in this ShadowRoot, the children
+ // of the shadow insertion point needs to be distributed into the insertion
+ // points of the older ShadowRoot.
+ if (mShadowElement && mOlderShadow) {
+ mOlderShadow->DistributeAllNodes();
+ }
+
+ // If there is a younger ShadowRoot with a shadow insertion point,
+ // then the children of this ShadowRoot needs to be distributed to
+ // the younger ShadowRoot's shadow insertion point.
+ if (mYoungerShadow && mYoungerShadow->GetShadowElement()) {
+ mYoungerShadow->GetShadowElement()->DistributeAllNodes();
+ }
+
+ for (uint32_t i = 0; i < shadowsToUpdate.Length(); i++) {
+ shadowsToUpdate[i]->DistributeAllNodes();
+ }
+}
+
+void
+ShadowRoot::GetInnerHTML(nsAString& aInnerHTML)
+{
+ GetMarkup(false, aInnerHTML);
+}
+
+void
+ShadowRoot::SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError)
+{
+ SetInnerHTMLInternal(aInnerHTML, aError);
+}
+
+Element*
+ShadowRoot::Host()
+{
+ nsIContent* host = GetHost();
+ MOZ_ASSERT(host && host->IsElement(),
+ "ShadowRoot host should always be an element, "
+ "how else did we create this ShadowRoot?");
+ return host->AsElement();
+}
+
+bool
+ShadowRoot::ApplyAuthorStyles()
+{
+ return mProtoBinding->InheritsStyle();
+}
+
+void
+ShadowRoot::SetApplyAuthorStyles(bool aApplyAuthorStyles)
+{
+ mProtoBinding->SetInheritsStyle(aApplyAuthorStyles);
+
+ nsIPresShell* shell = OwnerDoc()->GetShell();
+ if (shell) {
+ OwnerDoc()->BeginUpdate(UPDATE_STYLE);
+ shell->RecordShadowStyleChange(this);
+ OwnerDoc()->EndUpdate(UPDATE_STYLE);
+ }
+}
+
+StyleSheetList*
+ShadowRoot::StyleSheets()
+{
+ if (!mStyleSheetList) {
+ mStyleSheetList = new ShadowRootStyleSheetList(this);
+ }
+
+ return mStyleSheetList;
+}
+
+void
+ShadowRoot::SetShadowElement(HTMLShadowElement* aShadowElement)
+{
+ // If there is already a shadow element point, remove
+ // the projected shadow because it is no longer an insertion
+ // point.
+ if (mShadowElement) {
+ mShadowElement->SetProjectedShadow(nullptr);
+ }
+
+ if (mOlderShadow) {
+ // Nodes for distribution will come from the new shadow element.
+ mOlderShadow->ChangePoolHost(aShadowElement);
+ }
+
+ // Set the new shadow element to project the older ShadowRoot because
+ // it is the current shadow insertion point.
+ mShadowElement = aShadowElement;
+ if (mShadowElement) {
+ mShadowElement->SetProjectedShadow(mOlderShadow);
+ }
+}
+
+void
+ShadowRoot::ChangePoolHost(nsIContent* aNewHost)
+{
+ if (mPoolHost) {
+ mPoolHost->RemoveMutationObserver(this);
+ }
+
+ // Clear the nodes matched to content insertion points
+ // because it is no longer relevant.
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
+ mInsertionPoints[i]->ClearMatchedNodes();
+ }
+
+ mPoolHost = aNewHost;
+ if (mPoolHost) {
+ mPoolHost->AddMutationObserver(this);
+ }
+}
+
+bool
+ShadowRoot::IsShadowInsertionPoint(nsIContent* aContent)
+{
+ if (!aContent) {
+ return false;
+ }
+
+ HTMLShadowElement* shadowElem = HTMLShadowElement::FromContent(aContent);
+ return shadowElem && shadowElem->IsInsertionPoint();
+}
+
+/**
+ * Returns whether the web components pool population algorithm
+ * on the host would contain |aContent|. This function ignores
+ * insertion points in the pool, thus should only be used to
+ * test nodes that have not yet been distributed.
+ */
+bool
+ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer,
+ nsIContent* aHost)
+{
+ if (nsContentUtils::IsContentInsertionPoint(aContent) ||
+ IsShadowInsertionPoint(aContent)) {
+ // Insertion points never end up in the pool.
+ return false;
+ }
+
+ if (aContainer == aHost &&
+ nsContentUtils::IsInSameAnonymousTree(aContainer, aContent)) {
+ // Children of the host will end up in the pool. We check to ensure
+ // that the content is in the same anonymous tree as the container
+ // because anonymous content may report its container as the host
+ // but it may not be in the host's child list.
+ return true;
+ }
+
+ if (aContainer) {
+ // Fallback content will end up in pool if its parent is a child of the host.
+ HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
+ return content && content->IsInsertionPoint() &&
+ content->MatchedNodes().IsEmpty() &&
+ aContainer->GetParentNode() == aHost;
+ }
+
+ return false;
+}
+
+void
+ShadowRoot::AttributeChanged(nsIDocument* aDocument,
+ Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ if (!IsPooledNode(aElement, aElement->GetParent(), mPoolHost)) {
+ return;
+ }
+
+ // Attributes may change insertion point matching, find its new distribution.
+ RemoveDistributedNode(aElement);
+ DistributeSingleNode(aElement);
+}
+
+void
+ShadowRoot::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ if (mInsertionPointChanged) {
+ DistributeAllNodes();
+ mInsertionPointChanged = false;
+ return;
+ }
+
+ // Watch for new nodes added to the pool because the node
+ // may need to be added to an insertion point.
+ nsIContent* currentChild = aFirstNewContent;
+ while (currentChild) {
+ // Add insertion point to destination insertion points of fallback content.
+ if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
+ HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
+ if (content->MatchedNodes().IsEmpty()) {
+ currentChild->DestInsertionPoints().AppendElement(aContainer);
+ }
+ }
+
+ if (IsPooledNode(currentChild, aContainer, mPoolHost)) {
+ DistributeSingleNode(currentChild);
+ }
+
+ currentChild = currentChild->GetNextSibling();
+ }
+}
+
+void
+ShadowRoot::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ if (mInsertionPointChanged) {
+ DistributeAllNodes();
+ mInsertionPointChanged = false;
+ return;
+ }
+
+ // Watch for new nodes added to the pool because the node
+ // may need to be added to an insertion point.
+ if (IsPooledNode(aChild, aContainer, mPoolHost)) {
+ // Add insertion point to destination insertion points of fallback content.
+ if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
+ HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
+ if (content->MatchedNodes().IsEmpty()) {
+ aChild->DestInsertionPoints().AppendElement(aContainer);
+ }
+ }
+
+ DistributeSingleNode(aChild);
+ }
+}
+
+void
+ShadowRoot::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ if (mInsertionPointChanged) {
+ DistributeAllNodes();
+ mInsertionPointChanged = false;
+ return;
+ }
+
+ // Clear destination insertion points for removed
+ // fallback content.
+ if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
+ HTMLContentElement* content = HTMLContentElement::FromContent(aContainer);
+ if (content->MatchedNodes().IsEmpty()) {
+ aChild->DestInsertionPoints().Clear();
+ }
+ }
+
+ // Watch for node that is removed from the pool because
+ // it may need to be removed from an insertion point.
+ if (IsPooledNode(aChild, aContainer, mPoolHost)) {
+ RemoveDistributedNode(aChild);
+ }
+}
+
+nsresult
+ShadowRoot::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ *aResult = nullptr;
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+}
+
+void
+ShadowRoot::DestroyContent()
+{
+ if (mOlderShadow) {
+ mOlderShadow->DestroyContent();
+ }
+ DocumentFragment::DestroyContent();
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ShadowRootStyleSheetList, StyleSheetList,
+ mShadowRoot)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ShadowRootStyleSheetList)
+NS_INTERFACE_MAP_END_INHERITING(StyleSheetList)
+
+NS_IMPL_ADDREF_INHERITED(ShadowRootStyleSheetList, StyleSheetList)
+NS_IMPL_RELEASE_INHERITED(ShadowRootStyleSheetList, StyleSheetList)
+
+ShadowRootStyleSheetList::ShadowRootStyleSheetList(ShadowRoot* aShadowRoot)
+ : mShadowRoot(aShadowRoot)
+{
+ MOZ_COUNT_CTOR(ShadowRootStyleSheetList);
+}
+
+ShadowRootStyleSheetList::~ShadowRootStyleSheetList()
+{
+ MOZ_COUNT_DTOR(ShadowRootStyleSheetList);
+}
+
+StyleSheet*
+ShadowRootStyleSheetList::IndexedGetter(uint32_t aIndex, bool& aFound)
+{
+ aFound = aIndex < mShadowRoot->mProtoBinding->SheetCount();
+ if (!aFound) {
+ return nullptr;
+ }
+ return mShadowRoot->mProtoBinding->StyleSheetAt(aIndex);
+}
+
+uint32_t
+ShadowRootStyleSheetList::Length()
+{
+ return mShadowRoot->mProtoBinding->SheetCount();
+}
+
diff --git a/dom/base/ShadowRoot.h b/dom/base/ShadowRoot.h
new file mode 100644
index 000000000..f84078134
--- /dev/null
+++ b/dom/base/ShadowRoot.h
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/dom/DocumentFragment.h"
+#include "mozilla/dom/StyleSheetList.h"
+#include "mozilla/StyleSheet.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIContentInlines.h"
+#include "nsTHashtable.h"
+#include "nsDocument.h"
+
+class nsIAtom;
+class nsIContent;
+class nsXBLPrototypeBinding;
+
+namespace mozilla {
+namespace dom {
+
+class Element;
+class HTMLContentElement;
+class HTMLShadowElement;
+class ShadowRootStyleSheetList;
+
+class ShadowRoot final : public DocumentFragment,
+ public nsStubMutationObserver
+{
+ friend class ShadowRootStyleSheetList;
+public:
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ShadowRoot,
+ DocumentFragment)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ ShadowRoot(nsIContent* aContent, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ nsXBLPrototypeBinding* aProtoBinding);
+
+ void AddToIdTable(Element* aElement, nsIAtom* aId);
+ void RemoveFromIdTable(Element* aElement, nsIAtom* aId);
+ void InsertSheet(StyleSheet* aSheet, nsIContent* aLinkingContent);
+ void RemoveSheet(StyleSheet* aSheet);
+ bool ApplyAuthorStyles();
+ void SetApplyAuthorStyles(bool aApplyAuthorStyles);
+ StyleSheetList* StyleSheets();
+ HTMLShadowElement* GetShadowElement() { return mShadowElement; }
+
+ /**
+ * Sets the current shadow insertion point where the older
+ * ShadowRoot will be projected.
+ */
+ void SetShadowElement(HTMLShadowElement* aShadowElement);
+
+ /**
+ * Change the node that populates the distribution pool with
+ * its children. This is distinct from the ShadowRoot host described
+ * in the specifications. The ShadowRoot host is the element
+ * which created this ShadowRoot and does not change. The pool host
+ * is the same as the ShadowRoot host if this is the youngest
+ * ShadowRoot. If this is an older ShadowRoot, the pool host is
+ * the <shadow> element in the younger ShadowRoot (if it exists).
+ */
+ void ChangePoolHost(nsIContent* aNewHost);
+
+ /**
+ * Distributes a single explicit child of the pool host to the content
+ * insertion points in this ShadowRoot.
+ */
+ void DistributeSingleNode(nsIContent* aContent);
+
+ /**
+ * Removes a single explicit child of the pool host from the content
+ * insertion points in this ShadowRoot.
+ */
+ void RemoveDistributedNode(nsIContent* aContent);
+
+ /**
+ * Distributes all the explicit children of the pool host to the content
+ * insertion points in this ShadowRoot.
+ */
+ void DistributeAllNodes();
+
+ void AddInsertionPoint(HTMLContentElement* aInsertionPoint);
+ void RemoveInsertionPoint(HTMLContentElement* aInsertionPoint);
+
+ void SetYoungerShadow(ShadowRoot* aYoungerShadow);
+ ShadowRoot* GetYoungerShadowRoot() { return mYoungerShadow; }
+ void SetInsertionPointChanged() { mInsertionPointChanged = true; }
+
+ void SetAssociatedBinding(nsXBLBinding* aBinding) { mAssociatedBinding = aBinding; }
+
+ nsISupports* GetParentObject() const { return mPoolHost; }
+
+ nsIContent* GetPoolHost() { return mPoolHost; }
+ nsTArray<HTMLShadowElement*>& ShadowDescendants() { return mShadowDescendants; }
+
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ static bool IsPooledNode(nsIContent* aChild, nsIContent* aContainer,
+ nsIContent* aHost);
+ static ShadowRoot* FromNode(nsINode* aNode);
+ static bool IsShadowInsertionPoint(nsIContent* aContent);
+
+ static void RemoveDestInsertionPoint(nsIContent* aInsertionPoint,
+ nsTArray<nsIContent*>& aDestInsertionPoints);
+
+ // WebIDL methods.
+ Element* GetElementById(const nsAString& aElementId);
+ already_AddRefed<nsContentList>
+ GetElementsByTagName(const nsAString& aNamespaceURI);
+ already_AddRefed<nsContentList>
+ GetElementsByTagNameNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName);
+ already_AddRefed<nsContentList>
+ GetElementsByClassName(const nsAString& aClasses);
+ void GetInnerHTML(nsAString& aInnerHTML);
+ void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
+ Element* Host();
+ ShadowRoot* GetOlderShadowRoot() { return mOlderShadow; }
+ void StyleSheetChanged();
+
+ bool IsComposedDocParticipant() { return mIsComposedDocParticipant; }
+ void SetIsComposedDocParticipant(bool aIsComposedDocParticipant)
+ {
+ mIsComposedDocParticipant = aIsComposedDocParticipant;
+ }
+
+ virtual void DestroyContent() override;
+protected:
+ virtual ~ShadowRoot();
+
+ // The pool host is the parent of the nodes that will be distributed
+ // into the insertion points in this ShadowRoot. See |ChangeShadowRoot|.
+ nsCOMPtr<nsIContent> mPoolHost;
+
+ // An array of content insertion points that are a descendant of the ShadowRoot
+ // sorted in tree order. Insertion points are responsible for notifying
+ // the ShadowRoot when they are removed or added as a descendant. The insertion
+ // points are kept alive by the parent node, thus weak references are held
+ // by the array.
+ nsTArray<HTMLContentElement*> mInsertionPoints;
+
+ // An array of the <shadow> elements that are descendant of the ShadowRoot
+ // sorted in tree order. Only the first may be a shadow insertion point.
+ nsTArray<HTMLShadowElement*> mShadowDescendants;
+
+ nsTHashtable<nsIdentifierMapEntry> mIdentifierMap;
+ nsXBLPrototypeBinding* mProtoBinding;
+
+ // It is necessary to hold a reference to the associated nsXBLBinding
+ // because the binding holds a reference on the nsXBLDocumentInfo that
+ // owns |mProtoBinding|.
+ RefPtr<nsXBLBinding> mAssociatedBinding;
+
+ RefPtr<ShadowRootStyleSheetList> mStyleSheetList;
+
+ // The current shadow insertion point of this ShadowRoot.
+ HTMLShadowElement* mShadowElement;
+
+ // The ShadowRoot that was created by the host element before
+ // this ShadowRoot was created.
+ RefPtr<ShadowRoot> mOlderShadow;
+
+ // The ShadowRoot that was created by the host element after
+ // this ShadowRoot was created.
+ RefPtr<ShadowRoot> mYoungerShadow;
+
+ // A boolean that indicates that an insertion point was added or removed
+ // from this ShadowRoot and that the nodes need to be redistributed into
+ // the insertion points. After this flag is set, nodes will be distributed
+ // on the next mutation event.
+ bool mInsertionPointChanged;
+
+ // Flag to indicate whether the descendants of this shadow root are part of the
+ // composed document. Ideally, we would use a node flag on nodes to
+ // mark whether it is in the composed document, but we have run out of flags
+ // so instead we track it here.
+ bool mIsComposedDocParticipant;
+
+ nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+};
+
+class ShadowRootStyleSheetList : public StyleSheetList
+{
+public:
+ explicit ShadowRootStyleSheetList(ShadowRoot* aShadowRoot);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ShadowRootStyleSheetList, StyleSheetList)
+
+ virtual nsINode* GetParentObject() const override
+ {
+ return mShadowRoot;
+ }
+
+ uint32_t Length() override;
+ StyleSheet* IndexedGetter(uint32_t aIndex, bool& aFound) override;
+
+protected:
+ virtual ~ShadowRootStyleSheetList();
+
+ RefPtr<ShadowRoot> mShadowRoot;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_shadowroot_h__
+
diff --git a/dom/base/SiteSpecificUserAgent.js b/dom/base/SiteSpecificUserAgent.js
new file mode 100644
index 000000000..7e1bf44f8
--- /dev/null
+++ b/dom/base/SiteSpecificUserAgent.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const MAX_CACHE_SIZE = 250;
+const PREF_UPDATE = "general.useragent.updates.";
+const PREF_OVERRIDE = "general.useragent.override.";
+const XPCOM_SHUTDOWN = "xpcom-shutdown";
+const HTTP_PROTO_HANDLER = Cc["@mozilla.org/network/protocol;1?name=http"]
+ .getService(Ci.nsIHttpProtocolHandler);
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsISyncMessageSender");
+
+function SiteSpecificUserAgent() {
+ this.inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
+ if (this.inParent) {
+ Cu.import("resource://gre/modules/UserAgentOverrides.jsm");
+ } else {
+ Cu.import("resource://gre/modules/Services.jsm");
+ Services.prefs.addObserver(PREF_OVERRIDE, this, false);
+ Services.prefs.addObserver(PREF_UPDATE, this, false);
+ Services.obs.addObserver(this, XPCOM_SHUTDOWN, false);
+ this.userAgentCache = new Map;
+ }
+}
+
+SiteSpecificUserAgent.prototype = {
+ getUserAgentForURIAndWindow: function ssua_getUserAgentForURIAndWindow(aURI, aWindow) {
+ if (this.inParent) {
+ return UserAgentOverrides.getOverrideForURI(aURI) || HTTP_PROTO_HANDLER.userAgent;
+ }
+
+ let host = aURI.asciiHost;
+ let cachedResult = this.userAgentCache.get(host);
+ if (cachedResult) {
+ return cachedResult;
+ }
+
+ let data = { uri: aURI.spec };
+ let result = cpmm.sendRpcMessage("Useragent:GetOverride", data)[0] || HTTP_PROTO_HANDLER.userAgent;
+
+ if (this.userAgentCache.size >= MAX_CACHE_SIZE) {
+ this.userAgentCache.clear();
+ }
+
+ this.userAgentCache.set(host, result);
+ return result;
+ },
+
+ invalidateCache: function() {
+ this.userAgentCache.clear();
+ },
+
+ clean: function() {
+ this.userAgentCache.clear();
+ if (!this.inParent) {
+ Services.obs.removeObserver(this, XPCOM_SHUTDOWN);
+ Services.prefs.removeObserver(PREF_OVERRIDE, this);
+ Services.prefs.removeObserver(PREF_UPDATE, this);
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ this.invalidateCache();
+ break;
+ case XPCOM_SHUTDOWN:
+ this.clean();
+ break;
+ }
+ },
+
+ classID: Components.ID("{506c680f-3d1c-4954-b351-2c80afbc37d3}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISiteSpecificUserAgent])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SiteSpecificUserAgent]);
diff --git a/dom/base/SiteSpecificUserAgent.manifest b/dom/base/SiteSpecificUserAgent.manifest
new file mode 100644
index 000000000..30820ccef
--- /dev/null
+++ b/dom/base/SiteSpecificUserAgent.manifest
@@ -0,0 +1,2 @@
+component {506c680f-3d1c-4954-b351-2c80afbc37d3} SiteSpecificUserAgent.js
+contract @mozilla.org/dom/site-specific-user-agent;1 {506c680f-3d1c-4954-b351-2c80afbc37d3}
diff --git a/dom/base/SlowScriptDebug.js b/dom/base/SlowScriptDebug.js
new file mode 100644
index 000000000..296889d7b
--- /dev/null
+++ b/dom/base/SlowScriptDebug.js
@@ -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/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function SlowScriptDebug() { }
+
+SlowScriptDebug.prototype = {
+ classID: Components.ID("{e740ddb4-18b4-4aac-8ae1-9b0f4320769d}"),
+ classDescription: "Slow script debug handler",
+ contractID: "@mozilla.org/dom/slow-script-debug;1",
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISlowScriptDebug]),
+
+ get activationHandler() { return this._activationHandler; },
+ set activationHandler(cb) { return this._activationHandler = cb; },
+
+ get remoteActivationHandler() { return this._remoteActivationHandler; },
+ set remoteActivationHandler(cb) { return this._remoteActivationHandler = cb; },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SlowScriptDebug]);
diff --git a/dom/base/SlowScriptDebug.manifest b/dom/base/SlowScriptDebug.manifest
new file mode 100644
index 000000000..f84fd837d
--- /dev/null
+++ b/dom/base/SlowScriptDebug.manifest
@@ -0,0 +1,2 @@
+component {e740ddb4-18b4-4aac-8ae1-9b0f4320769d} SlowScriptDebug.js
+contract @mozilla.org/dom/slow-script-debug;1 {e740ddb4-18b4-4aac-8ae1-9b0f4320769d}
diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp
new file mode 100644
index 000000000..1c27c632e
--- /dev/null
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -0,0 +1,1368 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "StructuredCloneHolder.h"
+
+#include "ImageContainer.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/CryptoKey.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/DirectoryBinding.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileList.h"
+#include "mozilla/dom/FileListBinding.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/ImageData.h"
+#include "mozilla/dom/ImageDataBinding.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/StructuredClone.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/OffscreenCanvas.h"
+#include "mozilla/dom/OffscreenCanvasBinding.h"
+#include "mozilla/dom/PMessagePort.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/SubtleCryptoBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/dom/URLSearchParamsBinding.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "MultipartBlobImpl.h"
+#include "nsIRemoteBlob.h"
+#include "nsQueryObject.h"
+
+#ifdef MOZ_WEBRTC
+#include "mozilla/dom/RTCCertificate.h"
+#include "mozilla/dom/RTCCertificateBinding.h"
+#endif
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+JSObject*
+StructuredCloneCallbacksRead(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag, uint32_t aIndex,
+ void* aClosure)
+{
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->CustomReadHandler(aCx, aReader, aTag, aIndex);
+}
+
+bool
+StructuredCloneCallbacksWrite(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj,
+ void* aClosure)
+{
+ StructuredCloneHolderBase* holder =
+ static_cast<StructuredCloneHolderBase*>(aClosure);
+ MOZ_ASSERT(holder);
+ return holder->CustomWriteHandler(aCx, aWriter, aObj);
+}
+
+bool
+StructuredCloneCallbacksReadTransfer(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ void* aContent,
+ uint64_t aExtraData,
+ void* aClosure,
+ JS::MutableHandleObject 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);
+}
+
+void
+StructuredCloneCallbacksError(JSContext* aCx,
+ uint32_t aErrorId)
+{
+ NS_WARNING("Failed to clone data.");
+}
+
+} // anonymous namespace
+
+const JSStructuredCloneCallbacks StructuredCloneHolder::sCallbacks = {
+ StructuredCloneCallbacksRead,
+ StructuredCloneCallbacksWrite,
+ StructuredCloneCallbacksError,
+ StructuredCloneCallbacksReadTransfer,
+ StructuredCloneCallbacksWriteTransfer,
+ StructuredCloneCallbacksFreeTransfer
+};
+
+// 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().denySharedArrayBuffer());
+}
+
+bool
+StructuredCloneHolderBase::Write(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aTransfer,
+ JS::CloneDataPolicy cloneDataPolicy)
+{
+ 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, cloneDataPolicy,
+ &StructuredCloneHolder::sCallbacks, this))
+ {
+ mBuffer = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+bool
+StructuredCloneHolderBase::Read(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue)
+{
+ 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, &StructuredCloneHolder::sCallbacks, this);
+ return ok;
+}
+
+bool
+StructuredCloneHolderBase::CustomReadTransferHandler(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ void* aContent,
+ uint64_t aExtraData,
+ JS::MutableHandleObject 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.");
+}
+
+// StructuredCloneHolder class
+
+StructuredCloneHolder::StructuredCloneHolder(CloningSupport aSupportsCloning,
+ TransferringSupport aSupportsTransferring,
+ StructuredCloneScope aScope)
+ : StructuredCloneHolderBase(aScope)
+ , mSupportsCloning(aSupportsCloning == CloningSupported)
+ , mSupportsTransferring(aSupportsTransferring == TransferringSupported)
+ , mParent(nullptr)
+#ifdef DEBUG
+ , mCreationThread(NS_GetCurrentThread())
+#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().denySharedArrayBuffer(), aRv);
+}
+
+void
+StructuredCloneHolder::Write(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aTransfer,
+ JS::CloneDataPolicy cloneDataPolicy,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT_IF(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread,
+ mCreationThread == NS_GetCurrentThread());
+
+ if (!StructuredCloneHolderBase::Write(aCx, aValue, aTransfer, cloneDataPolicy)) {
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+}
+
+void
+StructuredCloneHolder::Read(nsISupports* aParent,
+ JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT_IF(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread,
+ mCreationThread == NS_GetCurrentThread());
+ MOZ_ASSERT(aParent);
+
+ mozilla::AutoRestore<nsISupports*> guard(mParent);
+ mParent = aParent;
+
+ if (!StructuredCloneHolderBase::Read(aCx, aValue)) {
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ }
+
+ // If we are tranferring something, we cannot call 'Read()' more than once.
+ if (mSupportsTransferring) {
+ mBlobImplArray.Clear();
+ mClonedSurfaces.Clear();
+ Clear();
+ }
+}
+
+void
+StructuredCloneHolder::ReadFromBuffer(nsISupports* aParent,
+ JSContext* aCx,
+ JSStructuredCloneData& aBuffer,
+ JS::MutableHandle<JS::Value> aValue,
+ ErrorResult& aRv)
+{
+ ReadFromBuffer(aParent, aCx, aBuffer,
+ JS_STRUCTURED_CLONE_VERSION, aValue, aRv);
+}
+
+void
+StructuredCloneHolder::ReadFromBuffer(nsISupports* aParent,
+ JSContext* aCx,
+ JSStructuredCloneData& aBuffer,
+ uint32_t aAlgorithmVersion,
+ JS::MutableHandle<JS::Value> aValue,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT_IF(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread,
+ mCreationThread == NS_GetCurrentThread());
+
+ MOZ_ASSERT(!mBuffer, "ReadFromBuffer() must be called without a Write().");
+
+ mozilla::AutoRestore<nsISupports*> guard(mParent);
+ mParent = aParent;
+
+ if (!JS_ReadStructuredClone(aCx, aBuffer, aAlgorithmVersion,
+ mStructuredCloneScope, aValue, &sCallbacks,
+ this)) {
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ }
+}
+
+/* static */ JSObject*
+StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag)
+{
+ if (aTag == SCTAG_DOM_IMAGEDATA) {
+ return ReadStructuredCloneImageData(aCx, aReader);
+ }
+
+ if (aTag == SCTAG_DOM_WEBCRYPTO_KEY || aTag == SCTAG_DOM_URLSEARCHPARAMS) {
+ nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
+ if (!global) {
+ return nullptr;
+ }
+
+ // Prevent the return value from being trashed by a GC during ~nsRefPtr.
+ JS::Rooted<JSObject*> result(aCx);
+ {
+ if (aTag == SCTAG_DOM_WEBCRYPTO_KEY) {
+ RefPtr<CryptoKey> key = new CryptoKey(global);
+ if (!key->ReadStructuredClone(aReader)) {
+ result = nullptr;
+ } else {
+ result = key->WrapObject(aCx, nullptr);
+ }
+ } else if (aTag == SCTAG_DOM_URLSEARCHPARAMS) {
+ RefPtr<URLSearchParams> usp = new URLSearchParams(global);
+ if (!usp->ReadStructuredClone(aReader)) {
+ result = nullptr;
+ } else {
+ result = usp->WrapObject(aCx, nullptr);
+ }
+ }
+ }
+ return result;
+ }
+
+ 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;
+ }
+ // 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));
+
+ JS::RootedValue result(aCx);
+ 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();
+ }
+
+#ifdef MOZ_WEBRTC
+ if (aTag == SCTAG_DOM_RTC_CERTIFICATE) {
+ if (!NS_IsMainThread()) {
+ return nullptr;
+ }
+
+ nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
+ if (!global) {
+ return nullptr;
+ }
+
+ // Prevent the return value from being trashed by a GC during ~nsRefPtr.
+ JS::Rooted<JSObject*> result(aCx);
+ {
+ RefPtr<RTCCertificate> cert = new RTCCertificate(global);
+ if (!cert->ReadStructuredClone(aReader)) {
+ result = nullptr;
+ } else {
+ result = cert->WrapObject(aCx, nullptr);
+ }
+ }
+ return result;
+ }
+#endif
+
+ // 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)
+{
+ JS::Rooted<JSObject*> obj(aCx, aObj);
+
+ // See if this is a ImageData object.
+ {
+ ImageData* imageData = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, &obj, imageData))) {
+ return WriteStructuredCloneImageData(aCx, aWriter, imageData);
+ }
+ }
+
+ // Handle URLSearchParams cloning
+ {
+ URLSearchParams* usp = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(URLSearchParams, &obj, usp))) {
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_URLSEARCHPARAMS, 0) &&
+ usp->WriteStructuredClone(aWriter);
+ }
+ }
+
+ // Handle Key cloning
+ {
+ CryptoKey* key = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, &obj, key))) {
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_WEBCRYPTO_KEY, 0) &&
+ key->WriteStructuredClone(aWriter);
+ }
+ }
+
+#ifdef MOZ_WEBRTC
+ {
+ // Handle WebRTC Certificate cloning
+ RTCCertificate* cert = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, &obj, cert))) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_RTC_CERTIFICATE, 0) &&
+ cert->WriteStructuredClone(aWriter);
+ }
+ }
+#endif
+
+ if (NS_IsMainThread() && xpc::IsReflector(obj)) {
+ nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(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
+ xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+ return false;
+}
+
+namespace {
+
+// Recursive!
+already_AddRefed<BlobImpl>
+EnsureBlobForBackgroundManager(BlobImpl* aBlobImpl,
+ PBackgroundChild* aManager,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aBlobImpl);
+ RefPtr<BlobImpl> blobImpl = aBlobImpl;
+
+ if (!aManager) {
+ aManager = BackgroundChild::GetForCurrentThread();
+ if (!aManager) {
+ return blobImpl.forget();
+ }
+ }
+
+ const nsTArray<RefPtr<BlobImpl>>* subBlobImpls =
+ aBlobImpl->GetSubBlobImpls();
+
+ if (!subBlobImpls || !subBlobImpls->Length()) {
+ if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl)) {
+ // Always make sure we have a blob from an actor we can use on this
+ // thread.
+ BlobChild* blobChild = BlobChild::GetOrCreate(aManager, blobImpl);
+ MOZ_ASSERT(blobChild);
+
+ blobImpl = blobChild->GetBlobImpl();
+ MOZ_ASSERT(blobImpl);
+
+ DebugOnly<bool> isMutable;
+ MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable)));
+ MOZ_ASSERT(!isMutable);
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false));
+ }
+
+ return blobImpl.forget();
+ }
+
+ const uint32_t subBlobCount = subBlobImpls->Length();
+ MOZ_ASSERT(subBlobCount);
+
+ nsTArray<RefPtr<BlobImpl>> newSubBlobImpls;
+ newSubBlobImpls.SetLength(subBlobCount);
+
+ bool newBlobImplNeeded = false;
+
+ for (uint32_t index = 0; index < subBlobCount; index++) {
+ const RefPtr<BlobImpl>& subBlobImpl = subBlobImpls->ElementAt(index);
+ MOZ_ASSERT(subBlobImpl);
+
+ RefPtr<BlobImpl>& newSubBlobImpl = newSubBlobImpls[index];
+
+ newSubBlobImpl = EnsureBlobForBackgroundManager(subBlobImpl, aManager, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(newSubBlobImpl);
+
+ if (subBlobImpl != newSubBlobImpl) {
+ newBlobImplNeeded = true;
+ }
+ }
+
+ if (newBlobImplNeeded) {
+ nsString contentType;
+ blobImpl->GetType(contentType);
+
+ if (blobImpl->IsFile()) {
+ nsString name;
+ blobImpl->GetName(name);
+
+ blobImpl = MultipartBlobImpl::Create(Move(newSubBlobImpls), name,
+ contentType, aRv);
+ } else {
+ blobImpl = MultipartBlobImpl::Create(Move(newSubBlobImpls), contentType, aRv);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false));
+ }
+
+ return blobImpl.forget();
+}
+
+JSObject*
+ReadBlob(JSContext* aCx,
+ uint32_t aIndex,
+ StructuredCloneHolder* aHolder)
+{
+ MOZ_ASSERT(aHolder);
+ MOZ_ASSERT(aIndex < aHolder->BlobImpls().Length());
+ RefPtr<BlobImpl> blobImpl = aHolder->BlobImpls()[aIndex];
+
+ ErrorResult rv;
+ blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return nullptr;
+ }
+
+ MOZ_ASSERT(blobImpl);
+
+ // RefPtr<File> 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<Blob> blob = Blob::Create(aHolder->ParentDuringRead(), blobImpl);
+ 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);
+
+ if (JS_GetStructuredCloneScope(aWriter) != JS::StructuredCloneScope::SameProcessSameThread &&
+ !aBlob->Impl()->MayBeClonedToOtherThreads()) {
+ return false;
+ }
+
+ ErrorResult rv;
+ RefPtr<BlobImpl> blobImpl =
+ EnsureBlobForBackgroundManager(aBlob->Impl(), nullptr, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ MOZ_ASSERT(blobImpl);
+
+ MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false));
+
+ // 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;
+ path.SetLength(aPathLength);
+ 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->ParentDuringRead(), 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->ParentDuringRead());
+
+ uint32_t zero, index;
+ // |index| is the index of the first blobImpl.
+ if (!JS_ReadUint32Pair(aReader, &zero, &index)) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(zero == 0);
+
+ // |aCount| is the number of BlobImpls to use from the |index|.
+ for (uint32_t i = 0; i < aCount; ++i) {
+ uint32_t pos = index + i;
+ MOZ_ASSERT(pos < aHolder->BlobImpls().Length());
+
+ RefPtr<BlobImpl> blobImpl = aHolder->BlobImpls()[pos];
+ MOZ_ASSERT(blobImpl->IsFile());
+
+ ErrorResult rv;
+ blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return nullptr;
+ }
+
+ MOZ_ASSERT(blobImpl);
+
+ RefPtr<File> file = File::Create(aHolder->ParentDuringRead(), blobImpl);
+ 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;
+ }
+
+ ErrorResult rv;
+ nsTArray<RefPtr<BlobImpl>> blobImpls;
+
+ for (uint32_t i = 0; i < aFileList->Length(); ++i) {
+ RefPtr<BlobImpl> blobImpl =
+ EnsureBlobForBackgroundManager(aFileList->Item(i)->Impl(), nullptr, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ MOZ_ASSERT(blobImpl);
+ 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->ParentDuringRead());
+
+ Optional<nsAString> thirdArg;
+ for (uint32_t i = 0; i < aCount; ++i) {
+ nsAutoString name;
+ if (!ReadString(aReader, name)) {
+ return nullptr;
+ }
+
+ uint32_t tag, indexOrLengthOfString;
+ if (!JS_ReadUint32Pair(aReader, &tag, &indexOrLengthOfString)) {
+ return nullptr;
+ }
+
+ if (tag == SCTAG_DOM_BLOB) {
+ MOZ_ASSERT(indexOrLengthOfString < aHolder->BlobImpls().Length());
+
+ RefPtr<BlobImpl> blobImpl =
+ aHolder->BlobImpls()[indexOrLengthOfString];
+
+ ErrorResult rv;
+ blobImpl = EnsureBlobForBackgroundManager(blobImpl, nullptr, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return nullptr;
+ }
+
+ MOZ_ASSERT(blobImpl);
+
+ RefPtr<Blob> blob =
+ Blob::Create(aHolder->ParentDuringRead(), blobImpl);
+ MOZ_ASSERT(blob);
+
+ 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 {
+ MOZ_ASSERT(tag == 0);
+
+ nsAutoString value;
+ value.SetLength(indexOrLengthOfString);
+ 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 (!WriteString(closure->mWriter, aName)) {
+ return false;
+ }
+
+ if (aValue.IsBlob()) {
+ ErrorResult rv;
+ RefPtr<BlobImpl> blobImpl =
+ EnsureBlobForBackgroundManager(aValue.GetAsBlob()->Impl(), nullptr,
+ rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ if (!JS_WriteUint32Pair(closure->mWriter, SCTAG_DOM_BLOB,
+ closure->mHolder->BlobImpls().Length())) {
+ return false;
+ }
+
+ closure->mHolder->BlobImpls().AppendElement(blobImpl);
+ return true;
+ }
+
+ if (aValue.IsDirectory()) {
+ Directory* directory = aValue.GetAsDirectory();
+
+ if (closure->mHolder->CloneScope() !=
+ StructuredCloneHolder::StructuredCloneScope::SameProcessSameThread &&
+ !directory->ClonableToDifferentThreadOrProcess()) {
+ return false;
+ }
+
+ 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(aIndex < aHolder->WasmModules().Length());
+ MOZ_ASSERT(aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcessSameThread ||
+ aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcessDifferentThread);
+
+ RefPtr<JS::WasmModule> wasmModule = aHolder->WasmModules()[aIndex];
+ return wasmModule->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::SameProcessSameThread ||
+ aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcessDifferentThread);
+
+ // We store the position of the wasmModule in the array as index.
+ if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_WASM,
+ aHolder->WasmModules().Length())) {
+ aHolder->WasmModules().AppendElement(aWasmModule);
+ return true;
+ }
+
+ return false;
+}
+
+} // anonymous namespace
+
+JSObject*
+StructuredCloneHolder::CustomReadHandler(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ 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) {
+ MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+ mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread);
+
+ // Get the current global object.
+ // This can be null.
+ nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
+ // aIndex is the index of the cloned image.
+ return ImageBitmap::ReadStructuredClone(aCx, aReader,
+ parent, GetSurfaces(), aIndex);
+ }
+
+ if (aTag == SCTAG_DOM_WASM) {
+ return ReadWasmModule(aCx, aIndex, this);
+ }
+
+ return ReadFullySerializableObjects(aCx, aReader, aTag);
+}
+
+bool
+StructuredCloneHolder::CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj)
+{
+ 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))) {
+ if (mStructuredCloneScope != StructuredCloneScope::SameProcessSameThread &&
+ !directory->ClonableToDifferentThreadOrProcess()) {
+ return false;
+ }
+
+ 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.
+ if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+ mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) {
+ ImageBitmap* imageBitmap = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, &obj, imageBitmap))) {
+ return ImageBitmap::WriteStructuredClone(aWriter,
+ GetSurfaces(),
+ imageBitmap);
+ }
+ }
+
+ // See if this is a WasmModule.
+ if ((mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+ mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) &&
+ JS::IsWasmModuleObject(obj)) {
+ RefPtr<JS::WasmModule> module = JS::GetWasmModule(obj);
+ MOZ_ASSERT(module);
+
+ return WriteWasmModule(aWriter, module, this);
+ }
+
+ return WriteFullySerializableObjects(aCx, aWriter, aObj);
+}
+
+bool
+StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ void* aContent,
+ uint64_t aExtraData,
+ JS::MutableHandleObject aReturnObject)
+{
+ MOZ_ASSERT(mSupportsTransferring);
+
+ if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
+ MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
+ const MessagePortIdentifier& portIdentifier = mPortIdentifiers[aExtraData];
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
+
+ ErrorResult rv;
+ RefPtr<MessagePort> port =
+ MessagePort::Create(global, portIdentifier, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ 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) {
+ MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+ mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread);
+ MOZ_ASSERT(aContent);
+ OffscreenCanvasCloneData* data =
+ static_cast<OffscreenCanvasCloneData*>(aContent);
+ nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
+ RefPtr<OffscreenCanvas> canvas = OffscreenCanvas::CreateFromCloneData(parent, 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) {
+ MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+ mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread);
+ MOZ_ASSERT(aContent);
+ ImageBitmapCloneData* data =
+ static_cast<ImageBitmapCloneData*>(aContent);
+ nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
+ RefPtr<ImageBitmap> bitmap = ImageBitmap::CreateFromCloneData(parent, 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;
+ }
+
+ return false;
+}
+
+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)) {
+ // We use aExtraData to store the index of this new port identifier.
+ *aExtraData = mPortIdentifiers.Length();
+ MessagePortIdentifier* identifier = mPortIdentifiers.AppendElement();
+
+ port->CloneAndDisentangle(*identifier);
+
+ *aTag = SCTAG_DOM_MAP_MESSAGEPORT;
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ *aContent = nullptr;
+
+ return true;
+ }
+
+ if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+ mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) {
+ OffscreenCanvas* canvas = nullptr;
+ rv = UNWRAP_OBJECT(OffscreenCanvas, &obj, canvas);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(canvas);
+
+ *aExtraData = 0;
+ *aTag = SCTAG_DOM_CANVAS;
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ *aContent = canvas->ToCloneData();
+ MOZ_ASSERT(*aContent);
+ canvas->SetNeutered();
+
+ return true;
+ }
+
+ ImageBitmap* bitmap = nullptr;
+ rv = UNWRAP_OBJECT(ImageBitmap, &obj, bitmap);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(bitmap);
+
+ *aExtraData = 0;
+ *aTag = SCTAG_DOM_IMAGEBITMAP;
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ *aContent = bitmap->ToCloneData().release();
+ MOZ_ASSERT(*aContent);
+ bitmap->Close();
+
+ 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);
+ MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
+ MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
+ return;
+ }
+
+ if (aTag == SCTAG_DOM_CANVAS) {
+ MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+ mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread);
+ MOZ_ASSERT(aContent);
+ OffscreenCanvasCloneData* data =
+ static_cast<OffscreenCanvasCloneData*>(aContent);
+ delete data;
+ return;
+ }
+
+ if (aTag == SCTAG_DOM_IMAGEBITMAP) {
+ MOZ_ASSERT(mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+ mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread);
+ MOZ_ASSERT(aContent);
+ ImageBitmapCloneData* data =
+ static_cast<ImageBitmapCloneData*>(aContent);
+ delete data;
+ return;
+ }
+}
+
+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;
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/base/StructuredCloneHolder.h b/dom/base/StructuredCloneHolder.h
new file mode 100644
index 000000000..e6953d0f3
--- /dev/null
+++ b/dom/base/StructuredCloneHolder.h
@@ -0,0 +1,332 @@
+/* -*- 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 mozilla_dom_StructuredCloneHolder_h
+#define mozilla_dom_StructuredCloneHolder_h
+
+#include "jsapi.h"
+#include "js/StructuredClone.h"
+#include "mozilla/Move.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsISupports.h"
+#include "nsTArray.h"
+
+#ifdef DEBUG
+#include "nsIThread.h"
+#endif
+
+namespace mozilla {
+class ErrorResult;
+namespace layers {
+class Image;
+}
+
+namespace gfx {
+class DataSourceSurface;
+}
+
+namespace dom {
+
+class StructuredCloneHolderBase
+{
+public:
+ typedef JS::StructuredCloneScope StructuredCloneScope;
+
+ StructuredCloneHolderBase(StructuredCloneScope aScope = StructuredCloneScope::SameProcessSameThread);
+ virtual ~StructuredCloneHolderBase();
+
+ StructuredCloneHolderBase(StructuredCloneHolderBase&& aOther) = default;
+
+ // 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,
+ uint32_t aTag,
+ uint32_t aIndex) = 0;
+
+ virtual bool CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj) = 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::MutableHandleObject 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);
+
+ // 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,
+ JS::CloneDataPolicy cloneDataPolicy);
+
+ // If Write() has been called, this method retrieves data and stores it into
+ // aValue.
+ bool Read(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue);
+
+ bool HasData() const
+ {
+ return !!mBuffer;
+ }
+
+ JSStructuredCloneData& BufferData() const
+ {
+ MOZ_ASSERT(mBuffer, "Write() has never been called.");
+ return mBuffer->data();
+ }
+
+protected:
+ UniquePtr<JSAutoStructuredCloneBuffer> mBuffer;
+
+ StructuredCloneScope mStructuredCloneScope;
+
+#ifdef DEBUG
+ bool mClearCalled;
+#endif
+};
+
+class BlobImpl;
+class MessagePort;
+class MessagePortIdentifier;
+
+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) = default;
+
+ // Normally you should just use Write() and Read().
+
+ void Write(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult &aRv);
+
+ void Write(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aTransfer,
+ JS::CloneDataPolicy cloneDataPolicy,
+ ErrorResult &aRv);
+
+ void Read(nsISupports* aParent,
+ JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ 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();
+ }
+
+ 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;
+ }
+
+ StructuredCloneScope CloneScope() const
+ {
+ return mStructuredCloneScope;
+ }
+
+ // The parent object is set internally just during the Read(). This method
+ // can be used by read functions to retrieve it.
+ nsISupports* ParentDuringRead() const
+ {
+ return mParent;
+ }
+
+ // 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 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;
+ }
+
+ // Implementations of the virtual methods to allow cloning of objects which
+ // JS engine itself doesn't clone.
+
+ virtual JSObject* CustomReadHandler(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ uint32_t aIndex) override;
+
+ virtual bool CustomWriteHandler(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj) override;
+
+ virtual bool CustomReadTransferHandler(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ uint32_t aTag,
+ void* aContent,
+ uint64_t aExtraData,
+ JS::MutableHandleObject 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;
+
+ // 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);
+
+ 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(nsISupports* aParent,
+ JSContext* aCx,
+ JSStructuredCloneData& aBuffer,
+ JS::MutableHandle<JS::Value> aValue,
+ ErrorResult &aRv);
+
+ void ReadFromBuffer(nsISupports* aParent,
+ JSContext* aCx,
+ JSStructuredCloneData& aBuffer,
+ uint32_t aAlgorithmVersion,
+ JS::MutableHandle<JS::Value> aValue,
+ ErrorResult &aRv);
+
+ bool mSupportsCloning;
+ bool mSupportsTransferring;
+
+ // 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;
+
+ // 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;
+
+ // This raw pointer is only set within ::Read() and is unset by the end.
+ nsISupports* MOZ_NON_OWNING_REF mParent;
+
+ // 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<nsIThread> mCreationThread;
+#endif
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_StructuredCloneHolder_h
diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h
new file mode 100644
index 000000000..a19305311
--- /dev/null
+++ b/dom/base/StructuredCloneTags.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 {
+namespace dom {
+
+// CHANGING THE ORDER/PLACEMENT OF EXISTING ENUM VALUES MAY BREAK INDEXEDDB.
+// PROCEED WITH EXTREME CAUTION.
+enum StructuredCloneTags {
+ SCTAG_BASE = JS_SCTAG_USER_MIN,
+
+ SCTAG_DOM_BLOB,
+
+ // This tag is obsolete and exists only for backwards compatibility with
+ // existing IndexedDB databases.
+ SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE,
+
+ SCTAG_DOM_FILELIST,
+ SCTAG_DOM_MUTABLEFILE,
+ SCTAG_DOM_FILE,
+
+ SCTAG_DOM_WASM,
+
+ // New IDB tags go here!
+
+ // These tags are used for both main thread and workers.
+ SCTAG_DOM_IMAGEDATA,
+ SCTAG_DOM_MAP_MESSAGEPORT,
+
+ SCTAG_DOM_FUNCTION,
+
+ // This tag is for WebCrypto keys
+ SCTAG_DOM_WEBCRYPTO_KEY,
+
+ SCTAG_DOM_NULL_PRINCIPAL,
+ SCTAG_DOM_SYSTEM_PRINCIPAL,
+ SCTAG_DOM_CONTENT_PRINCIPAL,
+
+ SCTAG_DOM_IMAGEBITMAP,
+
+ SCTAG_DOM_RTC_CERTIFICATE,
+
+ SCTAG_DOM_FORMDATA,
+
+ // This tag is for OffscreenCanvas.
+ SCTAG_DOM_CANVAS,
+
+ SCTAG_DOM_EXPANDED_PRINCIPAL,
+
+ SCTAG_DOM_DIRECTORY,
+
+ // This tag is used by both main thread and workers.
+ SCTAG_DOM_URLSEARCHPARAMS,
+
+ // When adding a new tag for IDB, please don't add it to the end of the list!
+ // Tags that are supported by IDB must not ever change. See the static assert
+ // in IDBObjectStore.cpp, method CommonStructuredCloneReadCallback.
+ // Adding to the end of the list would make removing of other tags harder in
+ // future.
+
+ SCTAG_DOM_MAX
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // StructuredCloneTags_h__
diff --git a/dom/base/StyleSheetList.cpp b/dom/base/StyleSheetList.cpp
new file mode 100644
index 000000000..7274b5ea5
--- /dev/null
+++ b/dom/base/StyleSheetList.cpp
@@ -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/. */
+
+#include "mozilla/dom/StyleSheetList.h"
+
+#include "mozilla/CSSStyleSheet.h"
+#include "mozilla/dom/StyleSheetListBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(StyleSheetList)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StyleSheetList)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMStyleSheetList)
+ 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 StyleSheetListBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+StyleSheetList::GetLength(uint32_t* aLength)
+{
+ *aLength = Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StyleSheetList::SlowItem(uint32_t aIndex, nsIDOMStyleSheet** aItem)
+{
+ NS_IF_ADDREF(*aItem = Item(aIndex));
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/StyleSheetList.h b/dom/base/StyleSheetList.h
new file mode 100644
index 000000000..dfedc2214
--- /dev/null
+++ b/dom/base/StyleSheetList.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_StyleSheetList_h
+#define mozilla_dom_StyleSheetList_h
+
+#include "nsIDOMStyleSheetList.h"
+#include "nsWrapperCache.h"
+
+class nsINode;
+
+namespace mozilla {
+class StyleSheet;
+
+namespace dom {
+
+class StyleSheetList : public nsIDOMStyleSheetList
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StyleSheetList)
+ NS_DECL_NSIDOMSTYLESHEETLIST
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ virtual nsINode* GetParentObject() const = 0;
+
+ virtual uint32_t Length() = 0;
+ virtual StyleSheet* IndexedGetter(uint32_t aIndex, bool& aFound) = 0;
+ StyleSheet* Item(uint32_t aIndex)
+ {
+ bool dummy = false;
+ return IndexedGetter(aIndex, dummy);
+ }
+
+protected:
+ virtual ~StyleSheetList() {}
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StyleSheetList_h
diff --git a/dom/base/SubtleCrypto.cpp b/dom/base/SubtleCrypto.cpp
new file mode 100644
index 000000000..1b97529a1
--- /dev/null
+++ b/dom/base/SubtleCrypto.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 "mozilla/dom/SubtleCrypto.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/SubtleCryptoBinding.h"
+#include "mozilla/dom/WebCryptoTask.h"
+
+namespace mozilla {
+namespace 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 SubtleCryptoBinding::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__); \
+ 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 dom
+} // namespace mozilla
diff --git a/dom/base/SubtleCrypto.h b/dom/base/SubtleCrypto.h
new file mode 100644
index 000000000..9ac5d8b8f
--- /dev/null
+++ b/dom/base/SubtleCrypto.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 {
+namespace dom {
+
+class ObjectOrString;
+class Promise;
+
+typedef ArrayBufferViewOrArrayBuffer CryptoOperationData;
+
+class SubtleCrypto final : public nsISupports,
+ public nsWrapperCache
+{
+ ~SubtleCrypto() {}
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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 dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SubtleCrypto_h
diff --git a/dom/base/TabGroup.cpp b/dom/base/TabGroup.cpp
new file mode 100644
index 000000000..de67bcb78
--- /dev/null
+++ b/dom/base/TabGroup.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/TabGroup.h"
+
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "nsIDocShell.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace dom {
+
+static StaticRefPtr<TabGroup> sChromeTabGroup;
+
+TabGroup::TabGroup(bool aIsChrome)
+{
+ // Do not throttle runnables from chrome windows. In theory we should
+ // not have abuse issues from these windows and many browser chrome
+ // tests have races that fail if we do throttle chrome runnables.
+ if (aIsChrome) {
+ MOZ_ASSERT(!sChromeTabGroup);
+ return;
+ }
+
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ MOZ_DIAGNOSTIC_ASSERT(mainThread);
+
+ // This may return nullptr during xpcom shutdown. This is ok as we
+ // do not guarantee a ThrottledEventQueue will be present.
+ mThrottledEventQueue = ThrottledEventQueue::Create(mainThread);
+}
+
+TabGroup::~TabGroup()
+{
+ MOZ_ASSERT(mDocGroups.IsEmpty());
+ MOZ_ASSERT(mWindows.IsEmpty());
+}
+
+TabGroup*
+TabGroup::GetChromeTabGroup()
+{
+ if (!sChromeTabGroup) {
+ sChromeTabGroup = new TabGroup(true /* chrome tab group */);
+ ClearOnShutdown(&sChromeTabGroup);
+ }
+ return sChromeTabGroup;
+}
+
+already_AddRefed<DocGroup>
+TabGroup::GetDocGroup(const nsACString& aKey)
+{
+ RefPtr<DocGroup> docGroup(mDocGroups.GetEntry(aKey)->mDocGroup);
+ return docGroup.forget();
+}
+
+already_AddRefed<DocGroup>
+TabGroup::AddDocument(const nsACString& aKey, nsIDocument* aDocument)
+{
+ HashEntry* entry = mDocGroups.PutEntry(aKey);
+ RefPtr<DocGroup> docGroup;
+ if (entry->mDocGroup) {
+ docGroup = entry->mDocGroup;
+ } else {
+ docGroup = new DocGroup(this, aKey);
+ entry->mDocGroup = docGroup;
+ }
+
+ // Make sure that the hashtable was updated and now contains the correct value
+ MOZ_ASSERT(RefPtr<DocGroup>(GetDocGroup(aKey)) == docGroup);
+
+ docGroup->mDocuments.AppendElement(aDocument);
+
+ return docGroup.forget();
+}
+
+/* static */ already_AddRefed<TabGroup>
+TabGroup::Join(nsPIDOMWindowOuter* aWindow, TabGroup* aTabGroup)
+{
+ RefPtr<TabGroup> tabGroup = aTabGroup;
+ if (!tabGroup) {
+ tabGroup = new TabGroup();
+ }
+ MOZ_ASSERT(!tabGroup->mWindows.Contains(aWindow));
+ tabGroup->mWindows.AppendElement(aWindow);
+ return tabGroup.forget();
+}
+
+void
+TabGroup::Leave(nsPIDOMWindowOuter* aWindow)
+{
+ MOZ_ASSERT(mWindows.Contains(aWindow));
+ mWindows.RemoveElement(aWindow);
+}
+
+nsresult
+TabGroup::FindItemWithName(const nsAString& aName,
+ nsIDocShellTreeItem* aRequestor,
+ nsIDocShellTreeItem* aOriginalRequestor,
+ nsIDocShellTreeItem** aFoundItem)
+{
+ NS_ENSURE_ARG_POINTER(aFoundItem);
+ *aFoundItem = nullptr;
+
+ MOZ_ASSERT(!aName.LowerCaseEqualsLiteral("_blank") &&
+ !aName.LowerCaseEqualsLiteral("_top") &&
+ !aName.LowerCaseEqualsLiteral("_parent") &&
+ !aName.LowerCaseEqualsLiteral("_self"));
+
+ for (nsPIDOMWindowOuter* outerWindow : mWindows) {
+ // Ignore non-toplevel windows
+ if (outerWindow->GetScriptableParentOrNull()) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> docshell = outerWindow->GetDocShell();
+ if (!docshell) {
+ continue;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docshell->GetSameTypeRootTreeItem(getter_AddRefs(root));
+ MOZ_RELEASE_ASSERT(docshell == root);
+ if (root && aRequestor != root) {
+ root->FindItemWithName(aName, this, aOriginalRequestor, aFoundItem);
+ if (*aFoundItem) {
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsTArray<nsPIDOMWindowOuter*>
+TabGroup::GetTopLevelWindows()
+{
+ nsTArray<nsPIDOMWindowOuter*> array;
+
+ for (nsPIDOMWindowOuter* outerWindow : mWindows) {
+ if (!outerWindow->GetScriptableParentOrNull()) {
+ array.AppendElement(outerWindow);
+ }
+ }
+
+ return array;
+}
+
+ThrottledEventQueue*
+TabGroup::GetThrottledEventQueue() const
+{
+ return mThrottledEventQueue;
+}
+
+NS_IMPL_ISUPPORTS(TabGroup, nsISupports)
+
+TabGroup::HashEntry::HashEntry(const nsACString* aKey)
+ : nsCStringHashKey(aKey), mDocGroup(nullptr)
+{}
+
+}
+}
diff --git a/dom/base/TabGroup.h b/dom/base/TabGroup.h
new file mode 100644
index 000000000..f9fa9eec5
--- /dev/null
+++ b/dom/base/TabGroup.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 TabGroup_h
+#define TabGroup_h
+
+#include "nsISupports.h"
+#include "nsISupportsImpl.h"
+#include "nsIPrincipal.h"
+#include "nsTHashtable.h"
+#include "nsString.h"
+
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+class ThrottledEventQueue;
+namespace dom {
+
+// 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 "unit of related browsing contexts"
+//
+// 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 TabGroup is a set of browsing contexts which are all "related". Within a
+// TabGroup, browsing contexts are broken into "similar-origin" DocGroups. In
+// more detail, a DocGroup is actually a collection of documents, and a
+// TabGroup is a collection of DocGroups. A TabGroup typically will contain
+// (through its DocGroups) the documents from one or more tabs related by
+// window.opener. A DocGroup is a member of exactly one TabGroup.
+
+class DocGroup;
+
+class TabGroup final : public nsISupports
+{
+private:
+ class HashEntry : public nsCStringHashKey
+ {
+ public:
+ // NOTE: Weak reference. The DocGroup destructor removes itself from its
+ // owning TabGroup.
+ DocGroup* mDocGroup;
+ explicit HashEntry(const nsACString* aKey);
+ };
+
+ typedef nsTHashtable<HashEntry> DocGroupMap;
+public:
+ typedef DocGroupMap::Iterator Iterator;
+
+ friend class DocGroup;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static TabGroup*
+ GetChromeTabGroup();
+
+ explicit TabGroup(bool aIsChrome = false);
+
+ // Get the docgroup for the corresponding doc group key.
+ // Returns null if the given key hasn't been seen yet.
+ already_AddRefed<DocGroup>
+ GetDocGroup(const nsACString& aKey);
+
+ already_AddRefed<DocGroup>
+ AddDocument(const nsACString& aKey, nsIDocument* aDocument);
+
+ // Join the specified TabGroup, returning a reference to it. If aTabGroup is
+ // nullptr, create a new tabgroup to join.
+ static already_AddRefed<TabGroup>
+ Join(nsPIDOMWindowOuter* aWindow, TabGroup* aTabGroup);
+
+ void Leave(nsPIDOMWindowOuter* aWindow);
+
+ Iterator Iter()
+ {
+ return mDocGroups.Iter();
+ }
+
+
+ // Returns the nsIDocShellTreeItem with the given name, searching each of the
+ // docShell trees which are within this TabGroup. It will pass itself as
+ // aRequestor to each docShellTreeItem which it asks to search for the name,
+ // and will not search the docShellTreeItem which is passed as aRequestor.
+ //
+ // This method is used in order to correctly namespace named windows based on
+ // their unit of related browsing contexts.
+ //
+ // It is illegal to pass in the special case-insensitive names "_blank",
+ // "_self", "_parent" or "_top", as those should be handled elsewhere.
+ nsresult
+ FindItemWithName(const nsAString& aName,
+ nsIDocShellTreeItem* aRequestor,
+ nsIDocShellTreeItem* aOriginalRequestor,
+ nsIDocShellTreeItem** aFoundItem);
+
+ nsTArray<nsPIDOMWindowOuter*> GetTopLevelWindows();
+
+ // Get the event queue that associated windows can use to issue runnables to
+ // the main thread. This may return nullptr during browser shutdown.
+ ThrottledEventQueue*
+ GetThrottledEventQueue() const;
+
+private:
+ ~TabGroup();
+ DocGroupMap mDocGroups;
+ nsTArray<nsPIDOMWindowOuter*> mWindows;
+ RefPtr<ThrottledEventQueue> mThrottledEventQueue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // defined(TabGroup_h)
diff --git a/dom/base/Text.cpp b/dom/base/Text.cpp
new file mode 100644
index 000000000..b98471247
--- /dev/null
+++ b/dom/base/Text.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/Text.h"
+#include "nsTextNode.h"
+
+namespace mozilla {
+namespace dom {
+
+already_AddRefed<Text>
+Text::SplitText(uint32_t aOffset, ErrorResult& rv)
+{
+ nsCOMPtr<nsIContent> newChild;
+ rv = SplitData(aOffset, getter_AddRefs(newChild));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return newChild.forget().downcast<Text>();
+}
+
+/* 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);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/Text.h b/dom/base/Text.h
new file mode 100644
index 000000000..a16db848e
--- /dev/null
+++ b/dom/base/Text.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_Text_h
+#define mozilla_dom_Text_h
+
+#include "nsGenericDOMDataNode.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+namespace dom {
+
+class Text : public nsGenericDOMDataNode
+{
+public:
+ explicit Text(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericDOMDataNode(aNodeInfo)
+ {}
+
+ explicit Text(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsGenericDOMDataNode(aNodeInfo)
+ {}
+
+ using nsGenericDOMDataNode::GetWholeText;
+
+ // WebIDL API
+ already_AddRefed<Text> SplitText(uint32_t aOffset, ErrorResult& rv);
+ void GetWholeText(nsAString& aWholeText, ErrorResult& rv)
+ {
+ rv = GetWholeText(aWholeText);
+ }
+
+ static already_AddRefed<Text>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aData, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+inline mozilla::dom::Text* nsINode::GetAsText()
+{
+ return IsNodeOfType(eTEXT) ? static_cast<mozilla::dom::Text*>(this)
+ : nullptr;
+}
+
+inline const mozilla::dom::Text* nsINode::GetAsText() const
+{
+ return IsNodeOfType(eTEXT) ? static_cast<const mozilla::dom::Text*>(this)
+ : nullptr;
+}
+
+#endif // mozilla_dom_Text_h
diff --git a/dom/base/TextInputProcessor.cpp b/dom/base/TextInputProcessor.cpp
new file mode 100644
index 000000000..3ebbc5347
--- /dev/null
+++ b/dom/base/TextInputProcessor.cpp
@@ -0,0 +1,1035 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "gfxPrefs.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TextInputProcessor.h"
+#include "nsContentUtils.h"
+#include "nsIDocShell.h"
+#include "nsIWidget.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+
+using namespace mozilla::widget;
+
+namespace mozilla {
+
+/******************************************************************************
+ * TextInputProcessorNotification
+ ******************************************************************************/
+
+class TextInputProcessorNotification final :
+ public nsITextInputProcessorNotification
+{
+public:
+ explicit TextInputProcessorNotification(const char* aType)
+ : mType(aType)
+ {
+ }
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD GetType(nsACString& aType) override final
+ {
+ aType = mType;
+ return NS_OK;
+ }
+
+protected:
+ ~TextInputProcessorNotification() { }
+
+private:
+ nsAutoCString mType;
+
+ TextInputProcessorNotification() { }
+};
+
+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::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;
+ nsresult rv = docShell->GetPresContext(getter_AddRefs(presContext));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ 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();
+ }
+ }
+
+ if (aForTests) {
+ bool isAPZAware = gfxPrefs::TestEventsAsyncEnabled();
+ 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 "" or "keydown".
+ if (aKeyboardEvent.mMessage == eKeyDown) {
+ 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;
+ }
+
+ // 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
+ // keyup 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(
+ nsIDOMKeyEvent* aDOMKeyEvent,
+ uint32_t& aKeyFlags,
+ uint8_t aOptionalArgc,
+ WidgetKeyboardEvent*& aKeyboardEvent)
+{
+ aKeyboardEvent = nullptr;
+
+ aKeyboardEvent =
+ aOptionalArgc && aDOMKeyEvent ?
+ aDOMKeyEvent->AsEvent()->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(nsIDOMKeyEvent* 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<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv =
+ PrepareKeyboardEventForComposition(aDOMKeyEvent, 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(nsIDOMKeyEvent* 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();
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv =
+ PrepareKeyboardEventForComposition(aDOMKeyEvent, 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(nsIDOMKeyEvent* aDOMKeyEvent,
+ uint32_t aKeyFlags,
+ uint8_t aOptionalArgc)
+{
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv =
+ PrepareKeyboardEventForComposition(aDOMKeyEvent, aKeyFlags, aOptionalArgc,
+ keyboardEvent);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return CommitCompositionInternal(keyboardEvent, aKeyFlags);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::CommitCompositionWith(const nsAString& aCommitString,
+ nsIDOMKeyEvent* aDOMKeyEvent,
+ uint32_t aKeyFlags,
+ uint8_t aOptionalArgc,
+ bool* aSucceeded)
+{
+ MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv =
+ PrepareKeyboardEventForComposition(aDOMKeyEvent, 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(nsIDOMKeyEvent* aDOMKeyEvent,
+ uint32_t aKeyFlags,
+ uint8_t aOptionalArgc)
+{
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+ WidgetKeyboardEvent* keyboardEvent;
+ nsresult rv =
+ PrepareKeyboardEventForComposition(aDOMKeyEvent, 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;
+ 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_(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 = (mForTests)? false : true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::Keydown(nsIDOMKeyEvent* 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->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+ if (NS_WARN_IF(!originalKeyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aConsumedFlags);
+}
+
+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);
+ 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();
+
+ 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 &&
+ kungFuDeathGrip->MaybeDispatchKeypressEvents(keyEvent, status)) {
+ aConsumedFlags |=
+ (status == nsEventStatus_eConsumeNoDefault) ? KEYPRESS_IS_CONSUMED :
+ KEYEVENT_NOT_CONSUMED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::Keyup(nsIDOMKeyEvent* 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->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+ if (NS_WARN_IF(!originalKeyEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault);
+}
+
+nsresult
+TextInputProcessor::KeyupInternal(const WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aKeyFlags,
+ bool& aDoDefault)
+{
+ aDoDefault = false;
+
+ // We shouldn't modify the internal WidgetKeyboardEvent.
+ WidgetKeyboardEvent keyEvent(aKeyboardEvent);
+ 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();
+
+ 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());
+ if (!mModifierKeyDataArray) {
+ *aActive = false;
+ return NS_OK;
+ }
+ Modifiers activeModifiers = mModifierKeyDataArray->GetActiveModifiers();
+ Modifiers modifier = WidgetInputEvent::GetModifier(aModifierKeyName);
+ *aActive = ((activeModifiers & 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;
+}
+
+/******************************************************************************
+ * 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 000000000..07cfd3898
--- /dev/null
+++ b/dom/base/TextInputProcessor.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/EventForwards.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "nsITextInputProcessor.h"
+#include "nsITextInputProcessorCallback.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class TextInputProcessor final : public nsITextInputProcessor
+ , public widget::TextEventDispatcherListener
+{
+ typedef mozilla::widget::IMENotification IMENotification;
+ typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
+
+public:
+ TextInputProcessor();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITEXTINPUTPROCESSOR
+
+ // TextEventDispatcherListener
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+
+ NS_IMETHOD_(void) WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData) override;
+
+protected:
+ virtual ~TextInputProcessor();
+
+private:
+ bool IsComposing() const;
+ nsresult BeginInputTransactionInternal(
+ mozIDOMWindow* aWindow,
+ nsITextInputProcessorCallback* aCallback,
+ bool aForTests,
+ bool& aSucceeded);
+ nsresult CommitCompositionInternal(
+ const WidgetKeyboardEvent* aKeyboardEvent = nullptr,
+ uint32_t aKeyFlags = 0,
+ const nsAString* aCommitString = nullptr,
+ bool* aSucceeded = nullptr);
+ nsresult CancelCompositionInternal(
+ const WidgetKeyboardEvent* aKeyboardEvent = nullptr,
+ uint32_t aKeyFlags = 0);
+ 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);
+ bool IsValidEventTypeForComposition(
+ const WidgetKeyboardEvent& aKeyboardEvent) const;
+ nsresult PrepareKeyboardEventForComposition(
+ nsIDOMKeyEvent* 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)
+ {
+ }
+ };
+ 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() { }
+ };
+
+ Modifiers GetActiveModifiers() const
+ {
+ return mModifierKeyDataArray ?
+ mModifierKeyDataArray->GetActiveModifiers() : 0;
+ }
+ 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 000000000..61d97f634
--- /dev/null
+++ b/dom/base/ThirdPartyUtil.cpp
@@ -0,0 +1,320 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsIServiceManager.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIDOMWindow.h"
+#include "nsILoadContext.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIURI.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+
+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)
+
+nsresult
+ThirdPartyUtil::Init()
+{
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE);
+
+ nsresult rv;
+ mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
+
+ return rv;
+}
+
+// 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;
+ }
+
+ // Get the base domain for aSecondURI.
+ nsCString secondDomain;
+ nsresult rv = GetBaseDomain(aSecondURI, secondDomain);
+ LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain.get(), secondDomain.get()));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Check strict equality.
+ *aResult = aFirstDomain != secondDomain;
+ return NS_OK;
+}
+
+// Get the URI associated with a window.
+NS_IMETHODIMP
+ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy* aWin, nsIURI** result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aWin);
+ if (!scriptObjPrin) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsIPrincipal* prin = scriptObjPrin->GetPrincipal();
+ if (!prin) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (prin->GetIsNullPrincipal()) {
+ LOG(("ThirdPartyUtil::GetURIFromWindow can't use null principal\n"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = prin->GetURI(result);
+ return rv;
+}
+
+// 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");
+
+ nsCString firstHost;
+ nsresult rv = GetBaseDomain(aFirstURI, firstHost);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return IsThirdPartyInternal(firstHost, aSecondURI, aResult);
+}
+
+// Determine if any URI of the window hierarchy of aWindow is foreign with
+// respect to aSecondURI. 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;
+
+ // Get the URI of the window, and its base domain.
+ nsresult rv;
+ nsCOMPtr<nsIURI> currentURI;
+ rv = GetURIFromWindow(aWindow, getter_AddRefs(currentURI));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCString bottomDomain;
+ rv = GetBaseDomain(currentURI, bottomDomain);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (aURI) {
+ // Determine whether aURI is foreign with respect to currentURI.
+ rv = IsThirdPartyInternal(bottomDomain, aURI, &result);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (result) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> current = nsPIDOMWindowOuter::From(aWindow), parent;
+ nsCOMPtr<nsIURI> parentURI;
+ do {
+ // We use GetScriptableParent rather than GetParent because we consider
+ // <iframe mozbrowser/mozapp> to be a top-level frame.
+ parent = current->GetScriptableParent();
+ if (SameCOMIdentity(parent, current)) {
+ // We're at the topmost content window. We already know the answer.
+ *aResult = false;
+ return NS_OK;
+ }
+
+ rv = GetURIFromWindow(parent, getter_AddRefs(parentURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = IsThirdPartyInternal(bottomDomain, parentURI, &result);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (result) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ current = parent;
+ currentURI = parentURI;
+ } while (1);
+
+ NS_NOTREACHED("should've returned");
+ return NS_ERROR_UNEXPECTED;
+}
+
+// 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;
+ rv = httpChannelInternal->GetThirdPartyFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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;
+
+ // 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;
+
+ nsCString channelDomain;
+ rv = GetBaseDomain(channelURI, channelDomain);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!doForce) {
+ if (nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo()) {
+ parentIsThird = loadInfo->GetIsInThirdPartyContext();
+ if (!parentIsThird &&
+ loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
+ // Check if the channel itself is third-party to its own requestor.
+ // Unforunately, we have to go through the loading principal.
+ nsCOMPtr<nsIURI> parentURI;
+ loadInfo->LoadingPrincipal()->GetURI(getter_AddRefs(parentURI));
+ rv = IsThirdPartyInternal(channelDomain, parentURI, &parentIsThird);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ } else {
+ NS_WARNING("Found channel with no loadinfo, assuming third-party request");
+ parentIsThird = true;
+ }
+ }
+
+ // 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, 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 = nsPIDOMWindowOuter::From(window)->GetTop();
+ 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()) {
+ bool isFileURI = false;
+ aHostURI->SchemeIs("file", &isFileURI);
+ if (!isFileURI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/dom/base/ThirdPartyUtil.h b/dom/base/ThirdPartyUtil.h
new file mode 100644
index 000000000..90e45aac6
--- /dev/null
+++ b/dom/base/ThirdPartyUtil.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 ThirdPartyUtil_h__
+#define ThirdPartyUtil_h__
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsIEffectiveTLDService.h"
+#include "mozilla/Attributes.h"
+
+class nsIURI;
+
+class ThirdPartyUtil final : public mozIThirdPartyUtil
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZITHIRDPARTYUTIL
+
+ nsresult Init();
+
+private:
+ ~ThirdPartyUtil() {}
+
+ nsresult IsThirdPartyInternal(const nsCString& aFirstDomain,
+ nsIURI* aSecondURI, bool* aResult);
+
+ nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+};
+
+#endif
+
diff --git a/dom/base/Timeout.cpp b/dom/base/Timeout.cpp
new file mode 100644
index 000000000..35971a151
--- /dev/null
+++ b/dom/base/Timeout.cpp
@@ -0,0 +1,111 @@
+/* -*- 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 "Timeout.h"
+
+#include "nsGlobalWindow.h"
+#include "nsITimeoutHandler.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace dom {
+
+Timeout::Timeout()
+ : mCleared(false),
+ mRunning(false),
+ mIsInterval(false),
+ mReason(Reason::eTimeoutOrInterval),
+ mTimeoutId(0),
+ mInterval(0),
+ mFiringDepth(0),
+ mNestingLevel(0),
+ mPopupState(openAllowed)
+{
+ MOZ_COUNT_CTOR(Timeout);
+}
+
+Timeout::~Timeout()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ MOZ_COUNT_DTOR(Timeout);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Timeout)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Timeout)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptHandler)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Timeout)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Timeout, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Timeout, Release)
+
+namespace {
+
+void
+TimerCallback(nsITimer*, void* aClosure)
+{
+ RefPtr<Timeout> timeout = (Timeout*)aClosure;
+ timeout->mWindow->RunTimeout(timeout);
+}
+
+void
+TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf, size_t aLen)
+{
+ RefPtr<Timeout> timeout = (Timeout*)aClosure;
+
+ const char* filename;
+ uint32_t lineNum, column;
+ timeout->mScriptHandler->GetLocation(&filename, &lineNum, &column);
+ snprintf(aBuf, aLen, "[content] %s:%u:%u", filename, lineNum, column);
+}
+
+} // anonymous namespace
+
+nsresult
+Timeout::InitTimer(nsIEventTarget* aTarget, uint32_t aDelay)
+{
+ // If the given target does not match the timer's current target
+ // then we need to override it before the Init. Note that GetTarget()
+ // will return the current thread after setting the target to nullptr.
+ // So we need to special case the nullptr target comparison.
+ nsCOMPtr<nsIEventTarget> currentTarget;
+ MOZ_ALWAYS_SUCCEEDS(mTimer->GetTarget(getter_AddRefs(currentTarget)));
+ if ((aTarget && currentTarget != aTarget) ||
+ (!aTarget && currentTarget != NS_GetCurrentThread())) {
+ // Always call Cancel() in case we are re-using a timer. Otherwise
+ // the subsequent SetTarget() may fail.
+ MOZ_ALWAYS_SUCCEEDS(mTimer->Cancel());
+ MOZ_ALWAYS_SUCCEEDS(mTimer->SetTarget(aTarget));
+ }
+
+ return mTimer->InitWithNameableFuncCallback(
+ TimerCallback, this, aDelay, nsITimer::TYPE_ONE_SHOT, TimerNameCallback);
+}
+
+// Return true if this timeout has a refcount of 1. This is used to check
+// that dummy_timeout doesn't leak from nsGlobalWindow::RunTimeout.
+#ifdef DEBUG
+bool
+Timeout::HasRefCntOne() const
+{
+ return mRefCnt.get() == 1;
+}
+#endif // DEBUG
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/Timeout.h b/dom/base/Timeout.h
new file mode 100644
index 000000000..e929f3dd1
--- /dev/null
+++ b/dom/base/Timeout.h
@@ -0,0 +1,103 @@
+/* -*- 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_timeout_h
+#define mozilla_dom_timeout_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsPIDOMWindow.h"
+
+class nsGlobalWindow;
+class nsIPrincipal;
+class nsITimeoutHandler;
+class nsITimer;
+class nsIEventTarget;
+
+namespace mozilla {
+namespace 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
+ : public LinkedListElement<Timeout>
+{
+public:
+ Timeout();
+
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
+
+ // The target may be specified to use a particular event queue for the
+ // resulting timer runnable. A nullptr target will result in the
+ // default main thread being used.
+ nsresult InitTimer(nsIEventTarget* aTarget, uint32_t aDelay);
+
+ enum class Reason { eTimeoutOrInterval, eIdleCallbackTimeout };
+
+#ifdef DEBUG
+ bool HasRefCntOne() const;
+#endif // DEBUG
+
+ // Window for which this timeout fires
+ RefPtr<nsGlobalWindow> mWindow;
+
+ // The actual timer object
+ nsCOMPtr<nsITimer> mTimer;
+
+ // 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;
+
+ Reason mReason;
+
+ // Returned as value of setTimeout()
+ uint32_t mTimeoutId;
+
+ // Interval in milliseconds
+ uint32_t mInterval;
+
+ // 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
+ // suspended.
+ TimeStamp mWhen;
+ // Remaining time to wait. Used only when timeouts are suspended.
+ TimeDuration mTimeRemaining;
+
+ // Principal with which to execute
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ // stack depth at which timeout is firing
+ uint32_t mFiringDepth;
+
+ uint32_t mNestingLevel;
+
+ // The popup state at timeout creation time if not created from
+ // another timeout
+ PopupControlState mPopupState;
+
+ // The language-specific information about the callback.
+ nsCOMPtr<nsITimeoutHandler> mScriptHandler;
+
+private:
+ ~Timeout();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_timeout_h
diff --git a/dom/base/TreeWalker.cpp b/dom/base/TreeWalker.cpp
new file mode 100644
index 000000000..784509b00
--- /dev/null
+++ b/dom/base/TreeWalker.cpp
@@ -0,0 +1,449 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMTreeWalker
+ */
+
+#include "mozilla/dom/TreeWalker.h"
+
+#include "nsIContent.h"
+#include "nsIDOMNode.h"
+#include "nsError.h"
+#include "nsINode.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/TreeWalkerBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * Factories, constructors and destructors
+ */
+
+TreeWalker::TreeWalker(nsINode *aRoot,
+ uint32_t aWhatToShow,
+ NodeFilterHolder aFilter) :
+ nsTraversal(aRoot, aWhatToShow, Move(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(nsIDOMTreeWalker)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMTreeWalker)
+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)
+
+
+
+/*
+ * nsIDOMTreeWalker Getters/Setters
+ */
+
+NS_IMETHODIMP TreeWalker::GetRoot(nsIDOMNode * *aRoot)
+{
+ NS_ADDREF(*aRoot = Root()->AsDOMNode());
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeWalker::GetWhatToShow(uint32_t *aWhatToShow)
+{
+ *aWhatToShow = WhatToShow();
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeWalker::GetFilter(nsIDOMNodeFilter * *aFilter)
+{
+ NS_ENSURE_ARG_POINTER(aFilter);
+
+ *aFilter = mFilter.ToXPCOMCallback().take();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP TreeWalker::GetCurrentNode(nsIDOMNode * *aCurrentNode)
+{
+ if (mCurrentNode) {
+ return CallQueryInterface(mCurrentNode, aCurrentNode);
+ }
+
+ *aCurrentNode = nullptr;
+
+ return NS_OK;
+}
+NS_IMETHODIMP TreeWalker::SetCurrentNode(nsIDOMNode * aCurrentNode)
+{
+ NS_ENSURE_TRUE(aCurrentNode, NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ NS_ENSURE_TRUE(mRoot, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsINode> node = do_QueryInterface(aCurrentNode);
+ NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ SetCurrentNode(*node, rv);
+ return rv.StealNSResult();
+}
+
+void
+TreeWalker::SetCurrentNode(nsINode& aNode, ErrorResult& aResult)
+{
+ aResult = nsContentUtils::CheckSameOrigin(mRoot, &aNode);
+ if (aResult.Failed()) {
+ return;
+ }
+
+ mCurrentNode = &aNode;
+}
+
+/*
+ * nsIDOMTreeWalker functions
+ */
+
+NS_IMETHODIMP TreeWalker::ParentNode(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&TreeWalker::ParentNode, _retval);
+}
+
+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 == nsIDOMNodeFilter::FILTER_ACCEPT) {
+ mCurrentNode = node;
+ return node.forget();
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP TreeWalker::FirstChild(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&TreeWalker::FirstChild, _retval);
+}
+
+already_AddRefed<nsINode>
+TreeWalker::FirstChild(ErrorResult& aResult)
+{
+ return FirstChildInternal(false, aResult);
+}
+
+NS_IMETHODIMP TreeWalker::LastChild(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&TreeWalker::LastChild, _retval);
+}
+
+already_AddRefed<nsINode>
+TreeWalker::LastChild(ErrorResult& aResult)
+{
+ return FirstChildInternal(true, aResult);
+}
+
+NS_IMETHODIMP TreeWalker::PreviousSibling(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&TreeWalker::PreviousSibling, _retval);
+}
+
+already_AddRefed<nsINode>
+TreeWalker::PreviousSibling(ErrorResult& aResult)
+{
+ return NextSiblingInternal(true, aResult);
+}
+
+NS_IMETHODIMP TreeWalker::NextSibling(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&TreeWalker::NextSibling, _retval);
+}
+
+already_AddRefed<nsINode>
+TreeWalker::NextSibling(ErrorResult& aResult)
+{
+ return NextSiblingInternal(false, aResult);
+}
+
+NS_IMETHODIMP TreeWalker::PreviousNode(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&TreeWalker::PreviousNode, _retval);
+}
+
+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 != nsIDOMNodeFilter::FILTER_REJECT &&
+ (lastChild = node->GetLastChild())) {
+ node = lastChild;
+ filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+ }
+
+ if (filtered == nsIDOMNodeFilter::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 == nsIDOMNodeFilter::FILTER_ACCEPT) {
+ mCurrentNode = node;
+ return node.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP TreeWalker::NextNode(nsIDOMNode **_retval)
+{
+ return ImplNodeGetter(&TreeWalker::NextNode, _retval);
+}
+
+already_AddRefed<nsINode>
+TreeWalker::NextNode(ErrorResult& aResult)
+{
+ int16_t filtered = nsIDOMNodeFilter::FILTER_ACCEPT; // pre-init for inner loop
+
+ nsCOMPtr<nsINode> node = mCurrentNode;
+
+ while (1) {
+
+ nsINode *firstChild;
+ while (filtered != nsIDOMNodeFilter::FILTER_REJECT &&
+ (firstChild = node->GetFirstChild())) {
+ node = firstChild;
+
+ filtered = TestNode(node, aResult);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+
+ if (filtered == nsIDOMNodeFilter::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 == nsIDOMNodeFilter::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 nsIDOMNodeFilter::FILTER_ACCEPT:
+ // Node found
+ mCurrentNode = node;
+ return node.forget();
+ case nsIDOMNodeFilter::FILTER_SKIP: {
+ nsINode *child = aReversed ? node->GetLastChild()
+ : node->GetFirstChild();
+ if (child) {
+ node = child;
+ continue;
+ }
+ break;
+ }
+ case nsIDOMNodeFilter::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 == nsIDOMNodeFilter::FILTER_ACCEPT) {
+ // Node found
+ mCurrentNode = node;
+ return node.forget();
+ }
+
+ // If rejected or no children, try a sibling
+ if (filtered == nsIDOMNodeFilter::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 == nsIDOMNodeFilter::FILTER_ACCEPT) {
+ return nullptr;
+ }
+ }
+}
+
+bool
+TreeWalker::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector)
+{
+ return TreeWalkerBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/TreeWalker.h b/dom/base/TreeWalker.h
new file mode 100644
index 000000000..a834ab449
--- /dev/null
+++ b/dom/base/TreeWalker.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMTreeWalker
+ */
+
+#ifndef mozilla_dom_TreeWalker_h
+#define mozilla_dom_TreeWalker_h
+
+#include "nsIDOMTreeWalker.h"
+#include "nsTraversal.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsINode;
+class nsIDOMNode;
+
+namespace mozilla {
+namespace dom {
+
+class TreeWalker final : public nsIDOMTreeWalker, public nsTraversal
+{
+ virtual ~TreeWalker();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIDOMTREEWALKER
+
+ TreeWalker(nsINode *aRoot,
+ uint32_t aWhatToShow,
+ NodeFilterHolder aFilter);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(TreeWalker)
+
+ // WebIDL API
+ nsINode* Root() const
+ {
+ return mRoot;
+ }
+ uint32_t WhatToShow() const
+ {
+ return mWhatToShow;
+ }
+ already_AddRefed<NodeFilter> GetFilter()
+ {
+ return mFilter.ToWebIDLCallback();
+ }
+ 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);
+
+ // Implementation for our various XPCOM getters
+ typedef already_AddRefed<nsINode> (TreeWalker::*NodeGetter)(ErrorResult&);
+ inline nsresult ImplNodeGetter(NodeGetter aGetter, nsIDOMNode** aRetval)
+ {
+ mozilla::ErrorResult rv;
+ nsCOMPtr<nsINode> node = (this->*aGetter)(rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ *aRetval = node ? node.forget().take()->AsDOMNode() : nullptr;
+ return NS_OK;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TreeWalker_h
+
diff --git a/dom/base/UseCounter.h b/dom/base/UseCounter.h
new file mode 100644
index 000000000..01cdb9969
--- /dev/null
+++ b/dom/base/UseCounter.h
@@ -0,0 +1,36 @@
+/* -*- 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 UseCounter_h_
+#define UseCounter_h_
+
+#include <stdint.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_CSS_PROPERTY(name_, id_) \
+ eUseCounter_property_##id_,
+#include "mozilla/dom/UseCounterList.h"
+#undef USE_COUNTER_DOM_METHOD
+#undef USE_COUNTER_DOM_ATTRIBUTE
+#undef USE_COUNTER_CSS_PROPERTY
+
+#define DEPRECATED_OPERATION(op_) \
+ eUseCounter_##op_,
+#include "nsDeprecatedOperationList.h"
+#undef DEPRECATED_OPERATION
+
+ eUseCounter_Count
+};
+
+}
+
+#endif
diff --git a/dom/base/UseCounters.conf b/dom/base/UseCounters.conf
new file mode 100644
index 000000000..f9202b629
--- /dev/null
+++ b/dom/base/UseCounters.conf
@@ -0,0 +1,63 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.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>
+// property <CSS property method name>
+//
+// The |CSS property method name| should be identical to the |method|
+// argument to CSS_PROP and related macros. The method name is
+// identical to the name of the property, except that all hyphens are
+// removed and CamelCase naming is used. See nsCSSPropList.h for
+// further details.
+//
+// To actually cause use counters to be incremented, DOM methods
+// and attributes must have a [UseCounter] extended attribute in
+// the Web IDL file. CSS properties require no special treatment
+// beyond being listed below.
+//
+// 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, and
+// annotating the WebIDL files does nothing for identifying CSS
+// property usage, which we would also like to track.
+
+method SVGSVGElement.getElementById
+attribute SVGSVGElement.currentScale
+property Fill
+property FillOpacity
+
+// Push API
+method PushManager.subscribe
+method PushSubscription.unsubscribe
+
+// window.sidebar.addSearchEngine
+attribute Window.sidebar
+method External.addSearchEngine
+
+// AppCache API
+method OfflineResourceList.swapCache
+method OfflineResourceList.update
+attribute OfflineResourceList.status
+attribute OfflineResourceList.onchecking
+attribute OfflineResourceList.onerror
+attribute OfflineResourceList.onnoupdate
+attribute OfflineResourceList.ondownloading
+attribute OfflineResourceList.onprogress
+attribute OfflineResourceList.onupdateready
+attribute OfflineResourceList.oncached
+attribute OfflineResourceList.onobsolete
diff --git a/dom/base/WebKitCSSMatrix.cpp b/dom/base/WebKitCSSMatrix.cpp
new file mode 100644
index 000000000..2cb6d6bf0
--- /dev/null
+++ b/dom/base/WebKitCSSMatrix.cpp
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/WebKitCSSMatrix.h"
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/WebKitCSSMatrixBinding.h"
+#include "mozilla/Preferences.h"
+#include "nsCSSParser.h"
+#include "nsStyleTransformMatrix.h"
+#include "RuleNodeCacheConditions.h"
+
+namespace mozilla {
+namespace dom {
+
+static const double sRadPerDegree = 2.0 * M_PI / 360.0;
+
+bool
+WebKitCSSMatrix::FeatureEnabled(JSContext* aCx, JSObject* aObj)
+{
+ return Preferences::GetBool("layout.css.DOMMatrix.enabled", false) &&
+ Preferences::GetBool("layout.css.prefixes.webkit", false);
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ RefPtr<WebKitCSSMatrix> obj = new WebKitCSSMatrix(aGlobal.GetAsSupports());
+ return obj.forget();
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aTransformList, ErrorResult& aRv)
+{
+ RefPtr<WebKitCSSMatrix> obj = new WebKitCSSMatrix(aGlobal.GetAsSupports());
+ obj = obj->SetMatrixValue(aTransformList, aRv);
+ return obj.forget();
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::Constructor(const GlobalObject& aGlobal,
+ const DOMMatrixReadOnly& aOther, ErrorResult& aRv)
+{
+ RefPtr<WebKitCSSMatrix> obj = new WebKitCSSMatrix(aGlobal.GetAsSupports(),
+ aOther);
+ return obj.forget();
+}
+
+JSObject*
+WebKitCSSMatrix::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WebKitCSSMatrixBinding::Wrap(aCx, this, aGivenProto);
+}
+
+WebKitCSSMatrix*
+WebKitCSSMatrix::SetMatrixValue(const nsAString& aTransformList,
+ ErrorResult& aRv)
+{
+ // An empty string is a no-op.
+ if (aTransformList.IsEmpty()) {
+ return this;
+ }
+
+ nsCSSValue value;
+ nsCSSParser parser;
+ bool parseSuccess = parser.ParseTransformProperty(aTransformList,
+ true,
+ value);
+ if (!parseSuccess) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ // A value of "none" results in a 2D identity matrix.
+ if (value.GetUnit() == eCSSUnit_None) {
+ mMatrix3D = nullptr;
+ mMatrix2D = new gfx::Matrix();
+ return this;
+ }
+
+ // A value other than a transform-list is a syntax error.
+ if (value.GetUnit() != eCSSUnit_SharedList) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ RuleNodeCacheConditions dummy;
+ nsStyleTransformMatrix::TransformReferenceBox dummyBox;
+ bool contains3dTransform = false;
+ gfx::Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms(
+ value.GetSharedListValue()->mHead,
+ nullptr, nullptr, dummy, dummyBox,
+ nsPresContext::AppUnitsPerCSSPixel(),
+ &contains3dTransform);
+
+ if (!contains3dTransform) {
+ mMatrix3D = nullptr;
+ mMatrix2D = new gfx::Matrix();
+
+ SetA(transform._11);
+ SetB(transform._12);
+ SetC(transform._21);
+ SetD(transform._22);
+ SetE(transform._41);
+ SetF(transform._42);
+ } else {
+ mMatrix3D = new gfx::Matrix4x4(transform);
+ mMatrix2D = nullptr;
+ }
+
+ return this;
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::Multiply(const WebKitCSSMatrix& other) const
+{
+ RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
+ retval->MultiplySelf(other);
+
+ return retval.forget();
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::Inverse(ErrorResult& aRv) const
+{
+ RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
+ retval->InvertSelfThrow(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return retval.forget();
+}
+
+WebKitCSSMatrix*
+WebKitCSSMatrix::InvertSelfThrow(ErrorResult& aRv)
+{
+ if (mMatrix3D) {
+ if (!mMatrix3D->Invert()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ } else if (!mMatrix2D->Invert()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ return this;
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::Translate(double aTx,
+ double aTy,
+ double aTz) const
+{
+ RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
+ retval->TranslateSelf(aTx, aTy, aTz);
+
+ return retval.forget();
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::Scale(double aScaleX,
+ const Optional<double>& aScaleY,
+ double aScaleZ) const
+{
+ double scaleX = aScaleX;
+ double scaleY = aScaleY.WasPassed() ? aScaleY.Value() : scaleX;
+ double scaleZ = aScaleZ;
+
+ RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
+ retval->ScaleNonUniformSelf(scaleX, scaleY, scaleZ);
+
+ return retval.forget();
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::Rotate(double aRotX,
+ const Optional<double>& aRotY,
+ const Optional<double>& aRotZ) const
+{
+ double rotX = aRotX;
+ double rotY;
+ double rotZ;
+
+ if (!aRotY.WasPassed() && !aRotZ.WasPassed()) {
+ rotZ = rotX;
+ rotX = 0;
+ rotY = 0;
+ } else {
+ rotY = aRotY.WasPassed() ? aRotY.Value() : 0;
+ rotZ = aRotZ.WasPassed() ? aRotZ.Value() : 0;
+ }
+
+ RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
+ retval->Rotate3dSelf(rotX, rotY, rotZ);
+
+ return retval.forget();
+}
+
+WebKitCSSMatrix*
+WebKitCSSMatrix::Rotate3dSelf(double aRotX,
+ double aRotY,
+ double aRotZ)
+{
+ if (aRotX != 0 || aRotY != 0) {
+ Ensure3DMatrix();
+ }
+
+ if (mMatrix3D) {
+ if (fmod(aRotZ, 360) != 0) {
+ mMatrix3D->RotateZ(aRotZ * sRadPerDegree);
+ }
+ if (fmod(aRotY, 360) != 0) {
+ mMatrix3D->RotateY(aRotY * sRadPerDegree);
+ }
+ if (fmod(aRotX, 360) != 0) {
+ mMatrix3D->RotateX(aRotX * sRadPerDegree);
+ }
+ } else if (fmod(aRotZ, 360) != 0) {
+ mMatrix2D->PreRotate(aRotZ * sRadPerDegree);
+ }
+
+ return this;
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::RotateAxisAngle(double aX,
+ double aY,
+ double aZ,
+ double aAngle) const
+{
+ RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
+ retval->RotateAxisAngleSelf(aX, aY, aZ, aAngle);
+
+ return retval.forget();
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::SkewX(double aSx) const
+{
+ RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
+ retval->SkewXSelf(aSx);
+
+ return retval.forget();
+}
+
+already_AddRefed<WebKitCSSMatrix>
+WebKitCSSMatrix::SkewY(double aSy) const
+{
+ RefPtr<WebKitCSSMatrix> retval = new WebKitCSSMatrix(mParent, *this);
+ retval->SkewYSelf(aSy);
+
+ return retval.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/WebKitCSSMatrix.h b/dom/base/WebKitCSSMatrix.h
new file mode 100644
index 000000000..4b2adf9e0
--- /dev/null
+++ b/dom/base/WebKitCSSMatrix.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_webkitcssmatrix_h__
+#define mozilla_dom_webkitcssmatrix_h__
+
+#include "mozilla/dom/DOMMatrix.h"
+
+namespace mozilla {
+namespace dom {
+
+class WebKitCSSMatrix final : public DOMMatrix
+{
+public:
+ explicit WebKitCSSMatrix(nsISupports* aParent)
+ : DOMMatrix(aParent)
+ {}
+
+ WebKitCSSMatrix(nsISupports* aParent, const DOMMatrixReadOnly& other)
+ : DOMMatrix(aParent, other)
+ {}
+
+ static bool FeatureEnabled(JSContext* aCx, JSObject* aObj);
+
+ static already_AddRefed<WebKitCSSMatrix>
+ Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
+ static already_AddRefed<WebKitCSSMatrix>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aTransformList, ErrorResult& aRv);
+ static already_AddRefed<WebKitCSSMatrix>
+ Constructor(const GlobalObject& aGlobal,
+ const DOMMatrixReadOnly& aOther, ErrorResult& aRv);
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ WebKitCSSMatrix* SetMatrixValue(const nsAString& aTransformList,
+ ErrorResult& aRv);
+
+ already_AddRefed<WebKitCSSMatrix> Multiply(const WebKitCSSMatrix& aOther) const;
+ already_AddRefed<WebKitCSSMatrix> Inverse(ErrorResult& aRv) const;
+ already_AddRefed<WebKitCSSMatrix> Translate(double aTx,
+ double aTy,
+ double aTz) const;
+ already_AddRefed<WebKitCSSMatrix> Scale(double aScaleX,
+ const Optional<double>& aScaleY,
+ double aScaleZ) const;
+ already_AddRefed<WebKitCSSMatrix> Rotate(double aRotX,
+ const Optional<double>& aRotY,
+ const Optional<double>& aRotZ) const;
+ already_AddRefed<WebKitCSSMatrix> RotateAxisAngle(double aX,
+ double aY,
+ double aZ,
+ double aAngle) const;
+ already_AddRefed<WebKitCSSMatrix> SkewX(double aSx) const;
+ already_AddRefed<WebKitCSSMatrix> SkewY(double aSy) const;
+protected:
+ WebKitCSSMatrix* Rotate3dSelf(double aRotX,
+ double aRotY,
+ double aRotZ);
+
+ WebKitCSSMatrix* InvertSelfThrow(ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_webkitcssmatrix_h__ */
diff --git a/dom/base/WebSocket.cpp b/dom/base/WebSocket.cpp
new file mode 100644
index 000000000..d85bae82b
--- /dev/null
+++ b/dom/base/WebSocket.cpp
@@ -0,0 +1,2908 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "WebSocket.h"
+#include "mozilla/dom/WebSocketBinding.h"
+#include "mozilla/net/WebSocketChannel.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/net/WebSocketChannel.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "nsAutoPtr.h"
+#include "nsGlobalWindow.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsXPCOM.h"
+#include "nsIXPConnect.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIURL.h"
+#include "nsIUnicodeEncoder.h"
+#include "nsThreadUtils.h"
+#include "nsIPromptFactory.h"
+#include "nsIWindowWatcher.h"
+#include "nsIPrompt.h"
+#include "nsIStringBundle.h"
+#include "nsIConsoleService.h"
+#include "mozilla/dom/CloseEvent.h"
+#include "mozilla/net/WebSocketEventService.h"
+#include "nsICryptoHash.h"
+#include "nsJSUtils.h"
+#include "nsIScriptError.h"
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsILoadGroup.h"
+#include "mozilla/Preferences.h"
+#include "xpcpublic.h"
+#include "nsContentPolicyUtils.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsIObserverService.h"
+#include "nsIEventTarget.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsIRequest.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIWebSocketChannel.h"
+#include "nsIWebSocketListener.h"
+#include "nsProxyRelease.h"
+#include "nsWeakReference.h"
+
+using namespace mozilla::net;
+using namespace mozilla::dom::workers;
+
+namespace mozilla {
+namespace dom {
+
+class WebSocketImpl final : public nsIInterfaceRequestor
+ , public nsIWebSocketListener
+ , public nsIObserver
+ , public nsSupportsWeakReference
+ , public nsIRequest
+ , public nsIEventTarget
+{
+public:
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBSOCKETLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIREQUEST
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET
+ using nsIEventTarget::Dispatch;
+
+ explicit WebSocketImpl(WebSocket* aWebSocket)
+ : mWebSocket(aWebSocket)
+ , mIsServerSide(false)
+ , mSecure(false)
+ , mOnCloseScheduled(false)
+ , mFailed(false)
+ , mDisconnectingOrDisconnected(false)
+ , mCloseEventWasClean(false)
+ , mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL)
+ , mScriptLine(0)
+ , mScriptColumn(0)
+ , mInnerWindowID(0)
+ , mWorkerPrivate(nullptr)
+#ifdef DEBUG
+ , mHasWorkerHolderRegistered(false)
+#endif
+ , mIsMainThread(true)
+ , mMutex("WebSocketImpl::mMutex")
+ , mWorkerShuttingDown(false)
+ {
+ if (!NS_IsMainThread()) {
+ mWorkerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(mWorkerPrivate);
+ mIsMainThread = false;
+ }
+ }
+
+ void AssertIsOnTargetThread() const
+ {
+ MOZ_ASSERT(IsTargetThread());
+ }
+
+ bool IsTargetThread() const;
+
+ void Init(JSContext* aCx,
+ nsIPrincipal* aPrincipal,
+ bool aIsServerSide,
+ const nsAString& aURL,
+ nsTArray<nsString>& aProtocolArray,
+ const nsACString& aScriptFile,
+ uint32_t aScriptLine,
+ uint32_t aScriptColumn,
+ ErrorResult& aRv,
+ bool* aConnectionFailed);
+
+ void AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
+ nsITransportProvider* aTransportProvider,
+ const nsACString& aNegotiatedExtensions,
+ ErrorResult& aRv);
+
+ nsresult ParseURL(const nsAString& aURL);
+ nsresult InitializeConnection(nsIPrincipal* aPrincipal);
+
+ // These methods when called can release the WebSocket object
+ void FailConnection(uint16_t reasonCode,
+ const nsACString& aReasonString = EmptyCString());
+ nsresult CloseConnection(uint16_t reasonCode,
+ const nsACString& aReasonString = EmptyCString());
+ void Disconnect();
+ void DisconnectInternal();
+
+ nsresult ConsoleError();
+ void PrintErrorOnConsole(const char* aBundleURI,
+ const char16_t* aError,
+ const char16_t** aFormatStrings,
+ uint32_t aFormatStringsLen);
+
+ nsresult DoOnMessageAvailable(const nsACString& aMsg,
+ bool isBinary);
+
+ // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
+ nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
+ nsresult aStatusCode);
+ // 2nd half of ScheduleConnectionCloseEvents, run in its own event.
+ void DispatchConnectionCloseEvents();
+
+ nsresult UpdateURI();
+
+ void AddRefObject();
+ void ReleaseObject();
+
+ bool RegisterWorkerHolder();
+ void UnregisterWorkerHolder();
+
+ nsresult CancelInternal();
+
+ RefPtr<WebSocket> mWebSocket;
+
+ nsCOMPtr<nsIWebSocketChannel> mChannel;
+
+ bool mIsServerSide; // True if we're implementing the server side of a
+ // websocket connection
+
+ bool mSecure; // if true it is using SSL and the wss scheme,
+ // otherwise it is using the ws scheme with no SSL
+
+ bool mOnCloseScheduled;
+ bool mFailed;
+ bool mDisconnectingOrDisconnected;
+
+ // Set attributes of DOM 'onclose' message
+ bool mCloseEventWasClean;
+ nsString mCloseEventReason;
+ uint16_t mCloseEventCode;
+
+ nsCString mAsciiHost; // hostname
+ uint32_t mPort;
+ nsCString mResource; // [filepath[?query]]
+ nsString mUTF16Origin;
+
+ nsCString mURI;
+ nsCString mRequestedProtocolList;
+
+ nsWeakPtr mOriginDocument;
+
+ // Web Socket owner information:
+ // - the script file name, UTF8 encoded.
+ // - source code line number and column number where the Web Socket object
+ // was constructed.
+ // - the ID of the inner window where the script lives. Note that this may not
+ // be the same as the Web Socket owner window.
+ // These attributes are used for error reporting.
+ nsCString mScriptFile;
+ uint32_t mScriptLine;
+ uint32_t mScriptColumn;
+ uint64_t mInnerWindowID;
+
+ WorkerPrivate* mWorkerPrivate;
+ nsAutoPtr<WorkerHolder> mWorkerHolder;
+
+#ifdef DEBUG
+ // This is protected by mutex.
+ bool mHasWorkerHolderRegistered;
+
+ bool HasWorkerHolderRegistered()
+ {
+ MOZ_ASSERT(mWebSocket);
+ MutexAutoLock lock(mWebSocket->mMutex);
+ return mHasWorkerHolderRegistered;
+ }
+
+ void SetHasWorkerHolderRegistered(bool aValue)
+ {
+ MOZ_ASSERT(mWebSocket);
+ MutexAutoLock lock(mWebSocket->mMutex);
+ mHasWorkerHolderRegistered = aValue;
+ }
+#endif
+
+ nsWeakPtr mWeakLoadGroup;
+
+ bool mIsMainThread;
+
+ // This mutex protects mWorkerShuttingDown.
+ mozilla::Mutex mMutex;
+ bool mWorkerShuttingDown;
+
+ RefPtr<WebSocketEventService> mService;
+
+private:
+ ~WebSocketImpl()
+ {
+ // If we threw during Init we never called disconnect
+ if (!mDisconnectingOrDisconnected) {
+ Disconnect();
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS(WebSocketImpl,
+ nsIInterfaceRequestor,
+ nsIWebSocketListener,
+ nsIObserver,
+ nsISupportsWeakReference,
+ nsIRequest,
+ nsIEventTarget)
+
+class CallDispatchConnectionCloseEvents final : public CancelableRunnable
+{
+public:
+ explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
+ : mWebSocketImpl(aWebSocketImpl)
+ {
+ aWebSocketImpl->AssertIsOnTargetThread();
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mWebSocketImpl->AssertIsOnTargetThread();
+ mWebSocketImpl->DispatchConnectionCloseEvents();
+ return NS_OK;
+ }
+
+private:
+ RefPtr<WebSocketImpl> mWebSocketImpl;
+};
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable
+{
+public:
+ PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl,
+ const char* aBundleURI,
+ const char16_t* aError,
+ const char16_t** aFormatStrings,
+ uint32_t aFormatStringsLen)
+ : WorkerMainThreadRunnable(aImpl->mWorkerPrivate,
+ NS_LITERAL_CSTRING("WebSocket :: print error on console"))
+ , mImpl(aImpl)
+ , mBundleURI(aBundleURI)
+ , mError(aError)
+ , mFormatStrings(aFormatStrings)
+ , mFormatStringsLen(aFormatStringsLen)
+ { }
+
+ bool MainThreadRun() override
+ {
+ mImpl->PrintErrorOnConsole(mBundleURI, mError, mFormatStrings,
+ mFormatStringsLen);
+ return true;
+ }
+
+private:
+ // Raw pointer because this runnable is sync.
+ WebSocketImpl* mImpl;
+
+ const char* mBundleURI;
+ const char16_t* mError;
+ const char16_t** mFormatStrings;
+ uint32_t mFormatStringsLen;
+};
+
+} // namespace
+
+void
+WebSocketImpl::PrintErrorOnConsole(const char *aBundleURI,
+ const char16_t *aError,
+ const char16_t **aFormatStrings,
+ uint32_t aFormatStringsLen)
+{
+ // This method must run on the main thread.
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(mWorkerPrivate);
+
+ RefPtr<PrintErrorOnConsoleRunnable> runnable =
+ new PrintErrorOnConsoleRunnable(this, aBundleURI, aError, aFormatStrings,
+ aFormatStringsLen);
+ ErrorResult rv;
+ runnable->Dispatch(rv);
+ // XXXbz this seems totally broken. We should be propagating this out, but
+ // none of our callers really propagate anything usefully. Come to think of
+ // it, why is this a syncrunnable anyway? Can't this be a fire-and-forget
+ // runnable??
+ rv.SuppressException();
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIStringBundle> strBundle;
+ rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIScriptError> errorObject(
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Localize the error message
+ nsXPIDLString message;
+ if (aFormatStrings) {
+ rv = strBundle->FormatStringFromName(aError, aFormatStrings,
+ aFormatStringsLen,
+ getter_Copies(message));
+ } else {
+ rv = strBundle->GetStringFromName(aError, getter_Copies(message));
+ }
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (mInnerWindowID) {
+ rv = errorObject->InitWithWindowID(message,
+ NS_ConvertUTF8toUTF16(mScriptFile),
+ EmptyString(), mScriptLine,
+ mScriptColumn,
+ nsIScriptError::errorFlag, "Web Socket",
+ mInnerWindowID);
+ } else {
+ rv = errorObject->Init(message,
+ NS_ConvertUTF8toUTF16(mScriptFile),
+ EmptyString(), mScriptLine, mScriptColumn,
+ nsIScriptError::errorFlag, "Web Socket");
+ }
+
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // print the error message directly to the JS console
+ rv = console->LogMessage(errorObject);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+namespace {
+
+class CancelWebSocketRunnable final : public Runnable
+{
+public:
+ CancelWebSocketRunnable(nsIWebSocketChannel* aChannel, uint16_t aReasonCode,
+ const nsACString& aReasonString)
+ : mChannel(aChannel)
+ , mReasonCode(aReasonCode)
+ , mReasonString(aReasonString)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mChannel->Close(mReasonCode, mReasonString);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIWebSocketChannel> mChannel;
+ uint16_t mReasonCode;
+ nsCString mReasonString;
+};
+
+class MOZ_STACK_CLASS MaybeDisconnect
+{
+public:
+ explicit MaybeDisconnect(WebSocketImpl* aImpl)
+ : mImpl(aImpl)
+ {
+ }
+
+ ~MaybeDisconnect()
+ {
+ bool toDisconnect = false;
+
+ {
+ MutexAutoLock lock(mImpl->mMutex);
+ toDisconnect = mImpl->mWorkerShuttingDown;
+ }
+
+ if (toDisconnect) {
+ mImpl->Disconnect();
+ }
+ }
+
+private:
+ WebSocketImpl* mImpl;
+};
+
+class CloseConnectionRunnable final : public Runnable
+{
+public:
+ CloseConnectionRunnable(WebSocketImpl* aImpl,
+ uint16_t aReasonCode,
+ const nsACString& aReasonString)
+ : mImpl(aImpl)
+ , mReasonCode(aReasonCode)
+ , mReasonString(aReasonString)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ return mImpl->CloseConnection(mReasonCode, mReasonString);
+ }
+
+private:
+ RefPtr<WebSocketImpl> mImpl;
+ uint16_t mReasonCode;
+ const nsCString mReasonString;
+};
+
+} // namespace
+
+nsresult
+WebSocketImpl::CloseConnection(uint16_t aReasonCode,
+ const nsACString& aReasonString)
+{
+ if (!IsTargetThread()) {
+ nsCOMPtr<nsIRunnable> runnable =
+ new CloseConnectionRunnable(this, aReasonCode, aReasonString);
+ return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ }
+
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ // If this method is called because the worker is going away, we will not
+ // receive the OnStop() method and we have to disconnect the WebSocket and
+ // release the WorkerHolder.
+ MaybeDisconnect md(this);
+
+ uint16_t readyState = mWebSocket->ReadyState();
+ if (readyState == WebSocket::CLOSING ||
+ readyState == WebSocket::CLOSED) {
+ return NS_OK;
+ }
+
+ // The common case...
+ if (mChannel) {
+ mWebSocket->SetReadyState(WebSocket::CLOSING);
+
+ // The channel has to be closed on the main-thread.
+
+ if (NS_IsMainThread()) {
+ return mChannel->Close(aReasonCode, aReasonString);
+ }
+
+ RefPtr<CancelWebSocketRunnable> runnable =
+ new CancelWebSocketRunnable(mChannel, aReasonCode, aReasonString);
+ return NS_DispatchToMainThread(runnable);
+ }
+
+ // No channel, but not disconnected: canceled or failed early
+ MOZ_ASSERT(readyState == WebSocket::CONNECTING,
+ "Should only get here for early websocket cancel/error");
+
+ // Server won't be sending us a close code, so use what's passed in here.
+ mCloseEventCode = aReasonCode;
+ CopyUTF8toUTF16(aReasonString, mCloseEventReason);
+
+ mWebSocket->SetReadyState(WebSocket::CLOSING);
+
+ ScheduleConnectionCloseEvents(
+ nullptr,
+ (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
+ aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ?
+ NS_OK : NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsresult
+WebSocketImpl::ConsoleError()
+{
+ AssertIsOnTargetThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mWorkerShuttingDown) {
+ // Too late to report anything, bail out.
+ return NS_OK;
+ }
+ }
+
+ NS_ConvertUTF8toUTF16 specUTF16(mURI);
+ const char16_t* formatStrings[] = { specUTF16.get() };
+
+ if (mWebSocket->ReadyState() < WebSocket::OPEN) {
+ PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ u"connectionFailure",
+ formatStrings, ArrayLength(formatStrings));
+ } else {
+ PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ u"netInterrupt",
+ formatStrings, ArrayLength(formatStrings));
+ }
+ /// todo some specific errors - like for message too large
+ return NS_OK;
+}
+
+void
+WebSocketImpl::FailConnection(uint16_t aReasonCode,
+ const nsACString& aReasonString)
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return;
+ }
+
+ ConsoleError();
+ mFailed = true;
+ CloseConnection(aReasonCode, aReasonString);
+}
+
+namespace {
+
+class DisconnectInternalRunnable final : public WorkerMainThreadRunnable
+{
+public:
+ explicit DisconnectInternalRunnable(WebSocketImpl* aImpl)
+ : WorkerMainThreadRunnable(aImpl->mWorkerPrivate,
+ NS_LITERAL_CSTRING("WebSocket :: disconnect"))
+ , mImpl(aImpl)
+ { }
+
+ bool MainThreadRun() override
+ {
+ mImpl->DisconnectInternal();
+ return true;
+ }
+
+private:
+ // A raw pointer because this runnable is sync.
+ WebSocketImpl* mImpl;
+};
+
+} // namespace
+
+void
+WebSocketImpl::Disconnect()
+{
+ if (mDisconnectingOrDisconnected) {
+ return;
+ }
+
+ AssertIsOnTargetThread();
+
+ // DontKeepAliveAnyMore() and DisconnectInternal() can release the object. So
+ // hold a reference to this until the end of the method.
+ RefPtr<WebSocketImpl> kungfuDeathGrip = this;
+
+ // Disconnect can be called from some control event (such as Notify() of
+ // WorkerHolder). This will be schedulated before any other sync/async
+ // runnable. In order to prevent some double Disconnect() calls, we use this
+ // boolean.
+ mDisconnectingOrDisconnected = true;
+
+ // DisconnectInternal touches observers and nsILoadGroup and it must run on
+ // the main thread.
+
+ if (NS_IsMainThread()) {
+ DisconnectInternal();
+ } else {
+ RefPtr<DisconnectInternalRunnable> runnable =
+ new DisconnectInternalRunnable(this);
+ ErrorResult rv;
+ runnable->Dispatch(rv);
+ // XXXbz this seems totally broken. We should be propagating this out, but
+ // where to, exactly?
+ rv.SuppressException();
+ }
+
+ NS_ReleaseOnMainThread(mChannel.forget());
+ NS_ReleaseOnMainThread(mService.forget());
+
+ mWebSocket->DontKeepAliveAnyMore();
+ mWebSocket->mImpl = nullptr;
+
+ if (mWorkerPrivate && mWorkerHolder) {
+ UnregisterWorkerHolder();
+ }
+
+ // We want to release the WebSocket in the correct thread.
+ mWebSocket = nullptr;
+}
+
+void
+WebSocketImpl::DisconnectInternal()
+{
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup);
+ if (loadGroup) {
+ loadGroup->RemoveRequest(this, nullptr, NS_OK);
+ // mWeakLoadGroup has to be release on main-thread because WeakReferences
+ // are not thread-safe.
+ mWeakLoadGroup = nullptr;
+ }
+
+ if (!mWorkerPrivate) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
+ os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIWebSocketListener methods:
+//-----------------------------------------------------------------------------
+
+nsresult
+WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ int16_t readyState = mWebSocket->ReadyState();
+ if (readyState == WebSocket::CLOSED) {
+ NS_ERROR("Received message after CLOSED");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (readyState == WebSocket::OPEN) {
+ // Dispatch New Message
+ nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the message event");
+ }
+
+ return NS_OK;
+ }
+
+ // CLOSING should be the only other state where it's possible to get msgs
+ // from channel: Spec says to drop them.
+ MOZ_ASSERT(readyState == WebSocket::CLOSING,
+ "Received message while CONNECTING or CLOSED");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg)
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ return DoOnMessageAvailable(aMsg, false);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg)
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ return DoOnMessageAvailable(aMsg, true);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnStart(nsISupports* aContext)
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ int16_t readyState = mWebSocket->ReadyState();
+
+ // This is the only function that sets OPEN, and should be called only once
+ MOZ_ASSERT(readyState != WebSocket::OPEN,
+ "readyState already OPEN! OnStart called twice?");
+
+ // Nothing to do if we've already closed/closing
+ if (readyState != WebSocket::CONNECTING) {
+ return NS_OK;
+ }
+
+ // Attempt to kill "ghost" websocket: but usually too early for check to fail
+ nsresult rv = mWebSocket->CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
+ return rv;
+ }
+
+ if (!mRequestedProtocolList.IsEmpty()) {
+ mChannel->GetProtocol(mWebSocket->mEstablishedProtocol);
+ }
+
+ mChannel->GetExtensions(mWebSocket->mEstablishedExtensions);
+ UpdateURI();
+
+ mWebSocket->SetReadyState(WebSocket::OPEN);
+
+ mService->WebSocketOpened(mChannel->Serial(),mInnerWindowID,
+ mWebSocket->mEffectiveURL,
+ mWebSocket->mEstablishedProtocol,
+ mWebSocket->mEstablishedExtensions);
+
+ // Let's keep the object alive because the webSocket can be CCed in the
+ // onopen callback.
+ RefPtr<WebSocket> webSocket = mWebSocket;
+
+ // Call 'onopen'
+ rv = webSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the open event");
+ }
+
+ webSocket->UpdateMustKeepAlive();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode)
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ // We can be CONNECTING here if connection failed.
+ // We can be OPEN if we have encountered a fatal protocol error
+ // We can be CLOSING if close() was called and/or server initiated close.
+ MOZ_ASSERT(mWebSocket->ReadyState() != WebSocket::CLOSED,
+ "Shouldn't already be CLOSED when OnStop called");
+
+ return ScheduleConnectionCloseEvents(aContext, aStatusCode);
+}
+
+nsresult
+WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext,
+ nsresult aStatusCode)
+{
+ AssertIsOnTargetThread();
+
+ // no-op if some other code has already initiated close event
+ if (!mOnCloseScheduled) {
+ mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
+
+ if (aStatusCode == NS_BASE_STREAM_CLOSED) {
+ // don't generate an error event just because of an unclean close
+ aStatusCode = NS_OK;
+ }
+
+ if (NS_FAILED(aStatusCode)) {
+ ConsoleError();
+ mFailed = true;
+ }
+
+ mOnCloseScheduled = true;
+
+ NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ if (aSize > mWebSocket->mOutgoingBufferedAmount) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWebSocket->mOutgoingBufferedAmount -= aSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnServerClose(nsISupports *aContext, uint16_t aCode,
+ const nsACString &aReason)
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ int16_t readyState = mWebSocket->ReadyState();
+
+ MOZ_ASSERT(readyState != WebSocket::CONNECTING,
+ "Received server close before connected?");
+ MOZ_ASSERT(readyState != WebSocket::CLOSED,
+ "Received server close after already closed!");
+
+ // store code/string for onclose DOM event
+ mCloseEventCode = aCode;
+ CopyUTF8toUTF16(aReason, mCloseEventReason);
+
+ if (readyState == WebSocket::OPEN) {
+ // Server initiating close.
+ // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
+ // typically echos the status code it received".
+ // But never send certain codes, per section 7.4.1
+ if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
+ CloseConnection(0, EmptyCString());
+ } else {
+ CloseConnection(aCode, aReason);
+ }
+ } else {
+ // We initiated close, and server has replied: OnStop does rest of the work.
+ MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult)
+{
+ AssertIsOnMainThread();
+
+ if (!mWebSocket || mWebSocket->ReadyState() == WebSocket::CLOSED) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsCOMPtr<nsPIDOMWindowInner> win = mWebSocket->GetWindowIfCurrent();
+ if (!win) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = win->GetOuterWindow();
+ return wwatch->GetPrompt(outerWindow, aIID, aResult);
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebSocket
+////////////////////////////////////////////////////////////////////////////////
+
+WebSocket::WebSocket(nsPIDOMWindowInner* aOwnerWindow)
+ : DOMEventTargetHelper(aOwnerWindow)
+ , mIsMainThread(true)
+ , mKeepingAlive(false)
+ , mCheckMustKeepAlive(true)
+ , mOutgoingBufferedAmount(0)
+ , mBinaryType(dom::BinaryType::Blob)
+ , mMutex("WebSocket::mMutex")
+ , mReadyState(CONNECTING)
+{
+ mImpl = new WebSocketImpl(this);
+ mIsMainThread = mImpl->mIsMainThread;
+}
+
+WebSocket::~WebSocket()
+{
+}
+
+JSObject*
+WebSocket::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WebSocketBinding::Wrap(cx, this, aGivenProto);
+}
+
+//---------------------------------------------------------------------------
+// WebIDL
+//---------------------------------------------------------------------------
+
+// Constructor:
+already_AddRefed<WebSocket>
+WebSocket::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ ErrorResult& aRv)
+{
+ Sequence<nsString> protocols;
+ return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
+ EmptyCString(), aRv);
+}
+
+already_AddRefed<WebSocket>
+WebSocket::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ const nsAString& aProtocol,
+ ErrorResult& aRv)
+{
+ Sequence<nsString> protocols;
+ if (!protocols.AppendElement(aProtocol, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
+ EmptyCString(), aRv);
+}
+
+already_AddRefed<WebSocket>
+WebSocket::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ const Sequence<nsString>& aProtocols,
+ ErrorResult& aRv)
+{
+ return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, nullptr,
+ EmptyCString(), aRv);
+}
+
+already_AddRefed<WebSocket>
+WebSocket::CreateServerWebSocket(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ const Sequence<nsString>& aProtocols,
+ nsITransportProvider* aTransportProvider,
+ const nsAString& aNegotiatedExtensions,
+ ErrorResult& aRv)
+{
+ return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, aTransportProvider,
+ NS_ConvertUTF16toUTF8(aNegotiatedExtensions), aRv);
+}
+
+namespace {
+
+// This class is used to clear any exception.
+class MOZ_STACK_CLASS ClearException
+{
+public:
+ explicit ClearException(JSContext* aCx)
+ : mCx(aCx)
+ {
+ }
+
+ ~ClearException()
+ {
+ JS_ClearPendingException(mCx);
+ }
+
+private:
+ JSContext* mCx;
+};
+
+class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable
+{
+public:
+ WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsACString& aTelemetryKey)
+ : WorkerMainThreadRunnable(aWorkerPrivate, aTelemetryKey)
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool MainThreadRun() override
+ {
+ AssertIsOnMainThread();
+
+ // Walk up to our containing page
+ WorkerPrivate* wp = mWorkerPrivate;
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+
+ nsPIDOMWindowInner* window = wp->GetWindow();
+ if (window) {
+ return InitWithWindow(window);
+ }
+
+ return InitWindowless(wp);
+ }
+
+protected:
+ virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) = 0;
+
+ virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0;
+};
+
+class InitRunnable final : public WebSocketMainThreadRunnable
+{
+public:
+ InitRunnable(WebSocketImpl* aImpl, bool aIsServerSide,
+ const nsAString& aURL,
+ nsTArray<nsString>& aProtocolArray,
+ const nsACString& aScriptFile, uint32_t aScriptLine,
+ uint32_t aScriptColumn,
+ ErrorResult& aRv, bool* aConnectionFailed)
+ : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate,
+ NS_LITERAL_CSTRING("WebSocket :: init"))
+ , mImpl(aImpl)
+ , mIsServerSide(aIsServerSide)
+ , mURL(aURL)
+ , mProtocolArray(aProtocolArray)
+ , mScriptFile(aScriptFile)
+ , mScriptLine(aScriptLine)
+ , mScriptColumn(aScriptColumn)
+ , mRv(aRv)
+ , mConnectionFailed(aConnectionFailed)
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+protected:
+ virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override
+ {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(aWindow))) {
+ mRv.Throw(NS_ERROR_FAILURE);
+ return true;
+ }
+
+ ClearException ce(jsapi.cx());
+
+ nsIDocument* doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ mRv.Throw(NS_ERROR_FAILURE);
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ if (!principal) {
+ mRv.Throw(NS_ERROR_FAILURE);
+ return true;
+ }
+
+ mImpl->Init(jsapi.cx(), principal, mIsServerSide, mURL, mProtocolArray,
+ mScriptFile, mScriptLine, mScriptColumn, mRv,
+ mConnectionFailed);
+ return true;
+ }
+
+ virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
+
+ mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide,
+ mURL, mProtocolArray, mScriptFile, mScriptLine, mScriptColumn,
+ mRv, mConnectionFailed);
+ return true;
+ }
+
+ // Raw pointer. This worker runs synchronously.
+ WebSocketImpl* mImpl;
+
+ bool mIsServerSide;
+ const nsAString& mURL;
+ nsTArray<nsString>& mProtocolArray;
+ nsCString mScriptFile;
+ uint32_t mScriptLine;
+ uint32_t mScriptColumn;
+ ErrorResult& mRv;
+ bool* mConnectionFailed;
+};
+
+class AsyncOpenRunnable final : public WebSocketMainThreadRunnable
+{
+public:
+ AsyncOpenRunnable(WebSocketImpl* aImpl, ErrorResult& aRv)
+ : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate,
+ NS_LITERAL_CSTRING("WebSocket :: AsyncOpen"))
+ , mImpl(aImpl)
+ , mRv(aRv)
+ {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+protected:
+ virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override
+ {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+
+ nsIDocument* doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ mRv.Throw(NS_ERROR_FAILURE);
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ if (!principal) {
+ mRv.Throw(NS_ERROR_FAILURE);
+ return true;
+ }
+
+ uint64_t windowID = 0;
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
+ nsCOMPtr<nsPIDOMWindowInner> topInner;
+ if (topWindow) {
+ topInner = topWindow->GetCurrentInnerWindow();
+ }
+
+ if (topInner) {
+ windowID = topInner->WindowID();
+ }
+
+ mImpl->AsyncOpen(principal, windowID, nullptr, EmptyCString(), mRv);
+ return true;
+ }
+
+ virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
+
+ mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, nullptr,
+ EmptyCString(), mRv);
+ return true;
+ }
+
+private:
+ // Raw pointer. This worker runs synchronously.
+ WebSocketImpl* mImpl;
+
+ ErrorResult& mRv;
+};
+
+} // namespace
+
+already_AddRefed<WebSocket>
+WebSocket::ConstructorCommon(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ const Sequence<nsString>& aProtocols,
+ nsITransportProvider* aTransportProvider,
+ const nsACString& aNegotiatedExtensions,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!scriptPrincipal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ principal = scriptPrincipal->GetPrincipal();
+ if (!principal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!sgo) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!ownerWindow) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ MOZ_ASSERT_IF(ownerWindow, ownerWindow->IsInnerWindow());
+
+ nsTArray<nsString> protocolArray;
+
+ for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
+
+ const nsString& protocolElement = aProtocols[index];
+
+ if (protocolElement.IsEmpty()) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+ if (protocolArray.Contains(protocolElement)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+ if (protocolElement.FindChar(',') != -1) /* interferes w/list */ {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ protocolArray.AppendElement(protocolElement);
+ }
+
+ RefPtr<WebSocket> webSocket = new WebSocket(ownerWindow);
+ RefPtr<WebSocketImpl> webSocketImpl = webSocket->mImpl;
+
+ bool connectionFailed = true;
+
+ if (NS_IsMainThread()) {
+ webSocketImpl->Init(aGlobal.Context(), principal, !!aTransportProvider,
+ aUrl, protocolArray, EmptyCString(),
+ 0, 0, aRv, &connectionFailed);
+ } else {
+ // In workers we have to keep the worker alive using a workerHolder in order
+ // to dispatch messages correctly.
+ if (!webSocketImpl->RegisterWorkerHolder()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ unsigned lineno, column;
+ JS::AutoFilename file;
+ if (!JS::DescribeScriptedCaller(aGlobal.Context(), &file, &lineno,
+ &column)) {
+ NS_WARNING("Failed to get line number and filename in workers.");
+ }
+
+ RefPtr<InitRunnable> runnable =
+ new InitRunnable(webSocketImpl, !!aTransportProvider, aUrl,
+ protocolArray, nsDependentCString(file.get()), lineno,
+ column, aRv, &connectionFailed);
+ runnable->Dispatch(aRv);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // It can be that we have been already disconnected because the WebSocket is
+ // gone away while we where initializing the webSocket.
+ if (!webSocket->mImpl) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // We don't return an error if the connection just failed. Instead we dispatch
+ // an event.
+ if (connectionFailed) {
+ webSocket->mImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
+ }
+
+ // If we don't have a channel, the connection is failed and onerror() will be
+ // called asynchrounsly.
+ if (!webSocket->mImpl->mChannel) {
+ return webSocket.forget();
+ }
+
+ class MOZ_STACK_CLASS ClearWebSocket
+ {
+ public:
+ explicit ClearWebSocket(WebSocketImpl* aWebSocketImpl)
+ : mWebSocketImpl(aWebSocketImpl)
+ , mDone(false)
+ {
+ }
+
+ void Done()
+ {
+ mDone = true;
+ }
+
+ ~ClearWebSocket()
+ {
+ if (!mDone) {
+ mWebSocketImpl->mChannel = nullptr;
+ mWebSocketImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
+ }
+ }
+
+ WebSocketImpl* mWebSocketImpl;
+ bool mDone;
+ };
+
+ ClearWebSocket cws(webSocket->mImpl);
+
+ // This operation must be done on the correct thread. The rest must run on the
+ // main-thread.
+ aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (NS_IsMainThread()) {
+ MOZ_ASSERT(principal);
+
+ nsPIDOMWindowOuter* outerWindow = ownerWindow->GetOuterWindow();
+
+ uint64_t windowID = 0;
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = outerWindow->GetScriptableTop();
+ nsCOMPtr<nsPIDOMWindowInner> topInner;
+ if (topWindow) {
+ topInner = topWindow->GetCurrentInnerWindow();
+ }
+
+ if (topInner) {
+ windowID = topInner->WindowID();
+ }
+
+ webSocket->mImpl->AsyncOpen(principal, windowID, aTransportProvider,
+ aNegotiatedExtensions, aRv);
+ } else {
+ MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
+ "not yet implemented");
+ RefPtr<AsyncOpenRunnable> runnable =
+ new AsyncOpenRunnable(webSocket->mImpl, aRv);
+ runnable->Dispatch(aRv);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // It can be that we have been already disconnected because the WebSocket is
+ // gone away while we where initializing the webSocket.
+ if (!webSocket->mImpl) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Let's inform devtools about this new active WebSocket.
+ webSocket->mImpl->mService->WebSocketCreated(webSocket->mImpl->mChannel->Serial(),
+ webSocket->mImpl->mInnerWindowID,
+ webSocket->mURI,
+ webSocket->mImpl->mRequestedProtocolList);
+
+ cws.Done();
+ return webSocket.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(WebSocket)
+ bool isBlack = tmp->IsBlack();
+ if (isBlack || tmp->mKeepingAlive) {
+ if (tmp->mListenerManager) {
+ tmp->mListenerManager->MarkForCC();
+ }
+ if (!isBlack && tmp->PreservingWrapper()) {
+ // This marks the wrapper black.
+ tmp->GetWrapper();
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(WebSocket)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(WebSocket)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
+ DOMEventTargetHelper)
+ if (tmp->mImpl) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel)
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket,
+ DOMEventTargetHelper)
+ if (tmp->mImpl) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mChannel)
+ tmp->mImpl->Disconnect();
+ MOZ_ASSERT(!tmp->mImpl);
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
+
+void
+WebSocket::DisconnectFromOwner()
+{
+ AssertIsOnMainThread();
+ DOMEventTargetHelper::DisconnectFromOwner();
+
+ if (mImpl) {
+ mImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
+ }
+
+ DontKeepAliveAnyMore();
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl:: initialization
+//-----------------------------------------------------------------------------
+
+void
+WebSocketImpl::Init(JSContext* aCx,
+ nsIPrincipal* aPrincipal,
+ bool aIsServerSide,
+ const nsAString& aURL,
+ nsTArray<nsString>& aProtocolArray,
+ const nsACString& aScriptFile,
+ uint32_t aScriptLine,
+ uint32_t aScriptColumn,
+ ErrorResult& aRv,
+ bool* aConnectionFailed)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ mService = WebSocketEventService::GetOrCreate();
+
+ // We need to keep the implementation alive in case the init disconnects it
+ // because of some error.
+ RefPtr<WebSocketImpl> kungfuDeathGrip = this;
+
+ // Attempt to kill "ghost" websocket: but usually too early for check to fail
+ aRv = mWebSocket->CheckInnerWindowCorrectness();
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // Shut down websocket if window is frozen or destroyed (only needed for
+ // "ghost" websockets--see bug 696085)
+ if (!mWorkerPrivate) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!os)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aRv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aRv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ if (mWorkerPrivate) {
+ mScriptFile = aScriptFile;
+ mScriptLine = aScriptLine;
+ mScriptColumn = aScriptColumn;
+ } else {
+ MOZ_ASSERT(aCx);
+
+ unsigned lineno, column;
+ JS::AutoFilename file;
+ if (JS::DescribeScriptedCaller(aCx, &file, &lineno, &column)) {
+ mScriptFile = file.get();
+ mScriptLine = lineno;
+ mScriptColumn = column;
+ }
+ }
+
+ mIsServerSide = aIsServerSide;
+
+ // If we don't have aCx, we are window-less, so we don't have a
+ // inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
+ // DedicateWorkers created by JSM.
+ if (aCx) {
+ mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
+ }
+
+ // parses the url
+ aRv = ParseURL(PromiseFlatString(aURL));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> originDoc = mWebSocket->GetDocumentIfCurrent();
+ if (!originDoc) {
+ nsresult rv = mWebSocket->CheckInnerWindowCorrectness();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ }
+ mOriginDocument = do_GetWeakReference(originDoc);
+
+ if (!mIsServerSide) {
+ nsCOMPtr<nsIURI> uri;
+ {
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
+
+ // We crash here because we are sure that mURI is a valid URI, so either we
+ // are OOM'ing or something else bad is happening.
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_CRASH();
+ }
+ }
+
+ // The 'real' nsHttpChannel of the websocket gets opened in the parent.
+ // Since we don't serialize the CSP within child and parent and also not
+ // the context, we have to perform content policy checks here instead of
+ // AsyncOpen2().
+ // Please note that websockets can't follow redirects, hence there is no
+ // need to perform a CSP check after redirects.
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
+ uri,
+ aPrincipal,
+ originDoc,
+ EmptyCString(),
+ nullptr,
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (NS_CP_REJECTED(shouldLoad)) {
+ // Disallowed by content policy
+ aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
+ return;
+ }
+ }
+
+ // Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
+ // In such a case we have to upgrade ws: to wss: and also update mSecure
+ // to reflect that upgrade. Please note that we can not upgrade from ws:
+ // to wss: before performing content policy checks because CSP needs to
+ // send reports in case the scheme is about to be upgraded.
+ if (!mIsServerSide && !mSecure && originDoc &&
+ originDoc->GetUpgradeInsecureRequests(false)) {
+ // let's use the old specification before the upgrade for logging
+ NS_ConvertUTF8toUTF16 reportSpec(mURI);
+
+ // upgrade the request from ws:// to wss:// and mark as secure
+ mURI.ReplaceSubstring("ws://", "wss://");
+ if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
+ return;
+ }
+ mSecure = true;
+
+ const char16_t* params[] = { reportSpec.get(), u"wss" };
+ CSP_LogLocalizedStr(u"upgradeInsecureRequest",
+ params, ArrayLength(params),
+ EmptyString(), // aSourceFile
+ EmptyString(), // aScriptSample
+ 0, // aLineNumber
+ 0, // aColumnNumber
+ nsIScriptError::warningFlag, "CSP",
+ mInnerWindowID);
+ }
+
+ // Don't allow https:// to open ws://
+ if (!mIsServerSide && !mSecure &&
+ !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
+ false)) {
+ // Confirmed we are opening plain ws:// and want to prevent this from a
+ // secure context (e.g. https).
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIURI> originURI;
+ if (mWorkerPrivate) {
+ // For workers, retrieve the URI from the WorkerPrivate
+ principal = mWorkerPrivate->GetPrincipal();
+ } else {
+ // Check the principal's uri to determine if we were loaded from https.
+ nsCOMPtr<nsIGlobalObject> globalObject(GetEntryGlobal());
+ if (globalObject) {
+ principal = globalObject->PrincipalOrNull();
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow;
+
+ while (true) {
+ if (principal && !principal->GetIsNullPrincipal()) {
+ break;
+ }
+
+ if (!innerWindow) {
+ innerWindow = do_QueryInterface(globalObject);
+ if (!innerWindow) {
+ // If we are in a XPConnect sandbox or in a JS component,
+ // innerWindow will be null. There is nothing on top of this to be
+ // considered.
+ break;
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
+ innerWindow->GetScriptableParent();
+ if (NS_WARN_IF(!parentWindow)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> currentInnerWindow =
+ parentWindow->GetCurrentInnerWindow();
+ if (NS_WARN_IF(!currentInnerWindow)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ // We are at the top. Let's see if we have an opener window.
+ if (innerWindow == currentInnerWindow) {
+ ErrorResult error;
+ parentWindow =
+ nsGlobalWindow::Cast(innerWindow)->GetOpenerWindow(error);
+ if (NS_WARN_IF(error.Failed())) {
+ error.SuppressException();
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ if (!parentWindow) {
+ break;
+ }
+
+ currentInnerWindow = parentWindow->GetCurrentInnerWindow();
+ if (NS_WARN_IF(!currentInnerWindow)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ MOZ_ASSERT(currentInnerWindow != innerWindow);
+ }
+
+ innerWindow = currentInnerWindow;
+
+ nsCOMPtr<nsIDocument> document = innerWindow->GetExtantDoc();
+ if (NS_WARN_IF(!document)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ principal = document->NodePrincipal();
+ }
+ }
+
+ if (principal) {
+ principal->GetURI(getter_AddRefs(originURI));
+ }
+
+ if (originURI) {
+ bool originIsHttps = false;
+ aRv = originURI->SchemeIs("https", &originIsHttps);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ if (originIsHttps) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ }
+ }
+
+ // Assign the sub protocol list and scan it for illegal values
+ for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
+ for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) {
+ if (aProtocolArray[index][i] < static_cast<char16_t>(0x0021) ||
+ aProtocolArray[index][i] > static_cast<char16_t>(0x007E)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+ }
+
+ if (!mRequestedProtocolList.IsEmpty()) {
+ mRequestedProtocolList.AppendLiteral(", ");
+ }
+
+ AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
+ }
+
+ // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
+ // url parameter, so don't throw if InitializeConnection fails, and call
+ // onerror/onclose asynchronously
+ if (NS_FAILED(InitializeConnection(aPrincipal))) {
+ *aConnectionFailed = true;
+ } else {
+ *aConnectionFailed = false;
+ }
+}
+
+void
+WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
+ nsITransportProvider* aTransportProvider,
+ const nsACString& aNegotiatedExtensions,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+ MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
+
+ nsCString asciiOrigin;
+ aRv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (aTransportProvider) {
+ aRv = mChannel->SetServerParameters(aTransportProvider, aNegotiatedExtensions);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ ToLowerCase(asciiOrigin);
+
+ nsCOMPtr<nsIURI> uri;
+ if (!aTransportProvider) {
+ aRv = NS_NewURI(getter_AddRefs(uri), mURI);
+ MOZ_ASSERT(!aRv.Failed());
+ }
+
+ aRv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr);
+ if (NS_WARN_IF(aRv.Failed())) {
+ aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
+ return;
+ }
+
+ mInnerWindowID = aInnerWindowID;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl methods:
+//-----------------------------------------------------------------------------
+
+class nsAutoCloseWS final
+{
+public:
+ explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
+ : mWebSocketImpl(aWebSocketImpl)
+ {}
+
+ ~nsAutoCloseWS()
+ {
+ if (!mWebSocketImpl->mChannel) {
+ mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
+ }
+ }
+private:
+ RefPtr<WebSocketImpl> mWebSocketImpl;
+};
+
+nsresult
+WebSocketImpl::InitializeConnection(nsIPrincipal* aPrincipal)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mChannel, "mChannel should be null");
+
+ nsCOMPtr<nsIWebSocketChannel> wsChannel;
+ nsAutoCloseWS autoClose(this);
+ nsresult rv;
+
+ if (mSecure) {
+ wsChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
+ } else {
+ wsChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // add ourselves to the document's load group and
+ // provide the http stack the loadgroup info too
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ rv = wsChannel->SetLoadGroup(loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = loadGroup->AddRequest(this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mWeakLoadGroup = do_GetWeakReference(loadGroup);
+ }
+
+ // manually adding loadinfo to the channel since it
+ // was not set during channel creation.
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mOriginDocument);
+
+ // mOriginDocument has to be release on main-thread because WeakReferences
+ // are not thread-safe.
+ mOriginDocument = nullptr;
+
+
+ // The TriggeringPrincipal for websockets must always be a script.
+ // Let's make sure that the doc's principal (if a doc exists)
+ // and aPrincipal are same origin.
+ MOZ_ASSERT(!doc || doc->NodePrincipal()->Equals(aPrincipal));
+
+ wsChannel->InitLoadInfo(doc ? doc->AsDOMNode() : nullptr,
+ doc ? doc->NodePrincipal() : aPrincipal,
+ aPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_WEBSOCKET);
+
+ if (!mRequestedProtocolList.IsEmpty()) {
+ rv = wsChannel->SetProtocol(mRequestedProtocolList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(wsChannel);
+ NS_ENSURE_TRUE(rr, NS_ERROR_FAILURE);
+
+ rv = rr->RetargetDeliveryTo(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mChannel = wsChannel;
+
+ return NS_OK;
+}
+
+void
+WebSocketImpl::DispatchConnectionCloseEvents()
+{
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return;
+ }
+
+ mWebSocket->SetReadyState(WebSocket::CLOSED);
+
+ // Let's keep the object alive because the webSocket can be CCed in the
+ // onerror or in the onclose callback.
+ RefPtr<WebSocket> webSocket = mWebSocket;
+
+ // Call 'onerror' if needed
+ if (mFailed) {
+ nsresult rv =
+ webSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event");
+ }
+ }
+
+ nsresult rv = webSocket->CreateAndDispatchCloseEvent(mCloseEventWasClean,
+ mCloseEventCode,
+ mCloseEventReason);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the close event");
+ }
+
+ webSocket->UpdateMustKeepAlive();
+ Disconnect();
+}
+
+nsresult
+WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName)
+{
+ MOZ_ASSERT(mImpl);
+ AssertIsOnTargetThread();
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ 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);
+
+ return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+nsresult
+WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
+ bool aIsBinary)
+{
+ MOZ_ASSERT(mImpl);
+ AssertIsOnTargetThread();
+
+ AutoJSAPI jsapi;
+
+ if (NS_IsMainThread()) {
+ if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ MOZ_ASSERT(!mIsMainThread);
+ MOZ_ASSERT(mImpl->mWorkerPrivate);
+ if (NS_WARN_IF(!jsapi.Init(mImpl->mWorkerPrivate->GlobalScope()))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING;
+
+ // Create appropriate JS object for message
+ JS::Rooted<JS::Value> jsData(cx);
+ if (aIsBinary) {
+ if (mBinaryType == dom::BinaryType::Blob) {
+ messageType = nsIWebSocketEventListener::TYPE_BLOB;
+
+ RefPtr<Blob> blob =
+ Blob::CreateStringBlob(GetOwner(), aData, EmptyString());
+ MOZ_ASSERT(blob);
+
+ if (!ToJSValue(cx, blob, &jsData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ } else if (mBinaryType == dom::BinaryType::Arraybuffer) {
+ messageType = nsIWebSocketEventListener::TYPE_ARRAYBUFFER;
+
+ JS::Rooted<JSObject*> arrayBuf(cx);
+ nsresult rv = nsContentUtils::CreateArrayBuffer(cx, aData,
+ arrayBuf.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+ jsData.setObject(*arrayBuf);
+ } else {
+ NS_RUNTIMEABORT("Unknown binary type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // JS string
+ NS_ConvertUTF8toUTF16 utf16Data(aData);
+ JSString* jsString;
+ jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
+ NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
+
+ jsData.setString(jsString);
+ }
+
+ mImpl->mService->WebSocketMessageAvailable(mImpl->mChannel->Serial(),
+ mImpl->mInnerWindowID,
+ aData, messageType);
+
+ // create an event that uses the MessageEvent interface,
+ // which does not bubble, is not cancelable, and has no default action
+
+ RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
+
+ event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"), false, false,
+ jsData, mImpl->mUTF16Origin, EmptyString(), nullptr,
+ Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ return DispatchDOMEvent(nullptr, static_cast<Event*>(event), nullptr,
+ nullptr);
+}
+
+nsresult
+WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
+ uint16_t aCode,
+ const nsAString& aReason)
+{
+ AssertIsOnTargetThread();
+
+ // This method is called by a runnable and it can happen that, in the
+ // meantime, GC unlinked this object, so mImpl could be null.
+ if (mImpl && mImpl->mChannel) {
+ mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(),
+ mImpl->mInnerWindowID,
+ aWasClean, aCode, aReason);
+ }
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ CloseEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mWasClean = aWasClean;
+ init.mCode = aCode;
+ init.mReason = aReason;
+
+ RefPtr<CloseEvent> event =
+ CloseEvent::Constructor(this, NS_LITERAL_STRING("close"), init);
+ event->SetTrusted(true);
+
+ return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+nsresult
+WebSocketImpl::ParseURL(const nsAString& aURL)
+{
+ AssertIsOnMainThread();
+ NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
+
+ if (mIsServerSide) {
+ mWebSocket->mURI = aURL;
+ CopyUTF16toUTF8(mWebSocket->mURI, mURI);
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ bool hasRef;
+ rv = parsedURL->GetHasRef(&hasRef);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef,
+ NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsAutoCString scheme;
+ rv = parsedURL->GetScheme(scheme);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
+ NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsAutoCString host;
+ rv = parsedURL->GetAsciiHost(host);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
+
+ int32_t port;
+ rv = parsedURL->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ rv = NS_CheckPortSafety(port, scheme.get());
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
+
+ nsAutoCString filePath;
+ rv = parsedURL->GetFilePath(filePath);
+ if (filePath.IsEmpty()) {
+ filePath.Assign('/');
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsAutoCString query;
+ rv = parsedURL->GetQuery(query);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ if (scheme.LowerCaseEqualsLiteral("ws")) {
+ mSecure = false;
+ mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
+ } else if (scheme.LowerCaseEqualsLiteral("wss")) {
+ mSecure = true;
+ mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
+ } else {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ rv = nsContentUtils::GetUTFOrigin(parsedURL, mUTF16Origin);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ mAsciiHost = host;
+ ToLowerCase(mAsciiHost);
+
+ mResource = filePath;
+ if (!query.IsEmpty()) {
+ mResource.Append('?');
+ mResource.Append(query);
+ }
+ uint32_t length = mResource.Length();
+ uint32_t i;
+ for (i = 0; i < length; ++i) {
+ if (mResource[i] < static_cast<char16_t>(0x0021) ||
+ mResource[i] > static_cast<char16_t>(0x007E)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+ }
+
+ rv = parsedURL->GetSpec(mURI);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ CopyUTF8toUTF16(mURI, mWebSocket->mURI);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Methods that keep alive the WebSocket object when:
+// 1. the object has registered event listeners that can be triggered
+// ("strong event listeners");
+// 2. there are outgoing not sent messages.
+//-----------------------------------------------------------------------------
+
+void
+WebSocket::UpdateMustKeepAlive()
+{
+ // Here we could not have mImpl.
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+
+ if (!mCheckMustKeepAlive || !mImpl) {
+ return;
+ }
+
+ bool shouldKeepAlive = false;
+ uint16_t readyState = ReadyState();
+
+ if (mListenerManager) {
+ switch (readyState)
+ {
+ case CONNECTING:
+ {
+ if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onclose)) {
+ shouldKeepAlive = true;
+ }
+ }
+ break;
+
+ case OPEN:
+ case CLOSING:
+ {
+ if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onclose) ||
+ mOutgoingBufferedAmount != 0) {
+ shouldKeepAlive = true;
+ }
+ }
+ break;
+
+ case CLOSED:
+ {
+ shouldKeepAlive = false;
+ }
+ }
+ }
+
+ if (mKeepingAlive && !shouldKeepAlive) {
+ mKeepingAlive = false;
+ mImpl->ReleaseObject();
+ } else if (!mKeepingAlive && shouldKeepAlive) {
+ mKeepingAlive = true;
+ mImpl->AddRefObject();
+ }
+}
+
+void
+WebSocket::DontKeepAliveAnyMore()
+{
+ // Here we could not have mImpl.
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+
+ if (mKeepingAlive) {
+ MOZ_ASSERT(mImpl);
+
+ mKeepingAlive = false;
+ mImpl->ReleaseObject();
+ }
+
+ mCheckMustKeepAlive = false;
+}
+
+namespace {
+
+class WebSocketWorkerHolder final : public WorkerHolder
+{
+public:
+ explicit WebSocketWorkerHolder(WebSocketImpl* aWebSocketImpl)
+ : mWebSocketImpl(aWebSocketImpl)
+ {
+ }
+
+ bool Notify(Status aStatus) override
+ {
+ MOZ_ASSERT(aStatus > workers::Running);
+
+ if (aStatus >= Canceling) {
+ {
+ MutexAutoLock lock(mWebSocketImpl->mMutex);
+ mWebSocketImpl->mWorkerShuttingDown = true;
+ }
+
+ mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY,
+ EmptyCString());
+ }
+
+ return true;
+ }
+
+private:
+ WebSocketImpl* mWebSocketImpl;
+};
+
+} // namespace
+
+void
+WebSocketImpl::AddRefObject()
+{
+ AssertIsOnTargetThread();
+ AddRef();
+}
+
+void
+WebSocketImpl::ReleaseObject()
+{
+ AssertIsOnTargetThread();
+ Release();
+}
+
+bool
+WebSocketImpl::RegisterWorkerHolder()
+{
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mWorkerHolder);
+ mWorkerHolder = new WebSocketWorkerHolder(this);
+
+ if (NS_WARN_IF(!mWorkerHolder->HoldWorker(mWorkerPrivate, Canceling))) {
+ mWorkerHolder = nullptr;
+ return false;
+ }
+
+#ifdef DEBUG
+ SetHasWorkerHolderRegistered(true);
+#endif
+
+ return true;
+}
+
+void
+WebSocketImpl::UnregisterWorkerHolder()
+{
+ MOZ_ASSERT(mDisconnectingOrDisconnected);
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(mWorkerHolder);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mWorkerShuttingDown = true;
+ }
+
+ // The DTOR of this WorkerHolder will release the worker for us.
+ mWorkerHolder = nullptr;
+
+ mWorkerPrivate = nullptr;
+
+#ifdef DEBUG
+ SetHasWorkerHolderRegistered(false);
+#endif
+}
+
+nsresult
+WebSocketImpl::UpdateURI()
+{
+ AssertIsOnTargetThread();
+
+ // Check for Redirections
+ RefPtr<BaseWebSocketChannel> channel;
+ channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
+ MOZ_ASSERT(channel);
+
+ channel->GetEffectiveURL(mWebSocket->mEffectiveURL);
+ mSecure = channel->IsEncrypted();
+
+ return NS_OK;
+}
+
+void
+WebSocket::EventListenerAdded(nsIAtom* aType)
+{
+ AssertIsOnMainThread();
+ UpdateMustKeepAlive();
+}
+
+void
+WebSocket::EventListenerRemoved(nsIAtom* aType)
+{
+ AssertIsOnMainThread();
+ UpdateMustKeepAlive();
+}
+
+//-----------------------------------------------------------------------------
+// WebSocket - methods
+//-----------------------------------------------------------------------------
+
+// webIDL: readonly attribute unsigned short readyState;
+uint16_t
+WebSocket::ReadyState()
+{
+ MutexAutoLock lock(mMutex);
+ return mReadyState;
+}
+
+void
+WebSocket::SetReadyState(uint16_t aReadyState)
+{
+ MutexAutoLock lock(mMutex);
+ mReadyState = aReadyState;
+}
+
+// webIDL: readonly attribute unsigned long bufferedAmount;
+uint32_t
+WebSocket::BufferedAmount() const
+{
+ AssertIsOnTargetThread();
+ return mOutgoingBufferedAmount;
+}
+
+// webIDL: attribute BinaryType binaryType;
+dom::BinaryType
+WebSocket::BinaryType() const
+{
+ AssertIsOnTargetThread();
+ return mBinaryType;
+}
+
+// webIDL: attribute BinaryType binaryType;
+void
+WebSocket::SetBinaryType(dom::BinaryType aData)
+{
+ AssertIsOnTargetThread();
+ mBinaryType = aData;
+}
+
+// webIDL: readonly attribute DOMString url
+void
+WebSocket::GetUrl(nsAString& aURL)
+{
+ AssertIsOnTargetThread();
+
+ if (mEffectiveURL.IsEmpty()) {
+ aURL = mURI;
+ } else {
+ aURL = mEffectiveURL;
+ }
+}
+
+// webIDL: readonly attribute DOMString extensions;
+void
+WebSocket::GetExtensions(nsAString& aExtensions)
+{
+ AssertIsOnTargetThread();
+ CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
+}
+
+// webIDL: readonly attribute DOMString protocol;
+void
+WebSocket::GetProtocol(nsAString& aProtocol)
+{
+ AssertIsOnTargetThread();
+ CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
+}
+
+// webIDL: void send(DOMString data);
+void
+WebSocket::Send(const nsAString& aData,
+ ErrorResult& aRv)
+{
+ AssertIsOnTargetThread();
+
+ NS_ConvertUTF16toUTF8 msgString(aData);
+ Send(nullptr, msgString, msgString.Length(), false, aRv);
+}
+
+void
+WebSocket::Send(Blob& aData, ErrorResult& aRv)
+{
+ AssertIsOnTargetThread();
+
+ nsCOMPtr<nsIInputStream> msgStream;
+ aData.GetInternalStream(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(msgStream, EmptyCString(), msgLength, true, aRv);
+}
+
+void
+WebSocket::Send(const ArrayBuffer& aData,
+ ErrorResult& aRv)
+{
+ AssertIsOnTargetThread();
+
+ aData.ComputeLengthAndData();
+
+ static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+ uint32_t len = aData.Length();
+ char* data = reinterpret_cast<char*>(aData.Data());
+
+ nsDependentCSubstring msgString(data, len);
+ Send(nullptr, msgString, len, true, aRv);
+}
+
+void
+WebSocket::Send(const ArrayBufferView& aData,
+ ErrorResult& aRv)
+{
+ AssertIsOnTargetThread();
+
+ aData.ComputeLengthAndData();
+
+ static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+ uint32_t len = aData.Length();
+ char* data = reinterpret_cast<char*>(aData.Data());
+
+ nsDependentCSubstring msgString(data, len);
+ Send(nullptr, msgString, len, true, aRv);
+}
+
+void
+WebSocket::Send(nsIInputStream* aMsgStream,
+ const nsACString& aMsgString,
+ uint32_t aMsgLength,
+ bool aIsBinary,
+ ErrorResult& aRv)
+{
+ AssertIsOnTargetThread();
+
+ int64_t readyState = ReadyState();
+ if (readyState == CONNECTING) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ // Always increment outgoing buffer len, even if closed
+ CheckedUint32 size = mOutgoingBufferedAmount;
+ size += aMsgLength;
+ if (!size.isValid()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ mOutgoingBufferedAmount = size.value();
+
+ if (readyState == CLOSING ||
+ readyState == CLOSED) {
+ return;
+ }
+
+ // We must have mImpl when connected.
+ MOZ_ASSERT(mImpl);
+ MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send");
+
+ nsresult rv;
+ if (aMsgStream) {
+ rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength);
+ } else {
+ if (aIsBinary) {
+ rv = mImpl->mChannel->SendBinaryMsg(aMsgString);
+ } else {
+ rv = mImpl->mChannel->SendMsg(aMsgString);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ UpdateMustKeepAlive();
+}
+
+// webIDL: void close(optional unsigned short code, optional DOMString reason):
+void
+WebSocket::Close(const Optional<uint16_t>& aCode,
+ const Optional<nsAString>& aReason,
+ ErrorResult& aRv)
+{
+ AssertIsOnTargetThread();
+
+ // the reason code is optional, but if provided it must be in a specific range
+ uint16_t closeCode = 0;
+ if (aCode.WasPassed()) {
+ if (aCode.Value() != 1000 && (aCode.Value() < 3000 || aCode.Value() > 4999)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return;
+ }
+ closeCode = aCode.Value();
+ }
+
+ nsCString closeReason;
+ if (aReason.WasPassed()) {
+ CopyUTF16toUTF8(aReason.Value(), closeReason);
+
+ // The API requires the UTF-8 string to be 123 or less bytes
+ if (closeReason.Length() > 123) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+ }
+
+ int64_t readyState = ReadyState();
+ if (readyState == CLOSING ||
+ readyState == CLOSED) {
+ return;
+ }
+
+ // If the webSocket is not closed we MUST have a mImpl.
+ MOZ_ASSERT(mImpl);
+
+ if (readyState == CONNECTING) {
+ mImpl->FailConnection(closeCode, closeReason);
+ return;
+ }
+
+ MOZ_ASSERT(readyState == OPEN);
+ mImpl->CloseConnection(closeCode, closeReason);
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketImpl::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ AssertIsOnMainThread();
+
+ int64_t readyState = mWebSocket->ReadyState();
+ if ((readyState == WebSocket::CLOSING) ||
+ (readyState == WebSocket::CLOSED)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
+ if (!mWebSocket->GetOwner() || window != mWebSocket->GetOwner()) {
+ return NS_OK;
+ }
+
+ if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
+ (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0))
+ {
+ CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketImpl::GetName(nsACString& aName)
+{
+ AssertIsOnMainThread();
+
+ CopyUTF16toUTF8(mWebSocket->mURI, aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::IsPending(bool* aValue)
+{
+ AssertIsOnTargetThread();
+
+ int64_t readyState = mWebSocket->ReadyState();
+ *aValue = (readyState != WebSocket::CLOSED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::GetStatus(nsresult* aStatus)
+{
+ AssertIsOnTargetThread();
+
+ *aStatus = NS_OK;
+ return NS_OK;
+}
+
+namespace {
+
+class CancelRunnable final : public MainThreadWorkerRunnable
+{
+public:
+ CancelRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
+ : MainThreadWorkerRunnable(aWorkerPrivate)
+ , mImpl(aImpl)
+ {
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ return !NS_FAILED(mImpl->CancelInternal());
+ }
+
+private:
+ RefPtr<WebSocketImpl> mImpl;
+};
+
+} // namespace
+
+// Window closed, stop/reload button pressed, user navigated away from page, etc.
+NS_IMETHODIMP
+WebSocketImpl::Cancel(nsresult aStatus)
+{
+ AssertIsOnMainThread();
+
+ if (!mIsMainThread) {
+ MOZ_ASSERT(mWorkerPrivate);
+ RefPtr<CancelRunnable> runnable =
+ new CancelRunnable(mWorkerPrivate, this);
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ return CancelInternal();
+}
+
+nsresult
+WebSocketImpl::CancelInternal()
+{
+ AssertIsOnTargetThread();
+
+ // If CancelInternal is called by a runnable, we may already be disconnected
+ // by the time it runs.
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ int64_t readyState = mWebSocket->ReadyState();
+ if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
+ return NS_OK;
+ }
+
+ return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::Suspend()
+{
+ AssertIsOnMainThread();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::Resume()
+{
+ AssertIsOnMainThread();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup)
+{
+ AssertIsOnMainThread();
+
+ *aLoadGroup = nullptr;
+
+ if (mIsMainThread) {
+ nsCOMPtr<nsIDocument> doc = mWebSocket->GetDocumentIfCurrent();
+ if (doc) {
+ *aLoadGroup = doc->GetDocumentLoadGroup().take();
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mWorkerPrivate);
+
+ // Walk up to our containing page
+ WorkerPrivate* wp = mWorkerPrivate;
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+
+ nsPIDOMWindowInner* window = wp->GetWindow();
+ if (!window) {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc) {
+ *aLoadGroup = doc->GetDocumentLoadGroup().take();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ AssertIsOnMainThread();
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags)
+{
+ AssertIsOnMainThread();
+
+ *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ AssertIsOnMainThread();
+
+ // we won't change the load flags at all.
+ return NS_OK;
+}
+
+namespace {
+
+class WorkerRunnableDispatcher final : public WorkerRunnable
+{
+ RefPtr<WebSocketImpl> mWebSocketImpl;
+
+public:
+ WorkerRunnableDispatcher(WebSocketImpl* aImpl, WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<nsIRunnable> aEvent)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ , mWebSocketImpl(aImpl)
+ , mEvent(Move(aEvent))
+ {
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ // No messages when disconnected.
+ if (mWebSocketImpl->mDisconnectingOrDisconnected) {
+ NS_WARNING("Dispatching a WebSocket event after the disconnection!");
+ return true;
+ }
+
+ return !NS_FAILED(mEvent->Run());
+ }
+
+ void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override
+ {
+ }
+
+ 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 socket
+ // transport thread).
+ return true;
+ }
+
+ void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) 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 socket
+ // transport thread).
+ }
+
+private:
+ nsCOMPtr<nsIRunnable> mEvent;
+};
+
+} // namespace
+
+NS_IMETHODIMP
+WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> event_ref(aEvent);
+ // If the target is the main-thread we can just dispatch the runnable.
+ if (mIsMainThread) {
+ return NS_DispatchToMainThread(event_ref.forget());
+ }
+
+ MutexAutoLock lock(mMutex);
+ if (mWorkerShuttingDown) {
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mWorkerPrivate);
+
+#ifdef DEBUG
+ MOZ_ASSERT(HasWorkerHolderRegistered());
+#endif
+
+ // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
+ // runnable.
+ RefPtr<WorkerRunnableDispatcher> event =
+ new WorkerRunnableDispatcher(this, mWorkerPrivate, event_ref.forget());
+
+ if (!event->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::IsOnCurrentThread(bool* aResult)
+{
+ *aResult = IsTargetThread();
+ return NS_OK;
+}
+
+bool
+WebSocketImpl::IsTargetThread() const
+{
+ return NS_IsMainThread() == mIsMainThread;
+}
+
+void
+WebSocket::AssertIsOnTargetThread() const
+{
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/base/WebSocket.h b/dom/base/WebSocket.h
new file mode 100644
index 000000000..f5fd170de
--- /dev/null
+++ b/dom/base/WebSocket.h
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 WebSocket_h__
+#define WebSocket_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/WebSocketBinding.h" // for BinaryType
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+#define DEFAULT_WS_SCHEME_PORT 80
+#define DEFAULT_WSS_SCHEME_PORT 443
+
+class nsIInputStream;
+class nsITransportProvider;
+
+namespace mozilla {
+namespace dom {
+
+class Blob;
+
+class WebSocketImpl;
+
+class WebSocket final : public DOMEventTargetHelper
+{
+ friend class WebSocketImpl;
+
+public:
+ enum {
+ CONNECTING = 0,
+ OPEN = 1,
+ CLOSING = 2,
+ CLOSED = 3
+ };
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(
+ WebSocket, DOMEventTargetHelper)
+
+ // EventTarget
+ virtual void EventListenerAdded(nsIAtom* aType) override;
+ virtual void EventListenerRemoved(nsIAtom* aType) override;
+
+ virtual void DisconnectFromOwner() override;
+
+ // nsWrapperCache
+ nsPIDOMWindowInner* GetParentObject() { return GetOwner(); }
+
+ virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
+
+public: // static helpers:
+
+ // Determine if preferences allow WebSocket
+ static bool PrefEnabled(JSContext* aCx = nullptr, JSObject* aGlobal = nullptr);
+
+public: // WebIDL interface:
+
+ // Constructor:
+ static already_AddRefed<WebSocket> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ ErrorResult& rv);
+
+ static already_AddRefed<WebSocket> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ const nsAString& aProtocol,
+ ErrorResult& rv);
+
+ static already_AddRefed<WebSocket> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ const Sequence<nsString>& aProtocols,
+ ErrorResult& rv);
+
+ static already_AddRefed<WebSocket> CreateServerWebSocket(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ const Sequence<nsString>& aProtocols,
+ nsITransportProvider* aTransportProvider,
+ const nsAString& aNegotiatedExtensions,
+ ErrorResult& rv);
+
+ static already_AddRefed<WebSocket> ConstructorCommon(const GlobalObject& aGlobal,
+ const nsAString& aUrl,
+ const Sequence<nsString>& aProtocols,
+ nsITransportProvider* aTransportProvider,
+ const nsACString& aNegotiatedExtensions,
+ ErrorResult& rv);
+
+ // webIDL: readonly attribute DOMString url
+ void GetUrl(nsAString& aResult);
+
+ // webIDL: readonly attribute unsigned short readyState;
+ uint16_t ReadyState();
+
+ // webIDL: readonly attribute unsigned long bufferedAmount;
+ uint32_t BufferedAmount() const;
+
+ // webIDL: attribute Function? onopen;
+ IMPL_EVENT_HANDLER(open)
+
+ // webIDL: attribute Function? onerror;
+ IMPL_EVENT_HANDLER(error)
+
+ // webIDL: attribute Function? onclose;
+ IMPL_EVENT_HANDLER(close)
+
+ // webIDL: readonly attribute DOMString extensions;
+ void GetExtensions(nsAString& aResult);
+
+ // webIDL: readonly attribute DOMString protocol;
+ void GetProtocol(nsAString& aResult);
+
+ // webIDL: void close(optional unsigned short code, optional DOMString reason):
+ void Close(const Optional<uint16_t>& aCode,
+ const Optional<nsAString>& aReason,
+ ErrorResult& aRv);
+
+ // webIDL: attribute Function? onmessage;
+ IMPL_EVENT_HANDLER(message)
+
+ // webIDL: attribute DOMString binaryType;
+ dom::BinaryType BinaryType() const;
+ void SetBinaryType(dom::BinaryType aData);
+
+ // webIDL: void send(DOMString|Blob|ArrayBufferView data);
+ void Send(const nsAString& aData,
+ ErrorResult& aRv);
+ void Send(Blob& aData,
+ ErrorResult& aRv);
+ void Send(const ArrayBuffer& aData,
+ ErrorResult& aRv);
+ void Send(const ArrayBufferView& aData,
+ ErrorResult& aRv);
+
+private: // constructor && destructor
+ explicit WebSocket(nsPIDOMWindowInner* aOwnerWindow);
+ virtual ~WebSocket();
+
+ void SetReadyState(uint16_t aReadyState);
+
+ // These methods actually do the dispatch for various events.
+ nsresult CreateAndDispatchSimpleEvent(const nsAString& aName);
+ nsresult CreateAndDispatchMessageEvent(const nsACString& aData,
+ bool aIsBinary);
+ nsresult CreateAndDispatchCloseEvent(bool aWasClean,
+ uint16_t aCode,
+ const nsAString& aReason);
+
+ // if there are "strong event listeners" (see comment in WebSocket.cpp) 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();
+
+private:
+ WebSocket(const WebSocket& x) = delete; // prevent bad usage
+ WebSocket& operator=(const WebSocket& x) = delete;
+
+ void Send(nsIInputStream* aMsgStream,
+ const nsACString& aMsgString,
+ uint32_t aMsgLength,
+ bool aIsBinary,
+ ErrorResult& aRv);
+
+ void AssertIsOnTargetThread() const;
+
+ // Raw pointer because this WebSocketImpl is created, managed and destroyed by
+ // WebSocket.
+ WebSocketImpl* mImpl;
+
+ bool mIsMainThread;
+
+ bool mKeepingAlive;
+ bool mCheckMustKeepAlive;
+
+ uint32_t mOutgoingBufferedAmount;
+
+ // related to the WebSocket constructor steps
+ nsString mURI;
+ nsString mEffectiveURL; // after redirects
+ nsCString mEstablishedExtensions;
+ nsCString mEstablishedProtocol;
+
+ dom::BinaryType mBinaryType;
+
+ // This mutex protects mReadyState that is the only variable that is used in
+ // different threads.
+ mozilla::Mutex mMutex;
+
+ // This value should not be used directly but use ReadyState() instead.
+ uint16_t mReadyState;
+};
+
+} //namespace dom
+} //namespace mozilla
+
+#endif
diff --git a/dom/base/WindowNamedPropertiesHandler.cpp b/dom/base/WindowNamedPropertiesHandler.cpp
new file mode 100644
index 000000000..c0b71dab3
--- /dev/null
+++ b/dom/base/WindowNamedPropertiesHandler.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/. */
+
+#include "WindowNamedPropertiesHandler.h"
+#include "mozilla/dom/EventTargetBinding.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "nsContentUtils.h"
+#include "nsDOMClassInfo.h"
+#include "nsDOMWindowList.h"
+#include "nsGlobalWindow.h"
+#include "nsHTMLDocument.h"
+#include "nsJSUtils.h"
+#include "xpcprivate.h"
+
+namespace mozilla {
+namespace dom {
+
+static bool
+ShouldExposeChildWindow(nsString& aNameBeingResolved, nsPIDOMWindowOuter* aChild)
+{
+ Element* e = aChild->GetFrameElementInternal();
+ if (e && e->IsInShadowTree()) {
+ return false;
+ }
+
+ // If we're same-origin with the child, go ahead and expose it.
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aChild);
+ NS_ENSURE_TRUE(sop, false);
+ if (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<JS::PropertyDescriptor> aDesc)
+ const
+{
+ if (!JSID_IS_STRING(aId)) {
+ // Nothing to do if we're resolving a non-string property.
+ return true;
+ }
+
+ bool hasOnPrototype;
+ if (!HasPropertyOnPrototype(aCx, aProxy, aId, &hasOnPrototype)) {
+ return false;
+ }
+ if (hasOnPrototype) {
+ return true;
+ }
+
+ nsAutoJSString str;
+ if (!str.init(aCx, JSID_TO_STRING(aId))) {
+ return false;
+ }
+
+ if(str.IsEmpty()) {
+ return true;
+ }
+
+ // Grab the DOM window.
+ JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, aProxy));
+ nsGlobalWindow* win = xpc::WindowOrNull(global);
+ if (win->Length() > 0) {
+ nsCOMPtr<nsPIDOMWindowOuter> childWin = win->GetChildWindow(str);
+ if (childWin && ShouldExposeChildWindow(str, childWin)) {
+ // 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 (!WrapObject(aCx, childWin, &v)) {
+ return false;
+ }
+ FillPropertyDescriptor(aDesc, aProxy, 0, v);
+ return true;
+ }
+ }
+
+ // The rest of this function is for HTML documents only.
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc());
+ if (!htmlDoc) {
+ return true;
+ }
+ nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get());
+
+ Element* element = document->GetElementById(str);
+ if (element) {
+ JS::Rooted<JS::Value> v(aCx);
+ if (!WrapObject(aCx, element, &v)) {
+ return false;
+ }
+ FillPropertyDescriptor(aDesc, aProxy, 0, v);
+ return true;
+ }
+
+ nsWrapperCache* cache;
+ nsISupports* result = document->ResolveName(str, &cache);
+ if (!result) {
+ return true;
+ }
+
+ JS::Rooted<JS::Value> v(aCx);
+ if (!WrapObject(aCx, result, cache, nullptr, &v)) {
+ return false;
+ }
+ FillPropertyDescriptor(aDesc, aProxy, 0, v);
+ return true;
+}
+
+bool
+WindowNamedPropertiesHandler::defineProperty(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<jsid> aId,
+ JS::Handle<JS::PropertyDescriptor> aDesc,
+ JS::ObjectOpResult &result) const
+{
+ ErrorResult rv;
+ rv.ThrowTypeError<MSG_DEFINEPROPERTY_ON_GSP>();
+ rv.MaybeSetPendingException(aCx);
+ return false;
+}
+
+bool
+WindowNamedPropertiesHandler::ownPropNames(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ unsigned flags,
+ JS::AutoIdVector& aProps) const
+{
+ if (!(flags & JSITER_HIDDEN)) {
+ // None of our named properties are enumerable.
+ return true;
+ }
+
+ // Grab the DOM window.
+ nsGlobalWindow* win = xpc::WindowOrNull(JS_GetGlobalForObject(aCx, aProxy));
+ nsTArray<nsString> names;
+ // The names live on the outer window, which might be null
+ nsGlobalWindow* outer = win->GetOuterWindowInternal();
+ if (outer) {
+ nsDOMWindowList* childWindows = outer->GetWindowList();
+ if (childWindows) {
+ uint32_t length = childWindows->GetLength();
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> item =
+ childWindows->GetDocShellTreeItemAt(i);
+ // This is a bit silly, since we could presumably just do
+ // item->GetWindow(). But it's not obvious whether this does the same
+ // thing as GetChildWindow() with the item's name (due to the complexity
+ // of FindChildWithName). Since GetChildWindow is what we use in
+ // getOwnPropDescriptor, let's try to be consistent.
+ nsString name;
+ item->GetName(name);
+ if (!names.Contains(name)) {
+ // Make sure we really would expose it from getOwnPropDescriptor.
+ nsCOMPtr<nsPIDOMWindowOuter> childWin = win->GetChildWindow(name);
+ if (childWin && ShouldExposeChildWindow(name, childWin)) {
+ names.AppendElement(name);
+ }
+ }
+ }
+ }
+ }
+ if (!AppendNamedPropertyIds(aCx, aProxy, names, false, aProps)) {
+ return false;
+ }
+
+ names.Clear();
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc());
+ if (!htmlDoc) {
+ return true;
+ }
+ nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get());
+ // Document names are enumerable, so we want to get them no matter what flags
+ // is.
+ document->GetSupportedNames(names);
+
+ JS::AutoIdVector docProps(aCx);
+ if (!AppendNamedPropertyIds(aCx, aProxy, names, false, docProps)) {
+ 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();
+}
+
+static bool
+ResolveWindowNamedProperty(JSContext* aCx, JS::Handle<JSObject*> aWrapper,
+ JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc)
+{
+ {
+ JSAutoCompartment ac(aCx, aObj);
+ if (!js::GetProxyHandler(aObj)->getOwnPropertyDescriptor(aCx, aObj, aId,
+ aDesc)) {
+ return false;
+ }
+ }
+
+ if (aDesc.object()) {
+ aDesc.object().set(aWrapper);
+
+ return JS_WrapPropertyDescriptor(aCx, aDesc);
+ }
+
+ return true;
+}
+
+static bool
+EnumerateWindowNamedProperties(JSContext* aCx, JS::Handle<JSObject*> aWrapper,
+ JS::Handle<JSObject*> aObj,
+ JS::AutoIdVector& aProps)
+{
+ JSAutoCompartment ac(aCx, aObj);
+ return js::GetProxyHandler(aObj)->ownPropertyKeys(aCx, aObj, aProps);
+}
+
+const NativePropertyHooks sWindowNamedPropertiesNativePropertyHooks[] = { {
+ ResolveWindowNamedProperty,
+ EnumerateWindowNamedProperties,
+ nullptr,
+ { nullptr, nullptr },
+ prototypes::id::_ID_Count,
+ constructors::id::_ID_Count,
+ nullptr
+} };
+
+static const DOMIfaceAndProtoJSClass WindowNamedPropertiesClass = {
+ PROXY_CLASS_DEF("WindowProperties",
+ JSCLASS_IS_DOMIFACEANDPROTOJSCLASS),
+ eNamedPropertiesObject,
+ false,
+ prototypes::id::_ID_Count,
+ 0,
+ sWindowNamedPropertiesNativePropertyHooks,
+ "[object WindowProperties]",
+ EventTargetBinding::GetProtoObject
+};
+
+// static
+JSObject*
+WindowNamedPropertiesHandler::Create(JSContext* aCx,
+ JS::Handle<JSObject*> aProto)
+{
+ // Note: since the scope polluter proxy lives on the window's prototype
+ // chain, it needs a singleton type to avoid polluting type information
+ // for properties on the window.
+ js::ProxyOptions options;
+ options.setSingleton(true);
+ options.setClass(&WindowNamedPropertiesClass.mBase);
+
+ JS::Rooted<JSObject*> gsp(aCx);
+ gsp = 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 dom
+} // namespace mozilla
diff --git a/dom/base/WindowNamedPropertiesHandler.h b/dom/base/WindowNamedPropertiesHandler.h
new file mode 100644
index 000000000..227d8c946
--- /dev/null
+++ b/dom/base/WindowNamedPropertiesHandler.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 {
+namespace 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<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::AutoIdVector& 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 dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_WindowNamedPropertiesHandler_h */
diff --git a/dom/base/WindowOrientationObserver.cpp b/dom/base/WindowOrientationObserver.cpp
new file mode 100644
index 000000000..d168cdd68
--- /dev/null
+++ b/dom/base/WindowOrientationObserver.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 "WindowOrientationObserver.h"
+
+#include "nsGlobalWindow.h"
+#include "mozilla/Hal.h"
+
+using namespace mozilla::dom;
+
+/**
+ * This class is used by nsGlobalWindow to implement window.orientation
+ * and window.onorientationchange. This class is defined in its own file
+ * because Hal.h pulls in windows.h and can't be included from
+ * nsGlobalWindow.cpp
+ */
+WindowOrientationObserver::WindowOrientationObserver(
+ nsGlobalWindow* aGlobalWindow)
+ : mWindow(aGlobalWindow)
+{
+ MOZ_ASSERT(aGlobalWindow && aGlobalWindow->IsInnerWindow());
+ hal::RegisterScreenConfigurationObserver(this);
+
+ hal::ScreenConfiguration config;
+ hal::GetCurrentScreenConfiguration(&config);
+ mAngle = config.angle();
+}
+
+WindowOrientationObserver::~WindowOrientationObserver()
+{
+ hal::UnregisterScreenConfigurationObserver(this);
+}
+
+void
+WindowOrientationObserver::Notify(
+ const mozilla::hal::ScreenConfiguration& aConfiguration)
+{
+ uint16_t currentAngle = aConfiguration.angle();
+ if (mAngle != currentAngle && mWindow->AsInner()->IsCurrentInnerWindow()) {
+ mAngle = currentAngle;
+ mWindow->GetOuterWindow()->DispatchCustomEvent(NS_LITERAL_STRING("orientationchange"));
+ }
+}
+
+/* static */ int16_t
+WindowOrientationObserver::OrientationAngle()
+{
+ hal::ScreenConfiguration config;
+ hal::GetCurrentScreenConfiguration(&config);
+ int16_t angle = static_cast<int16_t>(config.angle());
+ // config.angle() returns 0, 90, 180 or 270.
+ // window.orientation returns -90, 0, 90 or 180.
+ return angle <= 180 ? angle : angle - 360;
+}
diff --git a/dom/base/WindowOrientationObserver.h b/dom/base/WindowOrientationObserver.h
new file mode 100644
index 000000000..b544f202b
--- /dev/null
+++ b/dom/base/WindowOrientationObserver.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_WindowOrientationObserver_h
+#define mozilla_dom_WindowOrientationObserver_h
+
+#include "mozilla/HalScreenConfiguration.h"
+
+class nsGlobalWindow;
+
+namespace mozilla {
+namespace dom {
+
+class WindowOrientationObserver final :
+ public mozilla::hal::ScreenConfigurationObserver
+{
+public:
+ explicit WindowOrientationObserver(nsGlobalWindow* aGlobalWindow);
+ ~WindowOrientationObserver();
+ void Notify(const mozilla::hal::ScreenConfiguration& aConfiguration) override;
+ static int16_t OrientationAngle();
+
+private:
+ // Weak pointer, instance is owned by mWindow.
+ nsGlobalWindow* MOZ_NON_OWNING_REF mWindow;
+ uint16_t mAngle;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WindowOrientationObserver_h
diff --git a/dom/base/contentAreaDropListener.js b/dom/base/contentAreaDropListener.js
new file mode 100644
index 000000000..44d7b24a2
--- /dev/null
+++ b/dom/base/contentAreaDropListener.js
@@ -0,0 +1,228 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/osfile.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// 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.
+
+function ContentAreaDropListener() { };
+
+ContentAreaDropListener.prototype =
+{
+ classID: Components.ID("{1f34bc80-1bc7-11d6-a384-d705dd0746fc}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDroppedLinkHandler, Ci.nsISupports]),
+
+ _addLink : function(links, url, name, type)
+ {
+ links.push({ url, name, type });
+ },
+
+ _addLinksFromItem: function(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+$/mg, "").split("\n");
+ for (let line of lines) {
+ this._addLink(links, line, line, 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, OS.Path.toFileURI(files[i].mozFullPath),
+ files[i].name, "application/x-moz-file");
+ }
+ },
+
+ _getDropLinks : function (dt)
+ {
+ let links = [];
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ this._addLinksFromItem(links, dt, i);
+ }
+ return links;
+ },
+
+ _validateURI: function(dataTransfer, uriString, disallowInherit)
+ {
+ 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, '');
+
+ let uri;
+ let ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ try {
+ // Check that the uri is valid first and return an empty string if not.
+ // It may just be plain text and should be ignored here
+ uri = ioService.newURI(uriString, null, null);
+ } catch (ex) { }
+ if (!uri)
+ return uriString;
+
+ // uriString is a valid URI, so do the security check.
+ let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+ let sourceNode = dataTransfer.mozSourceNode;
+ let flags = secMan.STANDARD;
+ if (disallowInherit)
+ flags |= secMan.DISALLOW_INHERIT_PRINCIPAL;
+
+ // Use file:/// as the default uri so that drops of file URIs are always allowed
+ let principal = sourceNode ? sourceNode.nodePrincipal
+ : secMan.createCodebasePrincipal(ioService.newURI("file:///", null, null), {});
+
+ secMan.checkLoadURIStrWithPrincipal(principal, uriString, flags);
+
+ return uriString;
+ },
+
+ canDropLink: function(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;
+
+ let sourceNode = dataTransfer.mozSourceNode;
+ if (!sourceNode)
+ return true;
+
+ // don't allow a drop of a node from the same document onto this one
+ let sourceDocument = sourceNode.ownerDocument;
+ let eventDocument = aEvent.originalTarget.ownerDocument;
+ if (sourceDocument == eventDocument)
+ return false;
+
+ // also check for nodes in other child or sibling frames by checking
+ // if both have the same top window.
+ if (sourceDocument && eventDocument) {
+ if (sourceDocument.defaultView == null)
+ return true;
+ let sourceRoot = sourceDocument.defaultView.top;
+ if (sourceRoot && sourceRoot == eventDocument.defaultView.top)
+ return false;
+ }
+
+ return true;
+ },
+
+ dropLink: function(aEvent, aName, aDisallowInherit)
+ {
+ aName.value = "";
+ let links = this.dropLinks(aEvent, aDisallowInherit);
+ let url = "";
+ if (links.length > 0) {
+ url = links[0].url;
+ let name = links[0].name;
+ if (name)
+ aName.value = name;
+ }
+
+ return url;
+ },
+
+ dropLinks: function(aEvent, aDisallowInherit, aCount)
+ {
+ if (aEvent && this._eventTargetIsDisabled(aEvent))
+ return [];
+
+ let dataTransfer = aEvent.dataTransfer;
+ let links = this._getDropLinks(dataTransfer);
+
+ for (let link of links) {
+ try {
+ link.url = this._validateURI(dataTransfer, link.url, aDisallowInherit);
+ } 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;
+ }
+ }
+ if (aCount)
+ aCount.value = links.length;
+
+ return links;
+ },
+
+ _eventTargetIsDisabled: function(aEvent)
+ {
+ let ownerDoc = aEvent.originalTarget.ownerDocument;
+ if (!ownerDoc || !ownerDoc.defaultView)
+ return false;
+
+ return ownerDoc.defaultView
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .isNodeDisabledForEvents(aEvent.originalTarget);
+ }
+};
+
+var components = [ContentAreaDropListener];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/dom/base/contentAreaDropListener.manifest b/dom/base/contentAreaDropListener.manifest
new file mode 100644
index 000000000..8e6878b9b
--- /dev/null
+++ b/dom/base/contentAreaDropListener.manifest
@@ -0,0 +1,2 @@
+component {1f34bc80-1bc7-11d6-a384-d705dd0746fc} contentAreaDropListener.js
+contract @mozilla.org/content/dropped-link-handler;1 {1f34bc80-1bc7-11d6-a384-d705dd0746fc}
diff --git a/dom/base/crashtests/1024428-1.html b/dom/base/crashtests/1024428-1.html
new file mode 100644
index 000000000..ce181c8af
--- /dev/null
+++ b/dom/base/crashtests/1024428-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div id="host"></div>
+<script>
+var s = host.createShadowRoot();
+s.innerHTML = '<input type="range" />';
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1026714.html b/dom/base/crashtests/1026714.html
new file mode 100644
index 000000000..465d62942
--- /dev/null
+++ b/dom/base/crashtests/1026714.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<body onload="f()">
+ <div></div>
+ <style>
+ div { color: blue; }
+ </style>
+ <script>
+ function f() {
+ // This should not leak.
+ var div = document.querySelector("div");
+ var shadow = div.createShadowRoot();
+ shadow.innerHTML = '<div><style scoped>p { color: green; }</style>';
+ }
+ </script>
+</body>
diff --git a/dom/base/crashtests/1027461-1.html b/dom/base/crashtests/1027461-1.html
new file mode 100644
index 000000000..3117a5e3c
--- /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.xul">
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/base/crashtests/1027461-inner.xul b/dom/base/crashtests/1027461-inner.xul
new file mode 100644
index 000000000..84ee2d222
--- /dev/null
+++ b/dom/base/crashtests/1027461-inner.xul
@@ -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 000000000..b6bda3f26
--- /dev/null
+++ b/dom/base/crashtests/1029710.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ var x = document.createElement('span');
+ x.createShadowRoot();
+ x.id = 'a';
+</script>
+</body>
+</html>
+
diff --git a/dom/base/crashtests/1118764.html b/dom/base/crashtests/1118764.html
new file mode 100644
index 000000000..54a4d94b6
--- /dev/null
+++ b/dom/base/crashtests/1118764.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<body>
+<style>
+#foo {
+ overflow: scroll;
+ height: 100px;
+}
+</style>
+<div id="foo">
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+Mozilla Firefox<br>
+<script>
+foo.createShadowRoot().innerHTML = "<content></content>";
+</script>
+</body>
+</html>
diff --git a/dom/base/crashtests/1154598.xhtml b/dom/base/crashtests/1154598.xhtml
new file mode 100644
index 000000000..64d550654
--- /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 000000000..5d822c0a6
--- /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 000000000..e62fce1ab
--- /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 000000000..785d97fe6
--- /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 000000000..929207964
--- /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/1230422.html b/dom/base/crashtests/1230422.html
new file mode 100644
index 000000000..fbaa63d38
--- /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 000000000..57c76121f
--- /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/1304437.html b/dom/base/crashtests/1304437.html
new file mode 100644
index 000000000..a4e9d5110
--- /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/1385272-1.html b/dom/base/crashtests/1385272-1.html
new file mode 100644
index 000000000..5bdd40799
--- /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/149320-1.html b/dom/base/crashtests/149320-1.html
new file mode 100644
index 000000000..d09b2c5ad
--- /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/205225-1.html b/dom/base/crashtests/205225-1.html
new file mode 100644
index 000000000..26576d4d5
--- /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 000000000..4a0d997da
--- /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 000000000..7eaf96cc3
--- /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 000000000..08b5bc5fc
--- /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 000000000..ae1804076
--- /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, false);
+ reallyClear();
+}
+
+function init()
+{
+ document.body.addEventListener("DOMNodeRemoved", clear, false);
+}
+
+</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/308120-1.xul b/dom/base/crashtests/308120-1.xul
new file mode 100644
index 000000000..55963d758
--- /dev/null
+++ b/dom/base/crashtests/308120-1.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="var button=document.getElementsByTagName('button')[0]; try { button.appendChild(SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(button))[0]); } catch(e) { }">
+ <button/>
+</window>
diff --git a/dom/base/crashtests/324871-1.html b/dom/base/crashtests/324871-1.html
new file mode 100644
index 000000000..21aa46230
--- /dev/null
+++ b/dom/base/crashtests/324871-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<script>
+function loaded() {
+ var node = document;
+ var handler = function(operation, key, data, src, dst) { alert(data); };
+ node.setUserData("foo", "data", handler);
+}
+</script>
+</head>
+<body onload="loaded();">
+<div id="elem"></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 000000000..bcc1f462e
--- /dev/null
+++ b/dom/base/crashtests/325730-1.html
@@ -0,0 +1,27 @@
+<html>
+
+<head>
+
+<script>
+
+function init()
+{
+ document.addEventListener("DOMNodeRemoved", meep, false);
+ document.body.appendChild(document.getElementById("b"));
+
+ function meep()
+ {
+ document.removeEventListener("DOMNodeRemoved", meep, false);
+ document.body.removeChild(document.getElementById("c"));
+ }
+}
+
+window.addEventListener("load", init, false);
+
+
+</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 000000000..926670ab6
--- /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 000000000..5b654dd02
--- /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/326778-1.xul b/dom/base/crashtests/326778-1.xul
new file mode 100644
index 000000000..ea235730a
--- /dev/null
+++ b/dom/base/crashtests/326778-1.xul
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script>
+<![CDATA[
+try {
+ document.getBoxObjectFor({});
+} catch(e) { }
+]]>
+</script>
+</window>
diff --git a/dom/base/crashtests/326865-1.html b/dom/base/crashtests/326865-1.html
new file mode 100644
index 000000000..2450e65a6
--- /dev/null
+++ b/dom/base/crashtests/326865-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<script>
+try {
+ window.getComputedStyle(null, 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 000000000..117f09351
--- /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 000000000..daff350fa
--- /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, false);
+
+</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 000000000..a3d5562b3
--- /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 000000000..3ba5477f3
--- /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/330925-1.xhtml b/dom/base/crashtests/330925-1.xhtml
new file mode 100644
index 000000000..7dac8374c
--- /dev/null
+++ b/dom/base/crashtests/330925-1.xhtml
@@ -0,0 +1,35 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<head>
+
+<script>
+
+function init()
+{
+ var foopy = document.getElementById("foopy");
+ var emb = document.getElementById("emb");
+
+ try {
+ foopy.appendChild(SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(emb))[0]);
+ emb.parentNode.removeChild(emb);
+ foopy.parentNode.removeChild(foopy);
+ } catch (e) {
+ }
+
+ document.documentElement.removeAttribute("class");
+}
+
+
+window.addEventListener("load", function() { setTimeout(init, 30); }, false);
+
+</script>
+
+</head>
+<body>
+
+<div id="foopy"/>
+
+<embed src="data:foo/bar,baz" id="emb" />
+
+</body>
+</html>
diff --git a/dom/base/crashtests/336381-1.xhtml b/dom/base/crashtests/336381-1.xhtml
new file mode 100644
index 000000000..eac3eeb57
--- /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 000000000..ad9d1430a
--- /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 000000000..9366e0c0f
--- /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 000000000..2cba227dc
--- /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 000000000..26d8fb9cd
--- /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 000000000..82b5a26a8
--- /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 000000000..4bf313062
--- /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 000000000..8ea8bc28d
--- /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 000000000..28c5fc0e4
--- /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 000000000..6a8dd69d5
--- /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 000000000..017bac1e9
--- /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.parentNode.removeChild(everything);
+
+ 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 000000000..4d0263d80
--- /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/348049-1.xhtml b/dom/base/crashtests/348049-1.xhtml
new file mode 100644
index 000000000..d8f5790d2
--- /dev/null
+++ b/dom/base/crashtests/348049-1.xhtml
@@ -0,0 +1,40 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+
+<binding id="td">
+ <content>
+ <html:td><children/></html:td>
+ </content>
+</binding>
+
+<binding id="foo">
+ <content>
+ <html:span/>
+ </content>
+</binding>
+
+</bindings>
+
+
+<script>
+function f1()
+{
+ document.getElementById("inner").style.MozBinding = "url(#foo)";
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+</head>
+
+
+<body onload="setTimeout(f1, 30);">
+
+
+<div id="outer" style="-moz-binding: url(#td)"><div id="inner">M</div></div>
+
+
+</body>
+
+</html>
diff --git a/dom/base/crashtests/349355-1.html b/dom/base/crashtests/349355-1.html
new file mode 100644
index 000000000..b79387118
--- /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.parentNode.removeChild(j);
+ 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/354645-1.xul b/dom/base/crashtests/354645-1.xul
new file mode 100644
index 000000000..3e3670ee0
--- /dev/null
+++ b/dom/base/crashtests/354645-1.xul
@@ -0,0 +1,25 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();" class="reftest-wait">
+
+<script type="text/javascript">
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function boom()
+{
+ var A = document.getElementById("A");
+ var B = document.createElementNS(XUL_NS, "tabs");
+ var C = document.createElementNS(XUL_NS, "hbox");
+ B.appendChild(C);
+ A.appendChild(B);
+}
+
+function remove(n)
+{
+ n.parentNode.removeChild(n);
+}
+
+</script>
+
+<hbox id="A" onselect="remove(event.originalTarget); document.documentElement.removeAttribute('class');"></hbox>
+
+</window>
diff --git a/dom/base/crashtests/359432-1.xhtml b/dom/base/crashtests/359432-1.xhtml
new file mode 100644
index 000000000..391a4d162
--- /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 000000000..c090088c0
--- /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 000000000..7845f0bdf
--- /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, false);
+ document.removeChild(dE);
+ document.removeEventListener("DOMNodeRemoved", whee, false);
+
+ function whee()
+ {
+ document.removeEventListener("DOMNodeRemoved", whee, false);
+ document.insertBefore(newSpan, dE);
+ }
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+
+</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 000000000..b44242d26
--- /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 000000000..4fc3b7dfc
--- /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 000000000..d8fb45519
--- /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 000000000..ccefd3a32
--- /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 000000000..788309382
--- /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 000000000..93897635f
--- /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 000000000..8da0b22b1
--- /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 000000000..a87edcecb
--- /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 000000000..46573b49c
--- /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 000000000..8e50c7ef3
--- /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 000000000..77fa300be
--- /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 000000000..c61002eee
--- /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 000000000..dc42dc00d
--- /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.xul b/dom/base/crashtests/384663-1-inner.xul
new file mode 100644
index 000000000..f2914ce0a
--- /dev/null
+++ b/dom/base/crashtests/384663-1-inner.xul
@@ -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 000000000..8cef4f588
--- /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.xul"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/386000-1.html b/dom/base/crashtests/386000-1.html
new file mode 100644
index 000000000..0169a28fb
--- /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, false);
+
+ function f()
+ {
+ document.removeEventListener("DOMNodeRemoved", f, false);
+ 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 000000000..4b5022168
--- /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 000000000..33b1891e7
--- /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 000000000..bd1fc9353
--- /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/395469-1.xhtml b/dom/base/crashtests/395469-1.xhtml
new file mode 100644
index 000000000..937ace8d3
--- /dev/null
+++ b/dom/base/crashtests/395469-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="empty"><content></content></binding></bindings>
+
+<style>
+
+.bef:before { content: counter(c); }
+
+.fl:first-letter { }
+
+</style>
+
+<script>
+
+function boom()
+{
+ document.getElementById("xblize").style.MozBinding = "url('#empty')";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div class="fl"><span class="bef" id="xblize"></span><span class="bef"></span></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/395469-2.xhtml b/dom/base/crashtests/395469-2.xhtml
new file mode 100644
index 000000000..7da320256
--- /dev/null
+++ b/dom/base/crashtests/395469-2.xhtml
@@ -0,0 +1,45 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="empty"><content></content></binding></bindings>
+
+<style>
+
+.bef:before { content: counter(c); }
+
+.fl:first-letter { }
+
+</style>
+
+<script>
+
+var xblize, div;
+
+function boom()
+{
+ xblize = document.getElementById("xblize");
+ div = document.getElementById("div");
+
+ xblize.style.MozBinding = "url('#empty')";
+
+ // Give the XBL extra time to settle
+ setTimeout(boom2, 200);
+}
+
+function boom2()
+{
+ div.removeChild(xblize);
+ div.appendChild(xblize);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div class="fl" id="div"><span class="bef" id="xblize"></span><span class="bef"></span></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/398088-1.xul b/dom/base/crashtests/398088-1.xul
new file mode 100644
index 000000000..6548a11be
--- /dev/null
+++ b/dom/base/crashtests/398088-1.xul
@@ -0,0 +1,23 @@
+<window onload="boom();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script>
+
+function boom()
+{
+ var p = document.getElementById("p");
+
+ document.addEventListener("DOMAttrModified", removeP, true);
+
+ p.removeAttribute("mode");
+
+ function removeP()
+ {
+ document.documentElement.removeChild(p);
+ }
+}
+
+</script>
+
+<progressmeter id="p" mode="determined" />
+
+</window>
diff --git a/dom/base/crashtests/399712-1.html b/dom/base/crashtests/399712-1.html
new file mode 100644
index 000000000..58e957309
--- /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 000000000..2de672090
--- /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/401993-1.html b/dom/base/crashtests/401993-1.html
new file mode 100644
index 000000000..b8679dfd0
--- /dev/null
+++ b/dom/base/crashtests/401993-1.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function s()
+{
+ var x = document.getElementById("x");
+ x.style.MozBinding = "url(401993-1.xml#foo)";
+
+ setTimeout(boom, 0);
+
+ function boom()
+ {
+ var nodes = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(x));
+ if (!nodes) {
+ setTimeout(boom, 10);
+ return;
+ }
+
+ var newSpan = document.createElement("span");
+ newSpan.contentEditable = "true";
+ nodes[0].appendChild(newSpan);
+ x.parentNode.removeChild(x);
+
+ document.documentElement.removeAttribute("class");
+ }
+}
+</script>
+</head>
+
+<body onload="s();">
+
+<span contenteditable="true"></span>
+
+<div id="x"></div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/401993-1.xml b/dom/base/crashtests/401993-1.xml
new file mode 100644
index 000000000..747f8a5f8
--- /dev/null
+++ b/dom/base/crashtests/401993-1.xml
@@ -0,0 +1 @@
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><span xmlns="http://www.w3.org/1999/xhtml"><children xmlns="http://www.mozilla.org/xbl"/></span></content></binding></bindings> \ No newline at end of file
diff --git a/dom/base/crashtests/404869-1.xul b/dom/base/crashtests/404869-1.xul
new file mode 100644
index 000000000..f5c7facff
--- /dev/null
+++ b/dom/base/crashtests/404869-1.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="empty"><content></content></binding>
+</bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var menupopup = document.getElementById("menupopup");
+ var x = SpecialPowers.wrap(document).getAnonymousNodes(menupopup)[0];
+ menupopup.style.MozBinding = "url('#empty')";
+ for (var ppp in x) {
+ }
+}
+
+</script>
+
+<menupopup id="menupopup"/>
+
+</window>
diff --git a/dom/base/crashtests/407818.html b/dom/base/crashtests/407818.html
new file mode 100644
index 000000000..eea475be4
--- /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 000000000..4a7414fc9
--- /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 000000000..6cd121044
--- /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 000000000..fc5ed546a
--- /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-stack;"><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 000000000..bf8ba3486
--- /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 000000000..a3e42fe6f
--- /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 000000000..5fc2ea3bc
--- /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, false);
+ document.removeChild(a);
+ }
+
+ document.addEventListener("DOMAttrModified", t, false);
+ document.execCommand("insertunorderedlist", false, "<h1>");
+ document.removeEventListener("DOMAttrModified", t, false);
+}
+
+</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 000000000..feb39bf33
--- /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 000000000..7d277156e
--- /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 000000000..00165763c
--- /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 000000000..688ebe44d
--- /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 000000000..cb7388a7c
--- /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 000000000..bfb4f365a
--- /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 000000000..3c95b966c
--- /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 000000000..e75159c51
--- /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 000000000..f91d6e5b4
--- /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 000000000..124846fcb
--- /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 000000000..09581b308
--- /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 000000000..a64044c6e
--- /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 000000000..ad8fe34bf
--- /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/473284.xul b/dom/base/crashtests/473284.xul
new file mode 100644
index 000000000..2a00d2783
--- /dev/null
+++ b/dom/base/crashtests/473284.xul
@@ -0,0 +1,93 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait"
+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.popupNode = {};
+ result += '7';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.popupNode = document.documentElement;
+ result += '8';
+} catch (ex) {
+ result += '.';
+}
+
+try {
+ document.popupNode = null;
+ result += '9';
+} 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 += '.';
+}
+
+document.documentElement.textContent = result == '.23.56.89.bc' ? 'PASSED' : 'FAILED';
+if (document.documentElement.textContent == 'PASSED') {
+ document.documentElement.removeAttribute('class');
+}
+"/>
diff --git a/dom/base/crashtests/474041-1.svg b/dom/base/crashtests/474041-1.svg
new file mode 100644
index 000000000..2b8351472
--- /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 000000000..b3d8f8e1c
--- /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 000000000..11cc7c4ac
--- /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 000000000..bfa6732e8
--- /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, false);
+ q.innerHTML = "<span><\/span>";
+ window.removeEventListener("DOMNodeRemoved", f, false);
+
+ function f()
+ {
+ window.removeEventListener("DOMNodeRemoved", f, false);
+ 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 000000000..6dd3dd4a1
--- /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 000000000..236962469
--- /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 000000000..e0036cd83
--- /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 000000000..f02720b84
--- /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 000000000..86907c5bb
--- /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 000000000..4d4487817
--- /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 000000000..71e561d74
--- /dev/null
+++ b/dom/base/crashtests/504224.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+<title>Crash [@ nsFocusManager::GetCommonAncestor], part 2</title>
+</head>
+<body>
+<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%0A%3Cbody%20onunload%3D%22window.frameElement.parentNode.removeChild%28window.frameElement%29%22%20tabindex%3D%221%22%3E%0A%3Cscript%3E%0Adocument.body.focus%28%29%3B%0A%3C/script%3E%0A%3C/body%3E%0A%3C/html%3E" id="content"></iframe>
+<script>
+var src=document.getElementById('src');
+setInterval(function() {
+ if (!document.getElementById('content')) {
+ var x=document.createElement('iframe');
+ x.src=src;
+ x.id = 'content';
+ document.body.appendChild(x);
+ setTimeout(function() { window.focus(); document.documentElement.removeAttribute('class'); }, 100);
+ } else
+ window.frames[0].location.reload();
+}, 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 000000000..988164601
--- /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 000000000..87891ea19
--- /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 000000000..52e66ab8c
--- /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 000000000..bddd8dc28
--- /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, false);
+ 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 000000000..622666957
--- /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, false);
+</script>
diff --git a/dom/base/crashtests/551631-1.html b/dom/base/crashtests/551631-1.html
new file mode 100644
index 000000000..90a84ce58
--- /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 000000000..8c2a9b09f
--- /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 = "";
+ }, false);
+
+ 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 000000000..b04b966cf
--- /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 000000000..753d5c0fd
--- /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, false);
+
+]]>
+</script></html>
diff --git a/dom/base/crashtests/558973.html b/dom/base/crashtests/558973.html
new file mode 100644
index 000000000..878b8c925
--- /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/561981-1-iframe.xhtml b/dom/base/crashtests/561981-1-iframe.xhtml
new file mode 100644
index 000000000..bca0cf73f
--- /dev/null
+++ b/dom/base/crashtests/561981-1-iframe.xhtml
@@ -0,0 +1,56 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+var i = 0;
+
+function init()
+{
+ targetWindow = window.frames[0];
+ targetDocument = targetWindow.document;
+ targetDocument.body.appendChild(targetDocument.importNode(document.getElementById('rootish'), true));
+ targetDocument.designMode = 'on';
+ setTimeout(boom, 30);
+}
+
+function boom()
+{
+ var r = targetDocument.createRange();
+ r.setStart(targetDocument.getElementById("bar"), 0);
+ r.setEnd(targetDocument.getElementById("baz").firstChild, 0);
+ targetWindow.getSelection().addRange(r);
+ targetDocument.execCommand("indent", false, null);
+ setTimeout(whack, 300);
+}
+
+function whack()
+{
+ if (++i > 100) return;
+ document.documentElement.style.MozBinding = 'url("data:text/xml,' + encodeURIComponent("<bindings xmlns=\"http://www.mozilla.org/xbl\"><binding id=\"foo\" g=\""+Math.random()+"\"><content>\n<\/content><\/binding><\/bindings>\n") + '")';
+ setTimeout(bonk, 10);
+}
+
+function bonk()
+{
+ document.getElementById("i").style.MozBinding = 'url("data:text/xml,' + encodeURIComponent("<bindings xmlns=\"http://www.mozilla.org/xbl\"><binding id=\"foo\" g=\""+Math.random()+"\"><content>\n\n<\/content><\/binding><\/bindings>\n") + '")';
+ document.documentElement.style.MozBinding = 'url("data:text/xml,' + encodeURIComponent("<bindings xmlns=\"http://www.mozilla.org/xbl\"><binding id=\"foo\" g=\""+Math.random()+"\"><content><iframe xmlns=\"http://www.w3.org/1999/xhtml\" src=\"data:text/html,\" style=\"width: 95%; height: 500px;\"><\/iframe><\/content><\/binding><\/bindings>\n") + '")';
+ setTimeout(whack, 10);
+}
+
+]]>
+</script>
+</head>
+
+<body onload="init()">
+
+<iframe id="i" src="data:text/html," style="width: 95%; height: 500px;"/>
+
+<div id="rootish">
+ <div>Foo</div>
+ <div id="bar">Bar</div>
+ <div><select><option id="baz">baz</option></select></div>
+</div>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/561981-1.html b/dom/base/crashtests/561981-1.html
new file mode 100644
index 000000000..270f832c7
--- /dev/null
+++ b/dom/base/crashtests/561981-1.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait">
+<head>
+<script>
+function finish() {
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(finish, 5000)">
+<iframe src="561981-1-iframe.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/561981-2-iframe.xhtml b/dom/base/crashtests/561981-2-iframe.xhtml
new file mode 100644
index 000000000..7164c0a6a
--- /dev/null
+++ b/dom/base/crashtests/561981-2-iframe.xhtml
@@ -0,0 +1,38 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+var i = 0;
+
+function init()
+{
+ targetWindow = window.frames[0];
+ targetDocument = targetWindow.document;
+ targetDocument.designMode = 'on';
+ setTimeout(whack, 30);
+}
+
+function whack()
+{
+ if (++i > 100) return;
+ document.documentElement.style.MozBinding = 'url("data:text/xml,' + encodeURIComponent("<bindings xmlns=\"http://www.mozilla.org/xbl\"><binding id=\"foo\" g=\""+Math.random()+"\"><content>\n<\/content><\/binding><\/bindings>\n") + '")';
+ setTimeout(bonk, 10);
+}
+
+function bonk()
+{
+ document.getElementById("i").style.MozBinding = 'url("data:text/xml,' + encodeURIComponent("<bindings xmlns=\"http://www.mozilla.org/xbl\"><binding id=\"foo\" g=\""+Math.random()+"\"><content>\n\n<\/content><\/binding><\/bindings>\n") + '")';
+ setTimeout(whack, 10);
+}
+
+]]>
+</script>
+</head>
+
+<body onload="init()">
+
+<iframe id="i" src="data:text/html," style="width: 95%; height: 500px;"/>
+
+</body>
+</html>
diff --git a/dom/base/crashtests/561981-2.html b/dom/base/crashtests/561981-2.html
new file mode 100644
index 000000000..bac13eeea
--- /dev/null
+++ b/dom/base/crashtests/561981-2.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait">
+<head>
+<script>
+function finish() {
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="setTimeout(finish, 5000)">
+<iframe src="561981-2-iframe.xhtml"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/564079-1.html b/dom/base/crashtests/564079-1.html
new file mode 100644
index 000000000..1c97ff967
--- /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 000000000..93786e222
--- /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 000000000..ceeb923f4
--- /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 000000000..8131d5f1f
--- /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 000000000..4fb854a42
--- /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 000000000..3b653c5e7
--- /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 000000000..d433de0d5
--- /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.parentNode.removeChild(n); }
+ </script>
+ </head>
+ <body onload="boom();">
+ <iframe id="iframe" src="data:text/html,S"></iframe>
+ </body>
+</html>
diff --git a/dom/base/crashtests/593302-2.html b/dom/base/crashtests/593302-2.html
new file mode 100644
index 000000000..60e77d488
--- /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 000000000..82cae2636
--- /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 000000000..3cab264c0
--- /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 000000000..6eb9f4657
--- /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 000000000..8243e462e
--- /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 000000000..bfc48fc2c
--- /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(Components.interfaces.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 000000000..b929b2669
--- /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 000000000..c81479c20
--- /dev/null
+++ b/dom/base/crashtests/606729-1.html
@@ -0,0 +1 @@
+<script async src="data:">0</script>
diff --git a/dom/base/crashtests/609560-1.xhtml b/dom/base/crashtests/609560-1.xhtml
new file mode 100644
index 000000000..7849c8db7
--- /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 000000000..96dbc2271
--- /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 000000000..7f81422a7
--- /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 000000000..09be38f1e
--- /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 000000000..01e8cd418
--- /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), false);
+ 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 000000000..106428a29
--- /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 000000000..4f5c92927
--- /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 000000000..b2bc59085
--- /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 000000000..ef34d41a6
--- /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.parentNode.removeChild(f);
+ 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 000000000..40a6a3167
--- /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 000000000..ac11f64e1
--- /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 000000000..499f5a9ef
--- /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/675621-1.html b/dom/base/crashtests/675621-1.html
new file mode 100644
index 000000000..97a15be8d
--- /dev/null
+++ b/dom/base/crashtests/675621-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<body>
+<script>
+document.addEventListener("DOMSubtreeModified", function() {}, false);
+
+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 000000000..a320219a2
--- /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 000000000..c3cbb0c7c
--- /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 000000000..aab88bbc3
--- /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 000000000..735979e9f
--- /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 000000000..6cc600d3b
--- /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 000000000..e3629fbb2
--- /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 000000000..858e66f4f
--- /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 000000000..6e5855e61
--- /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 000000000..23bf4a9c8
--- /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 000000000..4cba753f7
--- /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 000000000..093e02ada
--- /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 000000000..b945c9a29
--- /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 000000000..30479454f
--- /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 000000000..c6d5eb211
--- /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 000000000..fcb558fcf
--- /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 000000000..49764e62b
--- /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 000000000..67b618e7f
--- /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 000000000..5e99e9c3c
--- /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 000000000..a06b7715e
--- /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 000000000..fed796988
--- /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.parentNode.removeChild(f);
+}
+
+</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 000000000..840b5d71a
--- /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.parentNode.removeChild(f);
+}
+
+</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 000000000..214569b02
--- /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 000000000..a9c93aa2e
--- /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, false);
+
+]]>
+</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 000000000..62262a949
--- /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 000000000..f513a33a8
--- /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 000000000..9c05388ff
--- /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 000000000..9d0aa1698
--- /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 000000000..53b48ac9e
--- /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, false);
+ }
+ r.extractContents();
+ }
+
+ document.addEventListener("DOMNodeRemoved", f, false);
+
+ 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 000000000..d32cb84d4
--- /dev/null
+++ b/dom/base/crashtests/771639.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script>
+var root = document.documentElement;
+while(root.firstChild) { root.removeChild(root.firstChild); }
+var div = document.createElement("div");
+root.appendChild(div);
+
+document.addEventListener("DOMNodeRemoved", function() {
+ root.appendChild(document.createTextNode("some mutation"));
+}, false);
+
+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 000000000..846741c08
--- /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 000000000..d5cdd9359
--- /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 000000000..8a691d668
--- /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 000000000..c374632da
--- /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, false);
+function CFcrash() {
+setTimeout('try { test5.appendChild(test4); } catch(e) {}', 50);
+try { test4.parentNode.removeChild(test4); 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 000000000..142e4feb7
--- /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, false);
+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 000000000..6a1960823
--- /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, null).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 000000000..0e7c22c50
--- /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/822691.html b/dom/base/crashtests/822691.html
new file mode 100644
index 000000000..8bc8eb831
--- /dev/null
+++ b/dom/base/crashtests/822691.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var frameDoc = document.getElementById("f").contentDocument;
+
+ var confusedNode = frameDoc.createTextNode("y");
+ confusedNode.__proto__ = document.createTextNode("x");
+ confusedNode.setUserData("key", "data", null);
+ confusedNode.setUserData("key", "data", null);
+}
+
+</script>
+</head>
+<body onload="boom();">
+<iframe src="data:text/html,1" id="f"></iframe>
+</body>
+</html>
diff --git a/dom/base/crashtests/822723.html b/dom/base/crashtests/822723.html
new file mode 100644
index 000000000..e057d5b61
--- /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 000000000..3749b0ec0
--- /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.parentNode.removeChild(test5)
+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 000000000..d2b55e61c
--- /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.parentNode.removeChild(a[key].domNode); }}
+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, false);
+</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 000000000..ae5b8d35a
--- /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 000000000..dc40c45d6
--- /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 000000000..1a5a4d31f
--- /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 000000000..4cab24d87
--- /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 000000000..da9d7f0d2
--- /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, false);
+function CFcrash() {
+try { test1.parentNode.removeChild(test1); } 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 000000000..3cd77fd21
--- /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, false);
+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 000000000..29429b8df
--- /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, false);
+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 000000000..a75fa3c45
--- /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 000000000..ed10dff5a
--- /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, false);
+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 000000000..e8df81a5c
--- /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, false);
+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 000000000..4de533a88
--- /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 000000000..9022837bf
--- /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 000000000..47658850f
--- /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, false);
+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 000000000..2d513449a
--- /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, false);
+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 000000000..4d78893d8
--- /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 000000000..0d09ebf79
--- /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, false);
+ a.insertAdjacentHTML("afterBegin", "<span></span>");
+ };
+
+ window.addEventListener("DOMSubtreeModified", f, false);
+}
+
+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 000000000..9b72b6c02
--- /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.parentNode.removeChild(a[key].domNode); }}
+document.addEventListener("DOMContentLoaded", CFcrash, false);
+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 000000000..af084e417
--- /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, false);
+function CFcrash() {
+try { if (test1 != docElement) { test1.parentNode.removeChild(test1); test1 = test4; }} catch(e) {}
+try { if (test1 != docElement) { test1.parentNode.removeChild(test1); }} 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 000000000..ac5d772b6
--- /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 src="data:text/html,<meta charset=UTF-8><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 000000000..7e8dd47f8
--- /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 000000000..42303a509
--- /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 000000000..fdb1a75ea
--- /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 000000000..9cba893f3
--- /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 000000000..12f744109
--- /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 000000000..de60573cc
--- /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 000000000..cf2c15e9e
--- /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 000000000..03814c9f7
--- /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();
+}, false);
+
+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','.');
+
+}, false);
+
+JNCNIPON.appendChild(a);
+
+var CACEQGDA=document.createElement('whatever2');
+
+CACEQGDA.addEventListener('DOMSubtreeModified',function EventHandlerEventHandlerMOOLBGNC(){
+
+}, false);
+
+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 000000000..070548edd
--- /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 000000000..09274ed16
--- /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 000000000..a0739f074
--- /dev/null
+++ b/dom/base/crashtests/crashtests.list
@@ -0,0 +1,211 @@
+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 308120-1.xul
+load 324871-1.html
+load 325730-1.html
+load 326618-1.html
+load 326646-1.html
+load 326778-1.xul
+load 326865-1.html
+load 327571-1.html
+load 327694.html
+load 327695-1.html
+load 329481-1.xhtml
+load 330925-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 348049-1.xhtml
+load 349355-1.html
+load 354645-1.xul
+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 395469-1.xhtml
+load 395469-2.xhtml
+load 398088-1.xul
+skip load 399712-1.html # sporadically times out (bug 473680)
+load 400763-1.html
+load 401993-1.html
+load 404869-1.xul
+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
+skip load 458637-1.html # sporadically times out (bug 473680)
+load 462947.html
+load 467392.html
+load 472593-1.html
+load 473284.xul
+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 561981-1.html
+load 561981-2.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 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
+load 642022-1.html
+load 646184.html
+load 658845-1.svg
+load 666869.html
+load 667336-1.html
+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 822691.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
+pref(dom.webcomponents.enabled,true) load 1024428-1.html
+load 1026714.html
+pref(dom.webcomponents.enabled,true) load 1027461-1.html
+pref(dom.webcomponents.enabled,true) load 1029710.html
+load 1154598.xhtml
+load 1157995.html
+load 1158412.html
+load 1181619.html
+load structured_clone_container_throws.html
+HTTP(..) load xhr_abortinprogress.html
+load xhr_empty_datauri.html
+load xhr_html_nullresponse.html
+load 1230422.html
+load 1251361.html
+load 1304437.html
+pref(clipboard.autocopy,true) load 1385272-1.html
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 000000000..c92c6f4ae
--- /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_abortinprogress.html b/dom/base/crashtests/xhr_abortinprogress.html
new file mode 100644
index 000000000..5345e9bf2
--- /dev/null
+++ b/dom/base/crashtests/xhr_abortinprogress.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset=UTF-8>
+<script>"use strict";
+(function(){
+ var req = new XMLHttpRequest();
+ req.open('GET', '?' + Date.now());
+ req.responseType = 'moz-blob';
+ var b = null;
+ req.onprogress = function(e) {
+ b = req.response;
+ };
+ req.onreadystatechange = function(e) {
+ if (req.readyState == 3) {
+ req.abort();
+ }
+ if (req.readyState == 4) {
+ document.documentElement.removeAttribute('class');
+ }
+ };
+ req.send();
+})();
+</script>
diff --git a/dom/base/crashtests/xhr_empty_datauri.html b/dom/base/crashtests/xhr_empty_datauri.html
new file mode 100644
index 000000000..77988cfe2
--- /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 000000000..47c85db9b
--- /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 000000000..9c84f42a3
--- /dev/null
+++ b/dom/base/domerr.msg
@@ -0,0 +1,168 @@
+/* -*- 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(InvalidPointerId, "Invalid pointer id.", NS_ERROR_DOM_INVALID_POINTER_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)
+
+/* XXX Should be JavaScript native TypeError */
+DOM4_MSG_DEF(TypeError, "The method parameter is missing or invalid.", NS_ERROR_TYPE_ERR)
+DOM4_MSG_DEF(RangeError, "The method parameter is out of valid range.", NS_ERROR_RANGE_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)
+
+/* SVG DOM errors from http://www.w3.org/TR/SVG11/svgdom.html */
+
+DOM4_MSG_DEF(TypeError, "Unknown or invalid type", NS_ERROR_DOM_SVG_WRONG_TYPE_ERR)
+DOM4_MSG_DEF(InvalidStateError, "The matrix could not be computed", NS_ERROR_DOM_SVG_MATRIX_NOT_INVERTABLE)
+
+/* XPath errors from http://www.w3.org/TR/DOM-Level-3-XPath/ */
+
+DOM4_MSG_DEF(SyntaxError, "The expression is not a legal expression.", NS_ERROR_DOM_INVALID_EXPRESSION_ERR)
+DOM4_MSG_DEF(TypeError, "The expression cannot be converted to return the specified type.", NS_ERROR_DOM_TYPE_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. For example, an object such as an object store or index already exists and a new one was being attempted to be created.", NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR)
+DOM4_MSG_DEF(DataError, "Data provided to an operation does not meet requirements.", NS_ERROR_DOM_INDEXEDDB_DATA_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(TimeoutError, "A lock for the transaction could not be obtained in a reasonable time.", NS_ERROR_DOM_INDEXEDDB_TIMEOUT_ERR)
+DOM4_MSG_DEF(QuotaExceededError, "The current transaction exceeded its quota limitations.", NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR)
+
+/* A Non-standard IndexedDB error */
+
+DOM_MSG_DEF(NS_ERROR_DOM_INDEXEDDB_RECOVERABLE_ERR, "The operation failed because the database was prevented from taking an action. The operation might be able to succeed if the application performs some recovery steps and retries the entire transaction. For example, there was not enough remaining storage space, or the storage quota was reached and the user declined to give more space to the database.")
+
+/* 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 */
+
+/* XXX string should be specified by norris */
+DOM_MSG_DEF(NS_ERROR_DOM_SECMAN_ERR, "Unable to obtain security manager")
+DOM_MSG_DEF(NS_ERROR_DOM_WRONG_TYPE_ERR, "Object is of wrong type")
+DOM_MSG_DEF(NS_ERROR_DOM_NOT_OBJECT_ERR, "Parameter is not an object")
+DOM_MSG_DEF(NS_ERROR_DOM_NOT_XPC_OBJECT_ERR, "Parameter is not a XPConnect object")
+DOM_MSG_DEF(NS_ERROR_DOM_NOT_NUMBER_ERR, "Parameter is not a number")
+DOM_MSG_DEF(NS_ERROR_DOM_NOT_BOOLEAN_ERR, "Parameter is not a boolean")
+DOM_MSG_DEF(NS_ERROR_DOM_NOT_FUNCTION_ERR, "Parameter is not a Function")
+DOM_MSG_DEF(NS_ERROR_DOM_TOO_FEW_PARAMETERS_ERR, "Too few parameters to method")
+DOM_MSG_DEF(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN, "Illegal document.domain value")
+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")
+DOM_MSG_DEF(NS_ERROR_DOM_QUOTA_REACHED, "Persistent storage maximum size reached")
+
+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)
+
+/* Web Animations errors */
+
+DOM4_MSG_DEF(NotSupportedError, "Animation to or from an underlying value is not yet supported.", NS_ERROR_DOM_ANIM_MISSING_PROPS_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_NO_AGGREGATION , "Component does not support aggregation")
+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(InvalidStateError, "Invalid service worker registration.", NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR)
+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)
+
+DOM_MSG_DEF(NS_ERROR_DOM_JS_EXCEPTION, "A callback threw an exception")
+DOM_MSG_DEF(NS_ERROR_DOM_DOMEXCEPTION, "A DOMException was thrown")
+
+/* 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)
+DOM4_MSG_DEF(InvalidStateError, "XMLHttpRequest state must be OPENED.", NS_ERROR_DOM_INVALID_STATE_XHR_MUST_BE_OPENED)
+DOM4_MSG_DEF(InvalidStateError, "XMLHttpRequest must not be sending.", NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_SENDING)
+DOM4_MSG_DEF(InvalidStateError, "XMLHttpRequest state must not be LOADING or DONE.", NS_ERROR_DOM_INVALID_STATE_XHR_MUST_NOT_BE_LOADING_OR_DONE)
+DOM4_MSG_DEF(InvalidStateError, "responseXML is only available if responseType is '' or 'document'.", NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSEXML)
+DOM4_MSG_DEF(InvalidStateError, "responseText is only available if responseType is '', 'document', or 'moz-chunked-text'.", NS_ERROR_DOM_INVALID_STATE_XHR_HAS_WRONG_RESPONSETYPE_FOR_RESPONSETEXT)
+DOM4_MSG_DEF(InvalidStateError, "synchronous XMLHttpRequests do not support 'moz-chunked-text' or 'moz-chunked-arraybuffer' responseType.", NS_ERROR_DOM_INVALID_STATE_XHR_CHUNKED_RESPONSETYPES_UNSUPPORTED_FOR_SYNC)
+DOM4_MSG_DEF(InvalidAccessError, "synchronous XMLHttpRequests do not support timeout and responseType.", NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC)
diff --git a/dom/base/gen-usecounters.py b/dom/base/gen-usecounters.py
new file mode 100755
index 000000000..5b17c22e4
--- /dev/null
+++ b/dom/base/gen-usecounters.py
@@ -0,0 +1,80 @@
+#!/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/.
+
+from __future__ import print_function
+
+import json
+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_CSS_PROPERTY')
+
+ 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'] == 'property':
+ prop = counter['property_name']
+ print('USE_COUNTER_CSS_PROPERTY(%s, %s)' % (prop, prop), file=f)
+
+ print_optional_macro_undeclare('USE_COUNTER_DOM_METHOD')
+ print_optional_macro_undeclare('USE_COUNTER_DOM_ATTRIBUTE')
+ print_optional_macro_undeclare('USE_COUNTER_CSS_PROPERTY')
+
+def generate_property_map(f, counters):
+ print(AUTOGENERATED_WARNING_COMMENT, file=f)
+ print('''
+enum {
+ #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
+ #define CSS_PROP_LIST_INCLUDE_LOGICAL
+ #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \\
+ kwtable_, stylestruct_, stylestructoffset_, animtype_) \\
+ USE_COUNTER_FOR_CSS_PROPERTY_##method_ = eUseCounter_UNKNOWN,
+ #include "nsCSSPropList.h"
+ #undef CSS_PROP
+ #undef CSS_PROP_LIST_INCLUDE_LOGICAL
+ #undef CSS_PROP_PUBLIC_OR_PRIVATE
+};
+''', file=f)
+ for counter in counters:
+ if counter['type'] == 'property':
+ prop = counter['property_name']
+ print('#define USE_COUNTER_FOR_CSS_PROPERTY_%s eUseCounter_property_%s' % (prop, prop), file=f)
+
+def use_counter_list(output_header, conf_filename):
+ counters = usecounters.read_conf(conf_filename)
+ generate_list(output_header, counters)
+
+def property_map(output_map, conf_filename):
+ counters = usecounters.read_conf(conf_filename)
+ generate_property_map(output_map, counters)
diff --git a/dom/base/messageWakeupService.js b/dom/base/messageWakeupService.js
new file mode 100644
index 000000000..73d7c2431
--- /dev/null
+++ b/dom/base/messageWakeupService.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const CATEGORY_WAKEUP_REQUEST = "wakeup-request";
+
+function MessageWakeupService() { };
+
+MessageWakeupService.prototype =
+{
+ classID: Components.ID("{f9798742-4f7b-4188-86ba-48b116412b29}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+ messagesData: [],
+
+ get messageManager() {
+ if (!this._messageManager)
+ this._messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"].
+ getService(Ci.nsIMessageListenerManager);
+ return this._messageManager;
+ },
+
+ requestWakeup: function(aMessageName, aCid, aIid, aMethod) {
+ this.messagesData[aMessageName] = {
+ cid: aCid,
+ iid: aIid,
+ method: aMethod,
+ };
+
+ this.messageManager.addMessageListener(aMessageName, this);
+ },
+
+ receiveMessage: function(aMessage) {
+ let data = this.messagesData[aMessage.name];
+ // TODO: When bug 593407 is ready, stop doing the wrappedJSObject hack
+ // and use this line instead:
+ // QueryInterface(Ci.nsIMessageListener);
+ let service = Cc[data.cid][data.method](Ci[data.iid]).
+ wrappedJSObject;
+
+ // The receiveMessage() call itself may spin an event loop, and we
+ // do not want to swap listeners in that - it would cause the current
+ // message to be answered by two listeners. So, we call that first,
+ // then queue the swap for the next event loop
+ let ret = service.receiveMessage(aMessage);
+
+ if (data.timer) {
+ // Handle the case of two such messages happening in quick succession
+ data.timer.cancel();
+ data.timer = null;
+ }
+
+ data.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ let self = this;
+ data.timer.initWithCallback(function() {
+ self.messageManager.addMessageListener(aMessage.name, service);
+ self.messageManager.removeMessageListener(aMessage.name, self);
+ delete self.messagesData[aMessage.name];
+ }, 0, Ci.nsITimer.TYPE_ONE_SHOT);
+
+ return ret;
+ },
+
+ observe: function TM_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "profile-after-change":
+ {
+ var catMan = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+ var entries = catMan.enumerateCategory(CATEGORY_WAKEUP_REQUEST);
+ while (entries.hasMoreElements()) {
+ var entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
+ var value = catMan.getCategoryEntry(CATEGORY_WAKEUP_REQUEST, entry);
+ var parts = value.split(",");
+ var cid = parts[0];
+ var iid = parts[1];
+ var method = parts[2];
+ var messages = parts.slice(3);
+ messages.forEach(function(messageName) {
+ this.requestWakeup(messageName, cid, iid, method);
+ }, this);
+ }
+ }
+ break;
+ }
+ },
+};
+
+var components = [MessageWakeupService];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
+
diff --git a/dom/base/messageWakeupService.manifest b/dom/base/messageWakeupService.manifest
new file mode 100644
index 000000000..271e7b8f5
--- /dev/null
+++ b/dom/base/messageWakeupService.manifest
@@ -0,0 +1,4 @@
+component {f9798742-4f7b-4188-86ba-48b116412b29} messageWakeupService.js
+contract @mozilla.org/content/messagewakeupservice;1 {f9798742-4f7b-4188-86ba-48b116412b29}
+category profile-after-change messageWakeupService @mozilla.org/content/messagewakeupservice;1
+
diff --git a/dom/base/moz.build b/dom/base/moz.build
new file mode 100644
index 000000000..d237acb03
--- /dev/null
+++ b/dom/base/moz.build
@@ -0,0 +1,495 @@
+# -*- 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/.
+
+TEST_DIRS += ['test']
+
+XPIDL_SOURCES += [
+ 'mozIDOMWindow.idl',
+ 'nsIContentPolicy.idl',
+ 'nsIContentPolicyBase.idl',
+ 'nsIDocumentEncoder.idl',
+ 'nsIDOMBlob.idl',
+ 'nsIDOMDataChannel.idl',
+ 'nsIDOMDOMCursor.idl',
+ 'nsIDOMDOMRequest.idl',
+ 'nsIDOMFileList.idl',
+ 'nsIDOMFormData.idl',
+ 'nsIDOMParser.idl',
+ 'nsIDOMSerializer.idl',
+ 'nsIDroppedLinkHandler.idl',
+ 'nsIFrameLoader.idl',
+ 'nsIImageLoadingContent.idl',
+ 'nsIMessageManager.idl',
+ 'nsIObjectLoadingContent.idl',
+ 'nsIRemoteWindowContext.idl',
+ 'nsIScriptChannel.idl',
+ 'nsIScriptLoaderObserver.idl',
+ 'nsISelection.idl',
+ 'nsISelectionController.idl',
+ 'nsISelectionDisplay.idl',
+ 'nsISelectionListener.idl',
+ 'nsISelectionPrivate.idl',
+ 'nsISimpleContentPolicy.idl',
+ 'nsISiteSpecificUserAgent.idl',
+ 'nsISlowScriptDebug.idl',
+]
+
+XPIDL_MODULE = 'dom'
+
+EXPORTS += [
+ 'AutocompleteFieldList.h',
+ 'Crypto.h',
+ 'HTMLSplitOnSpacesTokenizer.h',
+ 'IframeSandboxKeywordList.h',
+ 'mozAutoDocUpdate.h',
+ 'mozFlushType.h',
+ 'nsAtomListUtils.h',
+ 'nsAttrAndChildArray.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',
+ 'nsDOMClassInfoClasses.h',
+ 'nsDOMClassInfoID.h',
+ 'nsDOMJSUtils.h',
+ 'nsDOMNavigationTiming.h',
+ 'nsDOMString.h',
+ 'nsFocusManager.h',
+ 'nsFrameMessageManager.h',
+ 'nsGenericDOMDataNode.h',
+ 'nsGkAtomList.h',
+ 'nsGkAtoms.h',
+ 'nsHostObjectProtocolHandler.h',
+ 'nsHostObjectURI.h',
+ 'nsIAnimationObserver.h',
+ 'nsIAttribute.h',
+ 'nsIContent.h',
+ 'nsIContentInlines.h',
+ 'nsIContentIterator.h',
+ 'nsIContentSerializer.h',
+ 'nsIDocument.h',
+ 'nsIDocumentInlines.h',
+ 'nsIDocumentObserver.h',
+ 'nsIDOMClassInfo.h',
+ 'nsIGlobalObject.h',
+ 'nsImageLoadingContent.h',
+ 'nsIMutationObserver.h',
+ 'nsINode.h',
+ 'nsINodeList.h',
+ 'nsIScriptContext.h',
+ 'nsIScriptElement.h',
+ 'nsIScriptGlobalObject.h',
+ 'nsIScriptNameSpaceManager.h',
+ 'nsIScriptObjectPrincipal.h',
+ 'nsIScriptTimeoutHandler.h',
+ 'nsIStyleSheetLinkingElement.h',
+ 'nsITimeoutHandler.h',
+ 'nsJSEnvironment.h',
+ 'nsJSUtils.h',
+ 'nsLineBreaker.h',
+ 'nsMappedAttributeElement.h',
+ 'nsNameSpaceManager.h',
+ 'nsNodeInfoManager.h',
+ 'nsNodeUtils.h',
+ 'nsPIDOMWindow.h',
+ 'nsPIDOMWindowInlines.h',
+ 'nsPIWindowRoot.h',
+ 'nsPropertyTable.h',
+ 'nsRange.h',
+ 'nsReferencedElement.h',
+ 'nsSandboxFlags.h',
+ 'nsScriptLoader.h',
+ 'nsStructuredCloneContainer.h',
+ 'nsStubAnimationObserver.h',
+ 'nsStubDocumentObserver.h',
+ 'nsStubMutationObserver.h',
+ 'nsStyledElement.h',
+ 'nsTextFragment.h',
+ 'nsTraversal.h',
+ 'nsTreeSanitizer.h',
+ 'nsViewportInfo.h',
+ 'nsWindowMemoryReporter.h',
+ 'nsWrapperCache.h',
+ 'nsWrapperCacheInlines.h',
+ 'nsXMLNameSpaceMap.h',
+]
+
+if CONFIG['MOZ_WEBRTC']:
+ EXPORTS += [
+ 'nsDOMDataChannel.h',
+ 'nsDOMDataChannelDeclarations.h',
+ ]
+
+EXPORTS.mozilla += [
+ 'CORSMode.h',
+ 'FeedWriterEnabled.h',
+ 'TextInputProcessor.h',
+ 'UseCounter.h',
+]
+
+EXPORTS.mozilla.dom += [
+ '!UseCounterList.h',
+ 'AnonymousContent.h',
+ 'Attr.h',
+ 'BarProps.h',
+ 'BlobSet.h',
+ 'BodyUtil.h',
+ 'BorrowedAttrInfo.h',
+ 'ChildIterator.h',
+ 'ChromeNodeList.h',
+ 'ChromeUtils.h',
+ 'Comment.h',
+ 'CustomElementRegistry.h',
+ 'DirectionalityUtils.h',
+ 'DocGroup.h',
+ 'DocumentFragment.h',
+ 'DocumentType.h',
+ 'DOMCursor.h',
+ 'DOMError.h',
+ 'DOMException.h',
+ 'DOMImplementation.h',
+ 'DOMIntersectionObserver.h',
+ 'DOMMatrix.h',
+ 'DOMParser.h',
+ 'DOMPoint.h',
+ 'DOMQuad.h',
+ 'DOMRect.h',
+ 'DOMRequest.h',
+ 'DOMStringList.h',
+ 'DOMTokenListSupportedTokens.h',
+ 'Element.h',
+ 'ElementInlines.h',
+ 'EventSource.h',
+ 'File.h',
+ 'FileList.h',
+ 'FileReader.h',
+ 'FormData.h',
+ 'FragmentOrElement.h',
+ 'FromParser.h',
+ 'GroupedSHistory.h',
+ 'IdleDeadline.h',
+ 'IdleRequest.h',
+ 'ImageEncoder.h',
+ 'ImageTracker.h',
+ 'ImportManager.h',
+ 'Link.h',
+ 'Location.h',
+ 'MultipartBlobImpl.h',
+ 'MutableBlobStorage.h',
+ 'MutableBlobStreamListener.h',
+ 'NameSpaceConstants.h',
+ 'Navigator.h',
+ 'NodeInfo.h',
+ 'NodeInfoInlines.h',
+ 'NodeIterator.h',
+ 'PartialSHistory.h',
+ 'Pose.h',
+ 'ProcessGlobal.h',
+ 'ResponsiveImageSelector.h',
+ 'SameProcessMessageQueue.h',
+ 'ScreenOrientation.h',
+ 'ScriptSettings.h',
+ 'ShadowRoot.h',
+ 'StructuredCloneHolder.h',
+ 'StructuredCloneTags.h',
+ 'StyleSheetList.h',
+ 'SubtleCrypto.h',
+ 'TabGroup.h',
+ 'Text.h',
+ 'Timeout.h',
+ 'TreeWalker.h',
+ 'WebKitCSSMatrix.h',
+ 'WebSocket.h',
+ 'WindowOrientationObserver.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AnonymousContent.cpp',
+ 'Attr.cpp',
+ 'BarProps.cpp',
+ 'BlobSet.cpp',
+ 'BodyUtil.cpp',
+ 'BorrowedAttrInfo.cpp',
+ 'ChildIterator.cpp',
+ 'ChromeNodeList.cpp',
+ 'ChromeUtils.cpp',
+ 'Comment.cpp',
+ 'Crypto.cpp',
+ 'CustomElementRegistry.cpp',
+ 'DirectionalityUtils.cpp',
+ 'DocGroup.cpp',
+ 'DocumentFragment.cpp',
+ 'DocumentType.cpp',
+ 'DOMCursor.cpp',
+ 'DOMError.cpp',
+ 'DOMException.cpp',
+ 'DOMImplementation.cpp',
+ 'DOMMatrix.cpp',
+ 'DOMParser.cpp',
+ 'DOMPoint.cpp',
+ 'DOMQuad.cpp',
+ 'DOMRect.cpp',
+ 'DOMRequest.cpp',
+ 'DOMStringList.cpp',
+ 'Element.cpp',
+ 'EventSource.cpp',
+ 'File.cpp',
+ 'FileList.cpp',
+ 'FileReader.cpp',
+ 'FormData.cpp',
+ 'FragmentOrElement.cpp',
+ 'GroupedSHistory.cpp',
+ 'IdleDeadline.cpp',
+ 'IdleRequest.cpp',
+ 'ImageEncoder.cpp',
+ 'ImageTracker.cpp',
+ 'ImportManager.cpp',
+ 'Link.cpp',
+ 'Location.cpp',
+ 'MultipartBlobImpl.cpp',
+ 'MutableBlobStorage.cpp',
+ 'MutableBlobStreamListener.cpp',
+ 'Navigator.cpp',
+ 'NodeInfo.cpp',
+ 'NodeIterator.cpp',
+ 'nsAtomListUtils.cpp',
+ 'nsAttrAndChildArray.cpp',
+ 'nsAttrValue.cpp',
+ 'nsAttrValueOrString.cpp',
+ 'nsCCUncollectableMarker.cpp',
+ 'nsContentAreaDragDrop.cpp',
+ 'nsContentIterator.cpp',
+ 'nsContentList.cpp',
+ 'nsContentPermissionHelper.cpp',
+ 'nsContentPolicy.cpp',
+ 'nsContentSink.cpp',
+ 'nsCopySupport.cpp',
+ 'nsDataDocumentContentPolicy.cpp',
+ 'nsDocument.cpp',
+ 'nsDocumentEncoder.cpp',
+ 'nsDOMAttributeMap.cpp',
+ 'nsDOMCaretPosition.cpp',
+ 'nsDOMClassInfo.cpp',
+ 'nsDOMMutationObserver.cpp',
+ 'nsDOMNavigationTiming.cpp',
+ 'nsDOMSerializer.cpp',
+ 'nsDOMTokenList.cpp',
+ 'nsDOMWindowList.cpp',
+ 'nsFocusManager.cpp',
+ 'nsFrameLoader.cpp',
+ 'nsGenConImageContent.cpp',
+ 'nsGenericDOMDataNode.cpp',
+ 'nsGkAtoms.cpp',
+ 'nsGlobalWindowCommands.cpp',
+ 'nsHistory.cpp',
+ 'nsHostObjectProtocolHandler.cpp',
+ 'nsHostObjectURI.cpp',
+ 'nsHTMLContentSerializer.cpp',
+ 'nsIGlobalObject.cpp',
+ 'nsINode.cpp',
+ 'nsInProcessTabChildGlobal.cpp',
+ 'nsJSEnvironment.cpp',
+ 'nsJSTimeoutHandler.cpp',
+ 'nsJSUtils.cpp',
+ 'nsLineBreaker.cpp',
+ 'nsMappedAttributeElement.cpp',
+ 'nsMappedAttributes.cpp',
+ 'nsMimeTypeArray.cpp',
+ 'nsNameSpaceManager.cpp',
+ 'nsNoDataProtocolContentPolicy.cpp',
+ 'nsNodeInfoManager.cpp',
+ 'nsNodeUtils.cpp',
+ 'nsOpenURIInFrameParams.cpp',
+ 'nsPlainTextSerializer.cpp',
+ 'nsPropertyTable.cpp',
+ 'nsQueryContentEventResult.cpp',
+ 'nsRange.cpp',
+ 'nsReferencedElement.cpp',
+ 'nsScreen.cpp',
+ 'nsScriptElement.cpp',
+ 'nsScriptLoader.cpp',
+ 'nsScriptNameSpaceManager.cpp',
+ 'nsStructuredCloneContainer.cpp',
+ 'nsStubAnimationObserver.cpp',
+ 'nsStubDocumentObserver.cpp',
+ 'nsStubMutationObserver.cpp',
+ 'nsStyledElement.cpp',
+ 'nsStyleLinkElement.cpp',
+ 'nsSyncLoadService.cpp',
+ 'nsTextFragment.cpp',
+ 'nsTextNode.cpp',
+ 'nsTraversal.cpp',
+ 'nsTreeSanitizer.cpp',
+ 'nsViewportInfo.cpp',
+ 'nsWindowMemoryReporter.cpp',
+ 'nsWindowRoot.cpp',
+ 'nsWrapperCache.cpp',
+ 'nsXHTMLContentSerializer.cpp',
+ 'nsXMLContentSerializer.cpp',
+ 'nsXMLNameSpaceMap.cpp',
+ 'PartialSHistory.cpp',
+ 'Pose.cpp',
+ 'PostMessageEvent.cpp',
+ 'ProcessGlobal.cpp',
+ 'ResponsiveImageSelector.cpp',
+ 'SameProcessMessageQueue.cpp',
+ 'ScreenOrientation.cpp',
+ 'ScriptSettings.cpp',
+ 'ShadowRoot.cpp',
+ 'StructuredCloneHolder.cpp',
+ 'StyleSheetList.cpp',
+ 'SubtleCrypto.cpp',
+ 'TabGroup.cpp',
+ 'Text.cpp',
+ 'TextInputProcessor.cpp',
+ 'ThirdPartyUtil.cpp',
+ 'Timeout.cpp',
+ 'TreeWalker.cpp',
+ 'WebKitCSSMatrix.cpp',
+ 'WebSocket.cpp',
+ 'WindowNamedPropertiesHandler.cpp',
+ 'WindowOrientationObserver.cpp',
+]
+
+if CONFIG['MOZ_WEBRTC']:
+ UNIFIED_SOURCES += [
+ 'nsDOMDataChannel.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',
+ # This file has a #error "Never include windows.h in this file!"
+ 'nsGlobalWindow.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']
+
+EXTRA_COMPONENTS += [
+ 'contentAreaDropListener.js',
+ 'contentAreaDropListener.manifest',
+ 'messageWakeupService.js',
+ 'messageWakeupService.manifest',
+ 'SlowScriptDebug.js',
+ 'SlowScriptDebug.manifest',
+]
+
+# Firefox for Android provides an alternate version of this component
+if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
+ EXTRA_COMPONENTS += [
+ 'SiteSpecificUserAgent.js',
+ 'SiteSpecificUserAgent.manifest',
+ ]
+
+EXTRA_JS_MODULES += [
+ 'DOMRequestHelper.jsm',
+ 'IndexedDBHelper.jsm',
+]
+
+LOCAL_INCLUDES += [
+ '../battery',
+ '../events',
+ '../media',
+ '../network',
+ '../time',
+ '/caps',
+ '/docshell/base',
+ '/dom/base',
+ '/dom/geolocation',
+ '/dom/html',
+ '/dom/ipc',
+ '/dom/storage',
+ '/dom/svg',
+ '/dom/u2f',
+ '/dom/workers',
+ '/dom/xbl',
+ '/dom/xml',
+ '/dom/xslt/xpath',
+ '/dom/xul',
+ '/gfx/2d',
+ '/image',
+ '/js/xpconnect/src',
+ '/js/xpconnect/wrappers',
+ '/layout/base',
+ '/layout/forms',
+ '/layout/generic',
+ '/layout/style',
+ '/layout/svg',
+ '/layout/xul',
+ '/netwerk/base',
+ '/security/manager/ssl',
+ '/widget',
+ '/xpcom/ds',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ LOCAL_INCLUDES += [
+ '../system/gonk',
+ ]
+
+if CONFIG['MOZ_WEBRTC']:
+ LOCAL_INCLUDES += [
+ '/netwerk/sctp/datachannel',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']:
+ DEFINES['HAVE_SIDEBAR'] = True
+
+if CONFIG['MOZ_X11']:
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+
+GENERATED_FILES += [
+ 'PropertyUseCounterMap.inc',
+ 'UseCounterList.h',
+]
+
+countermap = GENERATED_FILES['PropertyUseCounterMap.inc']
+countermap.script = 'gen-usecounters.py:property_map'
+countermap.inputs = ['UseCounters.conf']
+
+counterlist = GENERATED_FILES['UseCounterList.h']
+counterlist.script = 'gen-usecounters.py:use_counter_list'
+counterlist.inputs = ['UseCounters.conf']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/base/mozAutoDocUpdate.h b/dom/base/mozAutoDocUpdate.h
new file mode 100644
index 000000000..10ce7f15e
--- /dev/null
+++ b/dom/base/mozAutoDocUpdate.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 mozAutoDocUpdate_h_
+#define mozAutoDocUpdate_h_
+
+#include "nsContentUtils.h" // For AddScriptBlocker() and RemoveScriptBlocker().
+#include "nsIDocument.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(nsIDocument* aDocument, nsUpdateType aUpdateType,
+ bool aNotify) :
+ mDocument(aNotify ? aDocument : nullptr),
+ mUpdateType(aUpdateType)
+ {
+ if (mDocument) {
+ mDocument->BeginUpdate(mUpdateType);
+ }
+ else {
+ nsContentUtils::AddScriptBlocker();
+ }
+ }
+
+ ~mozAutoDocUpdate()
+ {
+ if (mDocument) {
+ mDocument->EndUpdate(mUpdateType);
+ }
+ else {
+ nsContentUtils::RemoveScriptBlocker();
+ }
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+ nsUpdateType mUpdateType;
+};
+
+#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,type,notify) \
+ mozAutoDocUpdate MOZ_AUTO_DOC_UPDATE_PASTE(_autoDocUpdater_, __LINE__) \
+ (doc,type,notify)
+
+
+/**
+ * Creates an update batch only under certain conditions.
+ * Use this rather than mozAutoDocUpdate when you expect inner updates
+ * to notify but you don't always want to spec cycles creating a batch.
+ * This is needed to avoid having this batch always create a blocker,
+ * but then have inner mozAutoDocUpdate call the last EndUpdate before.
+ * we remove that blocker. See bug 423269.
+ */
+class MOZ_STACK_CLASS mozAutoDocConditionalContentUpdateBatch
+{
+public:
+ mozAutoDocConditionalContentUpdateBatch(nsIDocument* aDocument,
+ bool aNotify) :
+ mDocument(aNotify ? aDocument : nullptr)
+ {
+ if (mDocument) {
+ mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
+ }
+ }
+
+ ~mozAutoDocConditionalContentUpdateBatch()
+ {
+ if (mDocument) {
+ mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
+ }
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+};
+
+#endif
diff --git a/dom/base/mozFlushType.h b/dom/base/mozFlushType.h
new file mode 100644
index 000000000..0a5680176
--- /dev/null
+++ b/dom/base/mozFlushType.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 mozFlushType_h___
+#define mozFlushType_h___
+
+/**
+ * This is the enum used by nsIDocument::FlushPendingNotifications to
+ * decide what to flush.
+ *
+ * Please note that if you change these values, you should sync it with the
+ * flushTypeNames array inside PresShell::FlushPendingNotifications.
+ */
+enum mozFlushType {
+ Flush_None = 0, /* Actually don't flush anything */
+ Flush_Content = 1, /* flush the content model construction */
+ Flush_ContentAndNotify = 2, /* As above, plus flush the frame model
+ construction and other nsIMutationObserver
+ notifications. */
+ Flush_Style = 3, /* As above, plus flush style reresolution */
+ Flush_Frames = Flush_Style,
+ Flush_InterruptibleLayout = 4, /* As above, plus flush reflow,
+ but allow it to be interrupted (so
+ an incomplete layout may result) */
+ Flush_Layout = 5, /* As above, but layout must run to
+ completion */
+ Flush_Display = 6 /* As above, plus flush painting */
+};
+
+namespace mozilla {
+
+struct ChangesToFlush {
+ ChangesToFlush(mozFlushType aFlushType, bool aFlushAnimations)
+ : mFlushType(aFlushType)
+ , mFlushAnimations(aFlushAnimations)
+ {}
+
+ mozFlushType mFlushType;
+ bool mFlushAnimations;
+};
+
+} // namespace mozilla
+
+#endif /* mozFlushType_h___ */
diff --git a/dom/base/mozIDOMWindow.idl b/dom/base/mozIDOMWindow.idl
new file mode 100644
index 000000000..4b15d54b6
--- /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/nsAtomListUtils.cpp b/dom/base/nsAtomListUtils.cpp
new file mode 100644
index 000000000..e6dc8f728
--- /dev/null
+++ b/dom/base/nsAtomListUtils.cpp
@@ -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/. */
+
+/*
+ * Static helper class for implementing atom lists.
+ */
+
+#include "nsAtomListUtils.h"
+#include "nsIAtom.h"
+#include "nsStaticAtom.h"
+
+/* static */ bool
+nsAtomListUtils::IsMember(nsIAtom *aAtom,
+ const nsStaticAtom* aInfo,
+ uint32_t aInfoCount)
+{
+ for (const nsStaticAtom *info = aInfo, *info_end = aInfo + aInfoCount;
+ info != info_end; ++info) {
+ if (aAtom == *(info->mAtom))
+ return true;
+ }
+ return false;
+}
diff --git a/dom/base/nsAtomListUtils.h b/dom/base/nsAtomListUtils.h
new file mode 100644
index 000000000..1f1d8aceb
--- /dev/null
+++ b/dom/base/nsAtomListUtils.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/. */
+
+/*
+ * Static helper class for implementing atom lists.
+ */
+
+#ifndef nsAtomListUtils_h__
+#define nsAtomListUtils_h__
+
+#include <stdint.h>
+
+class nsIAtom;
+struct nsStaticAtom;
+
+class nsAtomListUtils {
+public:
+ static bool IsMember(nsIAtom *aAtom,
+ const nsStaticAtom* aInfo,
+ uint32_t aInfoCount);
+};
+
+#endif /* !defined(nsAtomListUtils_h__) */
diff --git a/dom/base/nsAttrAndChildArray.cpp b/dom/base/nsAttrAndChildArray.cpp
new file mode 100644
index 000000000..b285ee003
--- /dev/null
+++ b/dom/base/nsAttrAndChildArray.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/. */
+
+/*
+ * Storage of the children and attributes of a DOM node; storage for
+ * the two is unified to minimize footprint.
+ */
+
+#include "nsAttrAndChildArray.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "nsMappedAttributeElement.h"
+#include "nsString.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsRuleWalker.h"
+#include "nsMappedAttributes.h"
+#include "nsUnicharUtils.h"
+#include "nsContentUtils.h" // nsAutoScriptBlocker
+
+using mozilla::CheckedUint32;
+
+/*
+CACHE_POINTER_SHIFT indicates how many steps to downshift the |this| pointer.
+It should be small enough to not cause collisions between adjecent arrays, and
+large enough to make sure that all indexes are used. The size below is based
+on the size of the smallest possible element (currently 24[*] bytes) which is
+the smallest distance between two nsAttrAndChildArray. 24/(2^_5_) is 0.75.
+This means that two adjacent nsAttrAndChildArrays will overlap one in 4 times.
+However not all elements will have enough children to get cached. And any
+allocator that doesn't return addresses aligned to 64 bytes will ensure that
+any index will get used.
+
+[*] sizeof(Element) + 4 bytes for nsIDOMElement vtable pointer.
+*/
+
+#define CACHE_POINTER_SHIFT 5
+#define CACHE_NUM_SLOTS 128
+#define CACHE_CHILD_LIMIT 10
+
+#define CACHE_GET_INDEX(_array) \
+ ((NS_PTR_TO_INT32(_array) >> CACHE_POINTER_SHIFT) & \
+ (CACHE_NUM_SLOTS - 1))
+
+struct IndexCacheSlot
+{
+ const nsAttrAndChildArray* array;
+ int32_t index;
+};
+
+// This is inited to all zeroes since it's static. Though even if it wasn't
+// the worst thing that'd happen is a small inefficency if you'd get a false
+// positive cachehit.
+static IndexCacheSlot indexCache[CACHE_NUM_SLOTS];
+
+static
+inline
+void
+AddIndexToCache(const nsAttrAndChildArray* aArray, int32_t aIndex)
+{
+ uint32_t ix = CACHE_GET_INDEX(aArray);
+ indexCache[ix].array = aArray;
+ indexCache[ix].index = aIndex;
+}
+
+static
+inline
+int32_t
+GetIndexFromCache(const nsAttrAndChildArray* aArray)
+{
+ uint32_t ix = CACHE_GET_INDEX(aArray);
+ return indexCache[ix].array == aArray ? indexCache[ix].index : -1;
+}
+
+
+/**
+ * Due to a compiler bug in VisualAge C++ for AIX, we need to return the
+ * address of the first index into mBuffer here, instead of simply returning
+ * mBuffer itself.
+ *
+ * See Bug 231104 for more information.
+ */
+#define ATTRS(_impl) \
+ reinterpret_cast<InternalAttr*>(&((_impl)->mBuffer[0]))
+
+
+#define NS_IMPL_EXTRA_SIZE \
+ ((sizeof(Impl) - sizeof(mImpl->mBuffer)) / sizeof(void*))
+
+nsAttrAndChildArray::nsAttrAndChildArray()
+ : mImpl(nullptr)
+{
+}
+
+nsAttrAndChildArray::~nsAttrAndChildArray()
+{
+ if (!mImpl) {
+ return;
+ }
+
+ Clear();
+
+ free(mImpl);
+}
+
+nsIContent*
+nsAttrAndChildArray::GetSafeChildAt(uint32_t aPos) const
+{
+ if (aPos < ChildCount()) {
+ return ChildAt(aPos);
+ }
+
+ return nullptr;
+}
+
+nsIContent * const *
+nsAttrAndChildArray::GetChildArray(uint32_t* aChildCount) const
+{
+ *aChildCount = ChildCount();
+
+ if (!*aChildCount) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<nsIContent**>(mImpl->mBuffer + AttrSlotsSize());
+}
+
+nsresult
+nsAttrAndChildArray::InsertChildAt(nsIContent* aChild, uint32_t aPos)
+{
+ NS_ASSERTION(aChild, "nullchild");
+ NS_ASSERTION(aPos <= ChildCount(), "out-of-bounds");
+
+ uint32_t offset = AttrSlotsSize();
+ uint32_t childCount = ChildCount();
+
+ NS_ENSURE_TRUE(childCount < ATTRCHILD_ARRAY_MAX_CHILD_COUNT,
+ NS_ERROR_FAILURE);
+
+ // First try to fit new child in existing childlist
+ if (mImpl && offset + childCount < mImpl->mBufferSize) {
+ void** pos = mImpl->mBuffer + offset + aPos;
+ if (childCount != aPos) {
+ memmove(pos + 1, pos, (childCount - aPos) * sizeof(nsIContent*));
+ }
+ SetChildAtPos(pos, aChild, aPos, childCount);
+
+ SetChildCount(childCount + 1);
+
+ return NS_OK;
+ }
+
+ // Try to fit new child in existing buffer by compressing attrslots
+ if (offset && !mImpl->mBuffer[offset - ATTRSIZE]) {
+ // Compress away all empty slots while we're at it. This might not be the
+ // optimal thing to do.
+ uint32_t attrCount = NonMappedAttrCount();
+ void** newStart = mImpl->mBuffer + attrCount * ATTRSIZE;
+ void** oldStart = mImpl->mBuffer + offset;
+ memmove(newStart, oldStart, aPos * sizeof(nsIContent*));
+ memmove(&newStart[aPos + 1], &oldStart[aPos],
+ (childCount - aPos) * sizeof(nsIContent*));
+ SetChildAtPos(newStart + aPos, aChild, aPos, childCount);
+
+ SetAttrSlotAndChildCount(attrCount, childCount + 1);
+
+ return NS_OK;
+ }
+
+ // We can't fit in current buffer, Realloc time!
+ if (!GrowBy(1)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ void** pos = mImpl->mBuffer + offset + aPos;
+ if (childCount != aPos) {
+ memmove(pos + 1, pos, (childCount - aPos) * sizeof(nsIContent*));
+ }
+ SetChildAtPos(pos, aChild, aPos, childCount);
+
+ SetChildCount(childCount + 1);
+
+ return NS_OK;
+}
+
+void
+nsAttrAndChildArray::RemoveChildAt(uint32_t aPos)
+{
+ // Just store the return value of TakeChildAt in an nsCOMPtr to
+ // trigger a release.
+ nsCOMPtr<nsIContent> child = TakeChildAt(aPos);
+}
+
+already_AddRefed<nsIContent>
+nsAttrAndChildArray::TakeChildAt(uint32_t aPos)
+{
+ NS_ASSERTION(aPos < ChildCount(), "out-of-bounds");
+
+ uint32_t childCount = ChildCount();
+ void** pos = mImpl->mBuffer + AttrSlotsSize() + aPos;
+ nsIContent* child = static_cast<nsIContent*>(*pos);
+ if (child->mPreviousSibling) {
+ child->mPreviousSibling->mNextSibling = child->mNextSibling;
+ }
+ if (child->mNextSibling) {
+ child->mNextSibling->mPreviousSibling = child->mPreviousSibling;
+ }
+ child->mPreviousSibling = child->mNextSibling = nullptr;
+
+ memmove(pos, pos + 1, (childCount - aPos - 1) * sizeof(nsIContent*));
+ SetChildCount(childCount - 1);
+
+ return dont_AddRef(child);
+}
+
+int32_t
+nsAttrAndChildArray::IndexOfChild(const nsINode* aPossibleChild) const
+{
+ if (!mImpl) {
+ return -1;
+ }
+ void** children = mImpl->mBuffer + AttrSlotsSize();
+ // Use signed here since we compare count to cursor which has to be signed
+ int32_t i, count = ChildCount();
+
+ if (count >= CACHE_CHILD_LIMIT) {
+ int32_t cursor = GetIndexFromCache(this);
+ // Need to compare to count here since we may have removed children since
+ // the index was added to the cache.
+ // We're also relying on that GetIndexFromCache returns -1 if no cached
+ // index was found.
+ if (cursor >= count) {
+ cursor = -1;
+ }
+
+ // Seek outward from the last found index. |inc| will change sign every
+ // run through the loop. |sign| just exists to make sure the absolute
+ // value of |inc| increases each time through.
+ int32_t inc = 1, sign = 1;
+ while (cursor >= 0 && cursor < count) {
+ if (children[cursor] == aPossibleChild) {
+ AddIndexToCache(this, cursor);
+
+ return cursor;
+ }
+
+ cursor += inc;
+ inc = -inc - sign;
+ sign = -sign;
+ }
+
+ // We ran into one 'edge'. Add inc to cursor once more to get back to
+ // the 'side' where we still need to search, then step in the |sign|
+ // direction.
+ cursor += inc;
+
+ if (sign > 0) {
+ for (; cursor < count; ++cursor) {
+ if (children[cursor] == aPossibleChild) {
+ AddIndexToCache(this, cursor);
+
+ return static_cast<int32_t>(cursor);
+ }
+ }
+ }
+ else {
+ for (; cursor >= 0; --cursor) {
+ if (children[cursor] == aPossibleChild) {
+ AddIndexToCache(this, cursor);
+
+ return static_cast<int32_t>(cursor);
+ }
+ }
+ }
+
+ // The child wasn't even in the remaining children
+ return -1;
+ }
+
+ for (i = 0; i < count; ++i) {
+ if (children[i] == aPossibleChild) {
+ return static_cast<int32_t>(i);
+ }
+ }
+
+ return -1;
+}
+
+uint32_t
+nsAttrAndChildArray::AttrCount() const
+{
+ return NonMappedAttrCount() + MappedAttrCount();
+}
+
+const nsAttrValue*
+nsAttrAndChildArray::GetAttr(nsIAtom* aLocalName, int32_t aNamespaceID) const
+{
+ uint32_t i, slotCount = AttrSlotCount();
+ if (aNamespaceID == kNameSpaceID_None) {
+ // This should be the common case so lets make an optimized loop
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.Equals(aLocalName)) {
+ return &ATTRS(mImpl)[i].mValue;
+ }
+ }
+
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->GetAttr(aLocalName);
+ }
+ }
+ else {
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.Equals(aLocalName, aNamespaceID)) {
+ return &ATTRS(mImpl)[i].mValue;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+const nsAttrValue*
+nsAttrAndChildArray::GetAttr(const nsAString& aLocalName) const
+{
+ uint32_t i, slotCount = AttrSlotCount();
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.Equals(aLocalName)) {
+ return &ATTRS(mImpl)[i].mValue;
+ }
+ }
+
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->GetAttr(aLocalName);
+ }
+
+ return nullptr;
+}
+
+const nsAttrValue*
+nsAttrAndChildArray::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);
+ }
+
+ uint32_t i, slotCount = AttrSlotCount();
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.QualifiedNameEquals(aName)) {
+ return &ATTRS(mImpl)[i].mValue;
+ }
+ }
+
+ if (mImpl && mImpl->mMappedAttrs) {
+ const nsAttrValue* val =
+ mImpl->mMappedAttrs->GetAttr(aName);
+ if (val) {
+ return val;
+ }
+ }
+
+ return nullptr;
+}
+
+const nsAttrValue*
+nsAttrAndChildArray::AttrAt(uint32_t aPos) const
+{
+ NS_ASSERTION(aPos < AttrCount(),
+ "out-of-bounds access in nsAttrAndChildArray");
+
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ return &ATTRS(mImpl)[aPos].mValue;
+ }
+
+ return mImpl->mMappedAttrs->AttrAt(aPos - nonmapped);
+}
+
+nsresult
+nsAttrAndChildArray::SetAndSwapAttr(nsIAtom* aLocalName, nsAttrValue& aValue)
+{
+ uint32_t i, slotCount = AttrSlotCount();
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.Equals(aLocalName)) {
+ ATTRS(mImpl)[i].mValue.SwapValueWith(aValue);
+ return NS_OK;
+ }
+ }
+
+ NS_ENSURE_TRUE(i < ATTRCHILD_ARRAY_MAX_ATTR_COUNT,
+ NS_ERROR_FAILURE);
+
+ if (i == slotCount && !AddAttrSlot()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ new (&ATTRS(mImpl)[i].mName) nsAttrName(aLocalName);
+ new (&ATTRS(mImpl)[i].mValue) nsAttrValue();
+ ATTRS(mImpl)[i].mValue.SwapValueWith(aValue);
+
+ return NS_OK;
+}
+
+nsresult
+nsAttrAndChildArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue)
+{
+ int32_t namespaceID = aName->NamespaceID();
+ nsIAtom* localName = aName->NameAtom();
+ if (namespaceID == kNameSpaceID_None) {
+ return SetAndSwapAttr(localName, aValue);
+ }
+
+ uint32_t i, slotCount = AttrSlotCount();
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.Equals(localName, namespaceID)) {
+ ATTRS(mImpl)[i].mName.SetTo(aName);
+ ATTRS(mImpl)[i].mValue.Reset();
+ ATTRS(mImpl)[i].mValue.SwapValueWith(aValue);
+
+ return NS_OK;
+ }
+ }
+
+ NS_ENSURE_TRUE(i < ATTRCHILD_ARRAY_MAX_ATTR_COUNT,
+ NS_ERROR_FAILURE);
+
+ if (i == slotCount && !AddAttrSlot()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ new (&ATTRS(mImpl)[i].mName) nsAttrName(aName);
+ new (&ATTRS(mImpl)[i].mValue) nsAttrValue();
+ ATTRS(mImpl)[i].mValue.SwapValueWith(aValue);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsAttrAndChildArray::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue)
+{
+ NS_ASSERTION(aPos < AttrCount(), "out-of-bounds");
+
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ ATTRS(mImpl)[aPos].mValue.SwapValueWith(aValue);
+ ATTRS(mImpl)[aPos].~InternalAttr();
+
+ uint32_t slotCount = AttrSlotCount();
+ memmove(&ATTRS(mImpl)[aPos],
+ &ATTRS(mImpl)[aPos + 1],
+ (slotCount - aPos - 1) * sizeof(InternalAttr));
+ memset(&ATTRS(mImpl)[slotCount - 1], 0, sizeof(InternalAttr));
+
+ 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
+nsAttrAndChildArray::AttrInfoAt(uint32_t aPos) const
+{
+ NS_ASSERTION(aPos < AttrCount(),
+ "out-of-bounds access in nsAttrAndChildArray");
+
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ return BorrowedAttrInfo(&ATTRS(mImpl)[aPos].mName, &ATTRS(mImpl)[aPos].mValue);
+ }
+
+ return BorrowedAttrInfo(mImpl->mMappedAttrs->NameAt(aPos - nonmapped),
+ mImpl->mMappedAttrs->AttrAt(aPos - nonmapped));
+}
+
+const nsAttrName*
+nsAttrAndChildArray::AttrNameAt(uint32_t aPos) const
+{
+ NS_ASSERTION(aPos < AttrCount(),
+ "out-of-bounds access in nsAttrAndChildArray");
+
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ return &ATTRS(mImpl)[aPos].mName;
+ }
+
+ return mImpl->mMappedAttrs->NameAt(aPos - nonmapped);
+}
+
+const nsAttrName*
+nsAttrAndChildArray::GetSafeAttrNameAt(uint32_t aPos) const
+{
+ uint32_t nonmapped = NonMappedAttrCount();
+ if (aPos < nonmapped) {
+ void** pos = mImpl->mBuffer + aPos * ATTRSIZE;
+ if (!*pos) {
+ return nullptr;
+ }
+
+ return &reinterpret_cast<InternalAttr*>(pos)->mName;
+ }
+
+ if (aPos >= AttrCount()) {
+ return nullptr;
+ }
+
+ return mImpl->mMappedAttrs->NameAt(aPos - nonmapped);
+}
+
+const nsAttrName*
+nsAttrAndChildArray::GetExistingAttrNameFromQName(const nsAString& aName) const
+{
+ uint32_t i, slotCount = AttrSlotCount();
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.QualifiedNameEquals(aName)) {
+ return &ATTRS(mImpl)[i].mName;
+ }
+ }
+
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->GetExistingAttrNameFromQName(aName);
+ }
+
+ return nullptr;
+}
+
+int32_t
+nsAttrAndChildArray::IndexOfAttr(nsIAtom* aLocalName, int32_t aNamespaceID) const
+{
+ int32_t idx;
+ if (mImpl && mImpl->mMappedAttrs && aNamespaceID == kNameSpaceID_None) {
+ idx = mImpl->mMappedAttrs->IndexOfAttr(aLocalName);
+ if (idx >= 0) {
+ return NonMappedAttrCount() + idx;
+ }
+ }
+
+ uint32_t i;
+ uint32_t slotCount = AttrSlotCount();
+ if (aNamespaceID == kNameSpaceID_None) {
+ // This should be the common case so lets make an optimized loop
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.Equals(aLocalName)) {
+ return i;
+ }
+ }
+ }
+ else {
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ if (ATTRS(mImpl)[i].mName.Equals(aLocalName, aNamespaceID)) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+nsresult
+nsAttrAndChildArray::SetAndTakeMappedAttr(nsIAtom* aLocalName,
+ nsAttrValue& aValue,
+ nsMappedAttributeElement* aContent,
+ nsHTMLStyleSheet* aSheet)
+{
+ bool willAdd = true;
+ if (mImpl && mImpl->mMappedAttrs) {
+ willAdd = !mImpl->mMappedAttrs->GetAttr(aLocalName);
+ }
+
+ RefPtr<nsMappedAttributes> mapped =
+ GetModifiableMapped(aContent, aSheet, willAdd);
+
+ mapped->SetAndTakeAttr(aLocalName, aValue);
+
+ return MakeMappedUnique(mapped);
+}
+
+nsresult
+nsAttrAndChildArray::DoSetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet)
+{
+ NS_PRECONDITION(mImpl && mImpl->mMappedAttrs,
+ "Should have mapped attrs here!");
+ if (aSheet == mImpl->mMappedAttrs->GetStyleSheet()) {
+ return NS_OK;
+ }
+
+ RefPtr<nsMappedAttributes> mapped =
+ GetModifiableMapped(nullptr, nullptr, false);
+
+ mapped->SetStyleSheet(aSheet);
+
+ return MakeMappedUnique(mapped);
+}
+
+void
+nsAttrAndChildArray::WalkMappedAttributeStyleRules(nsRuleWalker* aRuleWalker)
+{
+ if (mImpl && mImpl->mMappedAttrs) {
+ aRuleWalker->Forward(mImpl->mMappedAttrs);
+ }
+}
+
+void
+nsAttrAndChildArray::Compact()
+{
+ if (!mImpl) {
+ return;
+ }
+
+ // First compress away empty attrslots
+ uint32_t slotCount = AttrSlotCount();
+ uint32_t attrCount = NonMappedAttrCount();
+ uint32_t childCount = ChildCount();
+
+ if (attrCount < slotCount) {
+ memmove(mImpl->mBuffer + attrCount * ATTRSIZE,
+ mImpl->mBuffer + slotCount * ATTRSIZE,
+ childCount * sizeof(nsIContent*));
+ SetAttrSlotCount(attrCount);
+ }
+
+ // Then resize or free buffer
+ uint32_t newSize = attrCount * ATTRSIZE + childCount;
+ if (!newSize && !mImpl->mMappedAttrs) {
+ free(mImpl);
+ mImpl = nullptr;
+ }
+ else if (newSize < mImpl->mBufferSize) {
+ mImpl = static_cast<Impl*>(realloc(mImpl, (newSize + NS_IMPL_EXTRA_SIZE) * sizeof(nsIContent*)));
+ NS_ASSERTION(mImpl, "failed to reallocate to smaller buffer");
+
+ mImpl->mBufferSize = newSize;
+ }
+}
+
+void
+nsAttrAndChildArray::Clear()
+{
+ if (!mImpl) {
+ return;
+ }
+
+ if (mImpl->mMappedAttrs) {
+ NS_RELEASE(mImpl->mMappedAttrs);
+ }
+
+ uint32_t i, slotCount = AttrSlotCount();
+ for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ ATTRS(mImpl)[i].~InternalAttr();
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ uint32_t end = slotCount * ATTRSIZE + ChildCount();
+ for (i = slotCount * ATTRSIZE; i < end; ++i) {
+ nsIContent* child = static_cast<nsIContent*>(mImpl->mBuffer[i]);
+ // making this false so tree teardown doesn't end up being
+ // O(N*D) (number of nodes times average depth of tree).
+ child->UnbindFromTree(false); // XXX is it better to let the owner do this?
+ // Make sure to unlink our kids from each other, since someone
+ // else could stil be holding references to some of them.
+
+ // XXXbz We probably can't push this assignment down into the |aNullParent|
+ // case of UnbindFromTree because we still need the assignment in
+ // RemoveChildAt. In particular, ContentRemoved fires between
+ // RemoveChildAt and UnbindFromTree, and in ContentRemoved the sibling
+ // chain needs to be correct. Though maybe we could set the prev and next
+ // to point to each other but keep the kid being removed pointing to them
+ // through ContentRemoved so consumers can find where it used to be in the
+ // list?
+ child->mPreviousSibling = child->mNextSibling = nullptr;
+ NS_RELEASE(child);
+ }
+
+ SetAttrSlotAndChildCount(0, 0);
+}
+
+uint32_t
+nsAttrAndChildArray::NonMappedAttrCount() const
+{
+ if (!mImpl) {
+ return 0;
+ }
+
+ uint32_t count = AttrSlotCount();
+ while (count > 0 && !mImpl->mBuffer[(count - 1) * ATTRSIZE]) {
+ --count;
+ }
+
+ return count;
+}
+
+uint32_t
+nsAttrAndChildArray::MappedAttrCount() const
+{
+ return mImpl && mImpl->mMappedAttrs ? (uint32_t)mImpl->mMappedAttrs->Count() : 0;
+}
+
+nsMappedAttributes*
+nsAttrAndChildArray::GetModifiableMapped(nsMappedAttributeElement* aContent,
+ nsHTMLStyleSheet* aSheet,
+ bool aWillAddAttr)
+{
+ if (mImpl && mImpl->mMappedAttrs) {
+ return mImpl->mMappedAttrs->Clone(aWillAddAttr);
+ }
+
+ MOZ_ASSERT(aContent, "Trying to create modifiable without content");
+
+ nsMapRuleToAttributesFunc mapRuleFunc =
+ aContent->GetAttributeMappingFunction();
+ return new nsMappedAttributes(aSheet, mapRuleFunc);
+}
+
+nsresult
+nsAttrAndChildArray::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;
+}
+
+
+bool
+nsAttrAndChildArray::GrowBy(uint32_t aGrowSize)
+{
+ CheckedUint32 size = 0;
+ if (mImpl) {
+ size += mImpl->mBufferSize;
+ size += NS_IMPL_EXTRA_SIZE;
+ if (!size.isValid()) {
+ return false;
+ }
+ }
+
+ CheckedUint32 minSize = size.value();
+ minSize += aGrowSize;
+ if (!minSize.isValid()) {
+ return false;
+ }
+
+ if (minSize.value() <= ATTRCHILD_ARRAY_LINEAR_THRESHOLD) {
+ do {
+ size += ATTRCHILD_ARRAY_GROWSIZE;
+ if (!size.isValid()) {
+ return false;
+ }
+ } while (size.value() < minSize.value());
+ }
+ else {
+ uint32_t shift = mozilla::CeilingLog2(minSize.value());
+ if (shift >= 32) {
+ return false;
+ }
+
+ size = 1u << shift;
+ }
+
+ bool needToInitialize = !mImpl;
+ CheckedUint32 neededSize = size;
+ neededSize *= sizeof(void*);
+ if (!neededSize.isValid()) {
+ return false;
+ }
+
+ Impl* newImpl = static_cast<Impl*>(realloc(mImpl, neededSize.value()));
+ NS_ENSURE_TRUE(newImpl, false);
+
+ mImpl = newImpl;
+
+ // Set initial counts if we didn't have a buffer before
+ if (needToInitialize) {
+ mImpl->mMappedAttrs = nullptr;
+ SetAttrSlotAndChildCount(0, 0);
+ }
+
+ mImpl->mBufferSize = size.value() - NS_IMPL_EXTRA_SIZE;
+
+ return true;
+}
+
+bool
+nsAttrAndChildArray::AddAttrSlot()
+{
+ uint32_t slotCount = AttrSlotCount();
+ uint32_t childCount = ChildCount();
+
+ CheckedUint32 size = slotCount;
+ size += 1;
+ size *= ATTRSIZE;
+ size += childCount;
+ if (!size.isValid()) {
+ return false;
+ }
+
+ // Grow buffer if needed
+ if (!(mImpl && mImpl->mBufferSize >= size.value()) &&
+ !GrowBy(ATTRSIZE)) {
+ return false;
+ }
+
+ void** offset = mImpl->mBuffer + slotCount * ATTRSIZE;
+
+ if (childCount > 0) {
+ memmove(&ATTRS(mImpl)[slotCount + 1], &ATTRS(mImpl)[slotCount],
+ childCount * sizeof(nsIContent*));
+ }
+
+ SetAttrSlotCount(slotCount + 1);
+ offset[0] = nullptr;
+ offset[1] = nullptr;
+
+ return true;
+}
+
+inline void
+nsAttrAndChildArray::SetChildAtPos(void** aPos, nsIContent* aChild,
+ uint32_t aIndex, uint32_t aChildCount)
+{
+ NS_PRECONDITION(!aChild->GetNextSibling(), "aChild with next sibling?");
+ NS_PRECONDITION(!aChild->GetPreviousSibling(), "aChild with prev sibling?");
+
+ *aPos = aChild;
+ NS_ADDREF(aChild);
+ if (aIndex != 0) {
+ nsIContent* previous = static_cast<nsIContent*>(*(aPos - 1));
+ aChild->mPreviousSibling = previous;
+ previous->mNextSibling = aChild;
+ }
+ if (aIndex != aChildCount) {
+ nsIContent* next = static_cast<nsIContent*>(*(aPos + 1));
+ aChild->mNextSibling = next;
+ next->mPreviousSibling = aChild;
+ }
+}
+
+size_t
+nsAttrAndChildArray::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);
+
+ uint32_t slotCount = AttrSlotCount();
+ for (uint32_t i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
+ nsAttrValue* value = &ATTRS(mImpl)[i].mValue;
+ n += value->SizeOfExcludingThis(aMallocSizeOf);
+ }
+ }
+
+ return n;
+}
+
diff --git a/dom/base/nsAttrAndChildArray.h b/dom/base/nsAttrAndChildArray.h
new file mode 100644
index 000000000..f34370c43
--- /dev/null
+++ b/dom/base/nsAttrAndChildArray.h
@@ -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/. */
+
+/*
+ * Storage of the children and attributes of a DOM node; storage for
+ * the two is unified to minimize footprint.
+ */
+
+#ifndef nsAttrAndChildArray_h___
+#define nsAttrAndChildArray_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.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;
+
+#define ATTRCHILD_ARRAY_GROWSIZE 8
+#define ATTRCHILD_ARRAY_LINEAR_THRESHOLD 32
+
+#define ATTRCHILD_ARRAY_ATTR_SLOTS_BITS 10
+
+#define ATTRCHILD_ARRAY_MAX_ATTR_COUNT \
+ ((1 << ATTRCHILD_ARRAY_ATTR_SLOTS_BITS) - 1)
+
+#define ATTRCHILD_ARRAY_MAX_CHILD_COUNT \
+ (~uint32_t(0) >> ATTRCHILD_ARRAY_ATTR_SLOTS_BITS)
+
+#define ATTRCHILD_ARRAY_ATTR_SLOTS_COUNT_MASK \
+ ((1 << ATTRCHILD_ARRAY_ATTR_SLOTS_BITS) - 1)
+
+
+#define ATTRSIZE (sizeof(InternalAttr) / sizeof(void*))
+
+class nsAttrAndChildArray
+{
+ typedef mozilla::dom::BorrowedAttrInfo BorrowedAttrInfo;
+public:
+ nsAttrAndChildArray();
+ ~nsAttrAndChildArray();
+
+ uint32_t ChildCount() const
+ {
+ return mImpl ? (mImpl->mAttrAndChildCount >> ATTRCHILD_ARRAY_ATTR_SLOTS_BITS) : 0;
+ }
+ nsIContent* ChildAt(uint32_t aPos) const
+ {
+ NS_ASSERTION(aPos < ChildCount(), "out-of-bounds access in nsAttrAndChildArray");
+ return reinterpret_cast<nsIContent*>(mImpl->mBuffer[AttrSlotsSize() + aPos]);
+ }
+ nsIContent* GetSafeChildAt(uint32_t aPos) const;
+ nsIContent * const * GetChildArray(uint32_t* aChildCount) const;
+ nsresult AppendChild(nsIContent* aChild)
+ {
+ return InsertChildAt(aChild, ChildCount());
+ }
+ nsresult InsertChildAt(nsIContent* aChild, uint32_t aPos);
+ void RemoveChildAt(uint32_t aPos);
+ // Like RemoveChildAt but hands the reference to the child being
+ // removed back to the caller instead of just releasing it.
+ already_AddRefed<nsIContent> TakeChildAt(uint32_t aPos);
+ int32_t IndexOfChild(const nsINode* aPossibleChild) const;
+
+ bool HasAttrs() const
+ {
+ return MappedAttrCount() || (AttrSlotCount() && AttrSlotIsTaken(0));
+ }
+
+ uint32_t AttrCount() const;
+ const nsAttrValue* GetAttr(nsIAtom* 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.
+ nsresult SetAndSwapAttr(nsIAtom* aLocalName, nsAttrValue& aValue);
+ nsresult SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue);
+
+ // 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(nsIAtom* aLocalName, int32_t aNamespaceID = kNameSpaceID_None) const;
+
+ nsresult SetAndTakeMappedAttr(nsIAtom* aLocalName, nsAttrValue& aValue,
+ nsMappedAttributeElement* aContent,
+ nsHTMLStyleSheet* aSheet);
+ nsresult SetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet) {
+ if (!mImpl || !mImpl->mMappedAttrs) {
+ return NS_OK;
+ }
+ return DoSetMappedAttrStyleSheet(aSheet);
+ }
+ void WalkMappedAttributeStyleRules(nsRuleWalker* aRuleWalker);
+
+ void Compact();
+
+ bool CanFitMoreAttrs() const
+ {
+ return AttrSlotCount() < ATTRCHILD_ARRAY_MAX_ATTR_COUNT ||
+ !AttrSlotIsTaken(ATTRCHILD_ARRAY_MAX_ATTR_COUNT - 1);
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ bool HasMappedAttrs() const
+ {
+ return MappedAttrCount();
+ }
+
+private:
+ nsAttrAndChildArray(const nsAttrAndChildArray& aOther) = delete;
+ nsAttrAndChildArray& operator=(const nsAttrAndChildArray& aOther) = delete;
+
+ void Clear();
+
+ uint32_t NonMappedAttrCount() const;
+ uint32_t MappedAttrCount() const;
+
+ // Returns a non-null zero-refcount object.
+ nsMappedAttributes*
+ GetModifiableMapped(nsMappedAttributeElement* aContent,
+ nsHTMLStyleSheet* aSheet,
+ bool aWillAddAttr);
+ nsresult MakeMappedUnique(nsMappedAttributes* aAttributes);
+
+ uint32_t AttrSlotsSize() const
+ {
+ return AttrSlotCount() * ATTRSIZE;
+ }
+
+ uint32_t AttrSlotCount() const
+ {
+ return mImpl ? mImpl->mAttrAndChildCount & ATTRCHILD_ARRAY_ATTR_SLOTS_COUNT_MASK : 0;
+ }
+
+ bool AttrSlotIsTaken(uint32_t aSlot) const
+ {
+ NS_PRECONDITION(aSlot < AttrSlotCount(), "out-of-bounds");
+ return mImpl->mBuffer[aSlot * ATTRSIZE];
+ }
+
+ void SetChildCount(uint32_t aCount)
+ {
+ mImpl->mAttrAndChildCount =
+ (mImpl->mAttrAndChildCount & ATTRCHILD_ARRAY_ATTR_SLOTS_COUNT_MASK) |
+ (aCount << ATTRCHILD_ARRAY_ATTR_SLOTS_BITS);
+ }
+
+ void SetAttrSlotCount(uint32_t aCount)
+ {
+ mImpl->mAttrAndChildCount =
+ (mImpl->mAttrAndChildCount & ~ATTRCHILD_ARRAY_ATTR_SLOTS_COUNT_MASK) |
+ aCount;
+ }
+
+ void SetAttrSlotAndChildCount(uint32_t aSlotCount, uint32_t aChildCount)
+ {
+ mImpl->mAttrAndChildCount = aSlotCount |
+ (aChildCount << ATTRCHILD_ARRAY_ATTR_SLOTS_BITS);
+ }
+
+ bool GrowBy(uint32_t aGrowSize);
+ bool AddAttrSlot();
+
+ /**
+ * Set *aPos to aChild and update sibling pointers as needed. aIndex is the
+ * index at which aChild is actually being inserted. aChildCount is the
+ * number of kids we had before the insertion.
+ */
+ inline void SetChildAtPos(void** aPos, nsIContent* aChild, uint32_t aIndex,
+ uint32_t aChildCount);
+
+ /**
+ * Guts of SetMappedAttrStyleSheet for the rare case when we have mapped attrs
+ */
+ nsresult DoSetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet);
+
+ struct InternalAttr
+ {
+ nsAttrName mName;
+ nsAttrValue mValue;
+ };
+
+ struct Impl {
+ uint32_t mAttrAndChildCount;
+ uint32_t mBufferSize;
+ nsMappedAttributes* mMappedAttrs;
+ void* mBuffer[1];
+ };
+
+ Impl* mImpl;
+};
+
+#endif
diff --git a/dom/base/nsAttrName.h b/dom/base/nsAttrName.h
new file mode 100644
index 000000000..828d2689d
--- /dev/null
+++ b/dom/base/nsAttrName.h
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIAtom.h"
+#include "nsDOMString.h"
+
+#define NS_ATTRNAME_NODEINFO_BIT 1
+class nsAttrName
+{
+public:
+ nsAttrName(const nsAttrName& aOther)
+ : mBits(aOther.mBits)
+ {
+ AddRefInternalName();
+ }
+
+ explicit nsAttrName(nsIAtom* 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(nsIAtom* 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);
+ }
+
+ nsIAtom* Atom() const
+ {
+ NS_ASSERTION(IsAtom(), "getting atom-value of nodeinfo-name");
+ return reinterpret_cast<nsIAtom*>(mBits);
+ }
+
+ bool Equals(const nsAttrName& aOther) const
+ {
+ return mBits == aOther.mBits;
+ }
+
+ // Faster comparison in the case we know the namespace is null
+ bool Equals(nsIAtom* 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(nsIAtom* 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));
+ }
+
+ nsIAtom* LocalName() const
+ {
+ return IsAtom() ? Atom() : NodeInfo()->NameAtom();
+ }
+
+ nsIAtom* 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(nsIAtom* 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 000000000..8eb1aaf97
--- /dev/null
+++ b/dom/base/nsAttrValue.cpp
@@ -0,0 +1,1974 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsIAtom.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ServoBindingTypes.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "prprf.h"
+#include "nsHTMLCSSStyleSheet.h"
+#include "nsCSSParser.h"
+#include "nsStyledElement.h"
+#include "nsIURI.h"
+#include "nsIDocument.h"
+#include <algorithm>
+
+#ifdef LoadImage
+// Undefine LoadImage to prevent naming conflict with Windows.
+#undef LoadImage
+#endif
+
+using namespace mozilla;
+
+#define MISC_STR_PTR(_cont) \
+ reinterpret_cast<void*>((_cont)->mStringBits & NS_ATTRVALUE_POINTERVALUE_MASK)
+
+bool
+MiscContainer::GetString(nsAString& aString) const
+{
+ void* ptr = MISC_STR_PTR(this);
+
+ if (!ptr) {
+ return false;
+ }
+
+ if (static_cast<nsAttrValue::ValueBaseType>(mStringBits &
+ NS_ATTRVALUE_BASETYPE_MASK) ==
+ nsAttrValue::eStringBase) {
+ nsStringBuffer* buffer = static_cast<nsStringBuffer*>(ptr);
+ if (!buffer) {
+ return false;
+ }
+
+ buffer->ToString(buffer->StorageSize() / sizeof(char16_t) - 1, aString);
+ return true;
+ }
+
+ nsIAtom* atom = static_cast<nsIAtom*>(ptr);
+ if (!atom) {
+ return false;
+ }
+
+ atom->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(nsIAtom* aValue)
+ : mBits(0)
+{
+ SetTo(aValue);
+}
+
+nsAttrValue::nsAttrValue(already_AddRefed<DeclarationBlock> aValue,
+ const nsAString* aSerialized)
+ : mBits(0)
+{
+ SetTo(Move(aValue), aSerialized);
+}
+
+nsAttrValue::nsAttrValue(const nsIntMargin& aValue)
+ : mBits(0)
+{
+ SetTo(aValue);
+}
+
+nsAttrValue::~nsAttrValue()
+{
+ ResetIfSet();
+}
+
+/* static */
+nsresult
+nsAttrValue::Init()
+{
+ NS_ASSERTION(!sEnumTableArray, "nsAttrValue already initialized");
+ sEnumTableArray = new nsTArray<const EnumTable*>;
+ return NS_OK;
+}
+
+/* static */
+void
+nsAttrValue::Shutdown()
+{
+ delete sEnumTableArray;
+ sEnumTableArray = nullptr;
+}
+
+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()));
+ }
+ }
+}
+
+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;
+ }
+
+ delete ClearMiscContainer();
+
+ break;
+ }
+ case eAtomBase:
+ {
+ nsIAtom* 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();
+ nsIAtom* atom = aOther.GetAtomValue();
+ NS_ADDREF(atom);
+ SetPtrValueAndType(atom, eAtomBase);
+ return;
+ }
+ case eIntegerBase:
+ {
+ ResetIfSet();
+ mBits = aOther.mBits;
+ return;
+ }
+ }
+
+ MiscContainer* otherCont = aOther.GetMiscContainer();
+ if (otherCont->IsRefCounted()) {
+ delete 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->mValue.mPercent = otherCont->mValue.mPercent;
+ break;
+ }
+ case eColor:
+ {
+ cont->mValue.mColor = otherCont->mValue.mColor;
+ break;
+ }
+ case eCSSDeclaration:
+ {
+ MOZ_CRASH("These should be refcounted!");
+ }
+ case eURL:
+ {
+ NS_ADDREF(cont->mValue.mURL = otherCont->mValue.mURL);
+ break;
+ }
+ case eImage:
+ {
+ NS_ADDREF(cont->mValue.mImage = otherCont->mValue.mImage);
+ break;
+ }
+ case eAtomArray:
+ {
+ if (!EnsureEmptyAtomArray() ||
+ !GetAtomArrayValue()->AppendElements(*otherCont->mValue.mAtomArray)) {
+ Reset();
+ return;
+ }
+ break;
+ }
+ case eDoubleValue:
+ {
+ cont->mDoubleValue = otherCont->mDoubleValue;
+ break;
+ }
+ case eIntMarginValue:
+ {
+ if (otherCont->mValue.mIntMargin)
+ cont->mValue.mIntMargin =
+ new nsIntMargin(*otherCont->mValue.mIntMargin);
+ 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.mSVGAngle = otherCont->mValue.mSVGAngle;
+ } else {
+ NS_NOTREACHED("unknown type stored in MiscContainer");
+ }
+ break;
+ }
+ }
+
+ void* otherPtr = MISC_STR_PTR(otherCont);
+ if (otherPtr) {
+ if (static_cast<ValueBaseType>(otherCont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) ==
+ eStringBase) {
+ static_cast<nsStringBuffer*>(otherPtr)->AddRef();
+ } else {
+ static_cast<nsIAtom*>(otherPtr)->AddRef();
+ }
+ cont->mStringBits = 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(nsIAtom* 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(css::URLValue* aValue, const nsAString* aSerialized)
+{
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ NS_ADDREF(cont->mValue.mURL = aValue);
+ cont->mType = eURL;
+ SetMiscAtomOrString(aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const nsIntMargin& aValue)
+{
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ cont->mValue.mIntMargin = new nsIntMargin(aValue);
+ cont->mType = eIntMarginValue;
+}
+
+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 nsSVGAngle& aValue, const nsAString* aSerialized)
+{
+ SetSVGType(eSVGAngle, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const nsSVGIntegerPair& aValue, const nsAString* aSerialized)
+{
+ SetSVGType(eSVGIntegerPair, &aValue, aSerialized);
+}
+
+void
+nsAttrValue::SetTo(const nsSVGLength2& 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 nsSVGNumberPair& 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 nsSVGViewBox& 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:
+ {
+ nsIAtom *atom = static_cast<nsIAtom*>(GetPtr());
+ atom->ToString(aResult);
+
+ break;
+ }
+ case eInteger:
+ {
+ nsAutoString intStr;
+ intStr.AppendInt(GetIntegerValue());
+ aResult = intStr;
+
+ break;
+ }
+#ifdef DEBUG
+ case eColor:
+ {
+ NS_NOTREACHED("color attribute without string data");
+ aResult.Truncate();
+ break;
+ }
+#endif
+ case eEnum:
+ {
+ GetEnumString(aResult, false);
+ break;
+ }
+ case ePercent:
+ {
+ nsAutoString intStr;
+ intStr.AppendInt(cont ? cont->mValue.mPercent : GetIntInternal());
+ aResult = intStr + NS_LITERAL_STRING("%");
+
+ break;
+ }
+ case eCSSDeclaration:
+ {
+ aResult.Truncate();
+ MiscContainer *container = GetMiscContainer();
+ if (DeclarationBlock* decl = container->mValue.mCSSDeclaration) {
+ decl->ToString(aResult);
+ }
+ const_cast<nsAttrValue*>(this)->SetMiscAtomOrString(&aResult);
+
+ break;
+ }
+ case eDoubleValue:
+ {
+ aResult.Truncate();
+ aResult.AppendFloat(GetDoubleValue());
+ break;
+ }
+ case eSVGAngle:
+ {
+ SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGAngle,
+ aResult);
+ break;
+ }
+ case eSVGIntegerPair:
+ {
+ SVGAttrValueWrapper::ToString(GetMiscContainer()->mValue.mSVGIntegerPair,
+ 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.mSVGNumberPair,
+ 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.mSVGPreserveAspectRatio,
+ 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.mSVGViewBox,
+ aResult);
+ break;
+ }
+ default:
+ {
+ aResult.Truncate();
+ break;
+ }
+ }
+}
+
+already_AddRefed<nsIAtom>
+nsAttrValue::GetAsAtom() const
+{
+ switch (Type()) {
+ case eString:
+ return NS_Atomize(GetStringValue());
+
+ case eAtom:
+ {
+ nsCOMPtr<nsIAtom> atom = GetAtomValue();
+ return atom.forget();
+ }
+
+ default:
+ {
+ nsAutoString val;
+ ToString(val);
+ return NS_Atomize(val);
+ }
+ }
+}
+
+const nsCheapString
+nsAttrValue::GetStringValue() const
+{
+ NS_PRECONDITION(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
+{
+ NS_PRECONDITION(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++;
+ }
+
+ NS_NOTREACHED("couldn't find value in EnumTable");
+}
+
+uint32_t
+nsAttrValue::GetAtomCount() const
+{
+ ValueType type = Type();
+
+ if (type == eAtom) {
+ return 1;
+ }
+
+ if (type == eAtomArray) {
+ return GetAtomArrayValue()->Length();
+ }
+
+ return 0;
+}
+
+nsIAtom*
+nsAttrValue::AtomAt(int32_t aIndex) const
+{
+ NS_PRECONDITION(aIndex >= 0, "Index must not be negative");
+ NS_PRECONDITION(GetAtomCount() > uint32_t(aIndex), "aIndex out of range");
+
+ if (BaseType() == eAtomBase) {
+ return GetAtomValue();
+ }
+
+ NS_ASSERTION(Type() == eAtomArray, "GetAtomCount must be confused");
+
+ return GetAtomArrayValue()->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->mValue.mPercent;
+ }
+ case eColor:
+ {
+ return cont->mValue.mColor;
+ }
+ case eCSSDeclaration:
+ {
+ return NS_PTR_TO_INT32(cont->mValue.mCSSDeclaration);
+ }
+ // Intentionally identical, so that loading the image does not change the
+ // hash code.
+ case eURL:
+ case eImage:
+ {
+ nsString str;
+ ToString(str);
+ return HashString(str);
+ }
+ case eAtomArray:
+ {
+ uint32_t hash = 0;
+ uint32_t count = cont->mValue.mAtomArray->Length();
+ for (nsCOMPtr<nsIAtom> *cur = cont->mValue.mAtomArray->Elements(),
+ *end = cur + count;
+ cur != end; ++cur) {
+ hash = AddToHash(hash, cur->get());
+ }
+ return hash;
+ }
+ case eDoubleValue:
+ {
+ // XXX this is crappy, but oh well
+ return cont->mDoubleValue;
+ }
+ case eIntMarginValue:
+ {
+ return NS_PTR_TO_INT32(cont->mValue.mIntMargin);
+ }
+ 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.mSVGAngle);
+ }
+ NS_NOTREACHED("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->mValue.mPercent == otherCont->mValue.mPercent) {
+ 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 eImage:
+ {
+ return thisCont->mValue.mImage == otherCont->mValue.mImage;
+ }
+ 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;
+ }
+ case eIntMarginValue:
+ {
+ return thisCont->mValue.mIntMargin == otherCont->mValue.mIntMargin;
+ }
+ 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;
+ }
+ NS_NOTREACHED("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*>(thisCont->mStringBits)).Equals(
+ nsCheapString(reinterpret_cast<nsStringBuffer*>(otherCont->mStringBits)));
+ }
+ }
+ return false;
+}
+
+bool
+nsAttrValue::Equals(const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const
+{
+ switch (BaseType()) {
+ case eStringBase:
+ {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
+ if (str) {
+ 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:
+ if (aCaseSensitive == eCaseMatters) {
+ return static_cast<nsIAtom*>(GetPtr())->Equals(aValue);
+ }
+ return nsContentUtils::EqualsIgnoreASCIICase(
+ nsDependentAtomString(static_cast<nsIAtom*>(GetPtr())),
+ aValue);
+ default:
+ break;
+ }
+
+ nsAutoString val;
+ ToString(val);
+ return aCaseSensitive == eCaseMatters ? val.Equals(aValue) :
+ nsContentUtils::EqualsIgnoreASCIICase(val, aValue);
+}
+
+bool
+nsAttrValue::Equals(nsIAtom* aValue, nsCaseTreatment aCaseSensitive) const
+{
+ if (aCaseSensitive != eCaseMatters) {
+ // Need a better way to handle this!
+ nsAutoString value;
+ aValue->ToString(value);
+ return Equals(value, aCaseSensitive);
+ }
+
+ switch (BaseType()) {
+ case eStringBase:
+ {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
+ if (str) {
+ nsDependentString dep(static_cast<char16_t*>(str->Data()),
+ str->StorageSize()/sizeof(char16_t) - 1);
+ return aValue->Equals(dep);
+ }
+ return aValue == nsGkAtoms::_empty;
+ }
+ case eAtomBase:
+ {
+ return static_cast<nsIAtom*>(GetPtr()) == aValue;
+ }
+ default:
+ break;
+ }
+
+ nsAutoString val;
+ ToString(val);
+ return aValue->Equals(val);
+}
+
+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(nsIAtom* aValue, nsCaseTreatment aCaseSensitive) const
+{
+ switch (BaseType()) {
+ case eAtomBase:
+ {
+ nsIAtom* 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(nsDependentAtomString(aValue),
+ nsDependentAtomString(atom));
+ }
+ default:
+ {
+ if (Type() == eAtomArray) {
+ AtomArray* array = GetAtomArrayValue();
+ if (aCaseSensitive == eCaseMatters) {
+ return array->Contains(aValue);
+ }
+
+ nsDependentAtomString val1(aValue);
+
+ for (nsCOMPtr<nsIAtom> *cur = array->Elements(),
+ *end = cur + array->Length();
+ cur != end; ++cur) {
+ // 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(val1,
+ nsDependentAtomString(*cur))) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+struct AtomArrayStringComparator {
+ bool Equals(nsIAtom* atom, const nsAString& string) const {
+ return atom->Equals(string);
+ }
+};
+
+bool
+nsAttrValue::Contains(const nsAString& aValue) const
+{
+ switch (BaseType()) {
+ case eAtomBase:
+ {
+ nsIAtom* atom = GetAtomValue();
+ return atom->Equals(aValue);
+ }
+ default:
+ {
+ if (Type() == eAtomArray) {
+ AtomArray* array = GetAtomArrayValue();
+ return array->Contains(aValue, AtomArrayStringComparator());
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+nsAttrValue::ParseAtom(const nsAString& aValue)
+{
+ ResetIfSet();
+
+ nsCOMPtr<nsIAtom> 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));
+
+ nsCOMPtr<nsIAtom> classAtom = NS_Atomize(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();
+ nsIAtom* atom = nullptr;
+ classAtom.swap(atom);
+ SetPtrValueAndType(atom, eAtomBase);
+ return;
+ }
+
+ if (!EnsureEmptyAtomArray()) {
+ return;
+ }
+
+ AtomArray* array = GetAtomArrayValue();
+
+ if (!array->AppendElement(classAtom)) {
+ Reset();
+ return;
+ }
+
+ // parse the rest of the classnames
+ while (iter != end) {
+ start = iter;
+
+ do {
+ ++iter;
+ } while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
+
+ classAtom = NS_Atomize(Substring(start, iter));
+
+ if (!array->AppendElement(classAtom)) {
+ Reset();
+ return;
+ }
+
+ // skip whitespace
+ while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ ++iter;
+ }
+ }
+
+ SetMiscAtomOrString(&aValue);
+ return;
+}
+
+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 efficently anyway.
+ if (len && len <= NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM) {
+ ParseAtom(aValue);
+ }
+ else {
+ SetTo(aValue);
+ }
+}
+
+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->mValue.mPercent = aValue;
+ break;
+ }
+ case eEnum:
+ {
+ cont->mValue.mEnumValue = aValue;
+ break;
+ }
+ default:
+ {
+ NS_NOTREACHED("unknown integer type");
+ break;
+ }
+ }
+ cont->mType = aType;
+ SetMiscAtomOrString(aStringValue);
+ } else {
+ NS_ASSERTION(!mBits, "Reset before calling SetIntValueAndType!");
+ mBits = (aValue * NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER) | aType;
+ }
+}
+
+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) {
+ NS_PRECONDITION(aTable <= aDefaultValue && aDefaultValue < tableEntry,
+ "aDefaultValue not inside aTable?");
+ SetIntValueAndType(EnumTableEntryToValue(aTable, aDefaultValue),
+ eEnum, &aValue);
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsAttrValue::ParseSpecialIntValue(const nsAString& aString)
+{
+ ResetIfSet();
+
+ nsAutoString tmp(aString);
+ nsContentUtils::ParseHTMLIntegerResultFlags result;
+ int32_t originalVal = nsContentUtils::ParseHTMLInteger(aString, &result);
+
+ if (result & nsContentUtils::eParseHTMLInteger_Error) {
+ return false;
+ }
+
+ bool isPercent = result & nsContentUtils::eParseHTMLInteger_IsPercent;
+ int32_t val = std::max(originalVal, 0);
+ bool nonStrict = val != originalVal ||
+ (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
+ (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput);
+
+ // % (percent)
+ if (isPercent || tmp.RFindChar('%') >= 0) {
+ isPercent = true;
+ }
+
+ SetIntValueAndType(val, isPercent ? ePercent : eInteger,
+ nonStrict ? &aString : nullptr);
+ return true;
+}
+
+bool
+nsAttrValue::ParseIntWithBounds(const nsAString& aString,
+ int32_t aMin, int32_t aMax)
+{
+ NS_PRECONDITION(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_IsPercent) ||
+ (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_IsPercent) ||
+ (result & nsContentUtils::eParseHTMLInteger_NonStandard) ||
+ (result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)) {
+ 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_IsPercent) ||
+ (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_IsPercent) ||
+ (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->mStringBits = 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::ParseIntMarginValue(const nsAString& aString)
+{
+ ResetIfSet();
+
+ nsIntMargin margins;
+ if (!nsContentUtils::ParseIntMarginValue(aString, margins))
+ return false;
+
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ cont->mValue.mIntMargin = new nsIntMargin(margins);
+ cont->mType = eIntMarginValue;
+ SetMiscAtomOrString(&aString);
+ return true;
+}
+
+void
+nsAttrValue::LoadImage(nsIDocument* aDocument)
+{
+ NS_ASSERTION(Type() == eURL, "wrong type");
+
+#ifdef DEBUG
+ {
+ nsString val;
+ ToString(val);
+ NS_ASSERTION(!val.IsEmpty(),
+ "How did we end up with an empty string for eURL");
+ }
+#endif
+
+ MiscContainer* cont = GetMiscContainer();
+ mozilla::css::URLValue* url = cont->mValue.mURL;
+ mozilla::css::ImageValue* image =
+ new css::ImageValue(url->GetURI(), url->mString, url->mBaseURI,
+ url->mReferrer, url->mOriginPrincipal, aDocument);
+
+ NS_ADDREF(image);
+ cont->mValue.mImage = image;
+ NS_RELEASE(url);
+ cont->mType = eImage;
+}
+
+bool
+nsAttrValue::ParseStyleAttribute(const nsAString& aString,
+ nsStyledElement* aElement)
+{
+ nsIDocument* ownerDoc = aElement->OwnerDoc();
+ nsHTMLCSSStyleSheet* sheet = ownerDoc->GetInlineStyleSheet();
+ nsCOMPtr<nsIURI> baseURI = aElement->GetBaseURI();
+ nsIURI* docURI = ownerDoc->GetDocumentURI();
+
+ NS_ASSERTION(aElement->NodePrincipal() == ownerDoc->NodePrincipal(),
+ "This is unexpected");
+
+ // 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.
+ bool cachingAllowed = sheet && baseURI == docURI;
+ 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;
+ if (ownerDoc->GetStyleBackendType() == StyleBackendType::Servo) {
+ decl = ServoDeclarationBlock::FromCssText(aString);
+ } else {
+ css::Loader* cssLoader = ownerDoc->CSSLoader();
+ nsCSSParser cssParser(cssLoader);
+ decl = cssParser.ParseStyleAttribute(aString, docURI, baseURI,
+ aElement->NodePrincipal());
+ }
+ 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,
+ "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) {
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(*aValue);
+ if (atom) {
+ cont->mStringBits =
+ reinterpret_cast<uintptr_t>(atom.forget().take()) | eAtomBase;
+ }
+ } else {
+ nsStringBuffer* buf = GetStringBuffer(*aValue).take();
+ if (buf) {
+ cont->mStringBits = reinterpret_cast<uintptr_t>(buf) | eStringBase;
+ }
+ }
+ }
+}
+
+void
+nsAttrValue::ResetMiscAtomOrString()
+{
+ MiscContainer* cont = GetMiscContainer();
+ void* ptr = MISC_STR_PTR(cont);
+ if (ptr) {
+ if (static_cast<ValueBaseType>(cont->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) ==
+ eStringBase) {
+ static_cast<nsStringBuffer*>(ptr)->Release();
+ } else {
+ static_cast<nsIAtom*>(ptr)->Release();
+ }
+ cont->mStringBits = 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.mSVGAngle = static_cast<const nsSVGAngle*>(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 = new MiscContainer;
+ 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 eURL:
+ {
+ NS_RELEASE(cont->mValue.mURL);
+ break;
+ }
+ case eImage:
+ {
+ NS_RELEASE(cont->mValue.mImage);
+ break;
+ }
+ case eAtomArray:
+ {
+ delete cont->mValue.mAtomArray;
+ break;
+ }
+ case eIntMarginValue:
+ {
+ delete cont->mValue.mIntMargin;
+ 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 = new MiscContainer;
+ SetPtrValueAndType(cont, eOtherBase);
+ }
+
+ return cont;
+}
+
+bool
+nsAttrValue::EnsureEmptyAtomArray()
+{
+ if (Type() == eAtomArray) {
+ ResetMiscAtomOrString();
+ GetAtomArrayValue()->Clear();
+ return true;
+ }
+
+ MiscContainer* cont = EnsureEmptyMiscContainer();
+ cont->mValue.mAtomArray = new AtomArray;
+ 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);
+
+ void* otherPtr = MISC_STR_PTR(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 (otherPtr &&
+ static_cast<ValueBaseType>(container->mStringBits & NS_ATTRVALUE_BASETYPE_MASK) == eStringBase) {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(otherPtr);
+ n += str ? str->SizeOfIncludingThisIfUnshared(aMallocSizeOf) : 0;
+ }
+
+ 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 ServoDeclarationBlock 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 nsIAtom, they are measured separatly.
+ n += container->mValue.mAtomArray->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 000000000..655e4ca61
--- /dev/null
+++ b/dom/base/nsAttrValue.h
@@ -0,0 +1,544 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsStringGlue.h"
+#include "nsStringBuffer.h"
+#include "nsColor.h"
+#include "nsCaseTreatment.h"
+#include "nsMargin.h"
+#include "nsCOMPtr.h"
+#include "SVGAttrValueWrapper.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsIAtom.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/EnumTypeTraits.h"
+
+// Undefine LoadImage to prevent naming conflict with Windows.
+#undef LoadImage
+
+class nsAString;
+class nsIDocument;
+class nsStyledElement;
+struct MiscContainer;
+
+namespace mozilla {
+class DeclarationBlock;
+namespace css {
+struct URLValue;
+struct ImageValue;
+} // namespace css
+} // namespace mozilla
+
+#define NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM 12
+
+#define NS_ATTRVALUE_BASETYPE_MASK (uintptr_t(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:
+ typedef nsTArray< nsCOMPtr<nsIAtom> > AtomArray;
+
+ // 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,
+ eIntMarginValue,
+ eSVGAngle,
+ eSVGTypesBegin = eSVGAngle,
+ eSVGIntegerPair,
+ eSVGLength,
+ eSVGLengthList,
+ eSVGNumberList,
+ eSVGNumberPair,
+ eSVGPathData,
+ eSVGPointList,
+ eSVGPreserveAspectRatio,
+ eSVGStringList,
+ eSVGTransformList,
+ eSVGViewBox,
+ eSVGTypesEnd = eSVGViewBox,
+ };
+
+ nsAttrValue();
+ nsAttrValue(const nsAttrValue& aOther);
+ explicit nsAttrValue(const nsAString& aValue);
+ explicit nsAttrValue(nsIAtom* aValue);
+ nsAttrValue(already_AddRefed<mozilla::DeclarationBlock> aValue,
+ const nsAString* aSerialized);
+ explicit nsAttrValue(const nsIntMargin& aValue);
+ ~nsAttrValue();
+
+ inline const nsAttrValue& operator=(const nsAttrValue& aOther);
+
+ static nsresult Init();
+ static void Shutdown();
+
+ 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(nsIAtom* 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(mozilla::css::URLValue* aValue, const nsAString* aSerialized);
+ void SetTo(const nsIntMargin& aValue);
+ void SetTo(const nsSVGAngle& aValue, const nsAString* aSerialized);
+ void SetTo(const nsSVGIntegerPair& aValue, const nsAString* aSerialized);
+ void SetTo(const nsSVGLength2& 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 nsSVGNumberPair& 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::SVGAnimatedPreserveAspectRatio& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGStringList& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const mozilla::SVGTransformList& aValue,
+ const nsAString* aSerialized);
+ void SetTo(const nsSVGViewBox& 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<nsIAtom> 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 nsIAtom* GetAtomValue() const;
+ inline int32_t GetIntegerValue() const;
+ bool GetColorValue(nscolor& aColor) const;
+ inline int16_t GetEnumValue() const;
+ inline float GetPercentValue() const;
+ inline AtomArray* GetAtomArrayValue() const;
+ inline mozilla::DeclarationBlock* GetCSSDeclarationValue() const;
+ inline mozilla::css::URLValue* GetURLValue() const;
+ inline mozilla::css::ImageValue* GetImageValue() const;
+ inline double GetDoubleValue() const;
+ bool GetIntMarginValue(nsIntMargin& aMargin) 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().
+ nsIAtom* 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(nsIAtom* 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(nsIAtom* 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);
+
+ /**
+ * 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");
+ }
+
+ /** 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 an integer. Can optionally parse percent (n%).
+ * This method explicitly sets a lower bound of zero on the element,
+ * whether it be percent or raw integer.
+ *
+ * @param aString the string to parse
+ * @return whether the value could be parsed
+ *
+ * @see http://www.whatwg.org/html/#rules-for-parsing-dimension-values
+ */
+ bool ParseSpecialIntValue(const nsAString& aString);
+
+
+ /**
+ * 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 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 lazy URI. This just sets up the storage for the URI; it
+ * doesn't actually allocate it.
+ */
+ bool ParseLazyURIValue(const nsAString& aString);
+
+ /**
+ * Parse a margin string of format 'top, right, bottom, left' into
+ * an nsIntMargin.
+ *
+ * @param aString the string to parse
+ * @return whether the value could be parsed
+ */
+ bool ParseIntMarginValue(const nsAString& aString);
+
+ /**
+ * Convert a URL nsAttrValue to an Image nsAttrValue.
+ *
+ * @param aDocument the document this nsAttrValue belongs to.
+ */
+ void LoadImage(nsIDocument* aDocument);
+
+ /**
+ * 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.
+ */
+ bool ParseStyleAttribute(const nsAString& aString,
+ nsStyledElement* aElement);
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) 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);
+ 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);
+
+ static nsTArray<const EnumTable*>* sEnumTableArray;
+
+ uintptr_t mBits;
+};
+
+inline const nsAttrValue&
+nsAttrValue::operator=(const nsAttrValue& aOther)
+{
+ SetTo(aOther);
+ return *this;
+}
+
+inline nsIAtom*
+nsAttrValue::GetAtomValue() const
+{
+ NS_PRECONDITION(Type() == eAtom, "wrong type");
+ return reinterpret_cast<nsIAtom*>(GetPtr());
+}
+
+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;
+}
+
+inline void
+nsAttrValue::ToString(mozilla::dom::DOMString& aResult) const
+{
+ switch (Type()) {
+ case eString:
+ {
+ nsStringBuffer* str = static_cast<nsStringBuffer*>(GetPtr());
+ if (str) {
+ aResult.SetStringBuffer(str, str->StorageSize()/sizeof(char16_t) - 1);
+ }
+ // else aResult is already empty
+ return;
+ }
+ case eAtom:
+ {
+ nsIAtom *atom = static_cast<nsIAtom*>(GetPtr());
+ aResult.SetStringBuffer(atom->GetStringBuffer(), atom->GetLength());
+ break;
+ }
+ default:
+ {
+ ToString(aResult.AsAString());
+ }
+ }
+}
+
+#endif
diff --git a/dom/base/nsAttrValueInlines.h b/dom/base/nsAttrValueInlines.h
new file mode 100644
index 000000000..d2749d486
--- /dev/null
+++ b/dom/base/nsAttrValueInlines.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 nsAttrValueInlines_h__
+#define nsAttrValueInlines_h__
+
+#include <stdint.h>
+
+#include "nsAttrValue.h"
+#include "mozilla/Attributes.h"
+
+struct MiscContainer;
+
+struct MiscContainer final
+{
+ typedef nsAttrValue::ValueType ValueType;
+
+ ValueType mType;
+ // mStringBits points to either nsIAtom* or nsStringBuffer* and is used when
+ // mType isn't eCSSDeclaration.
+ // Note eStringBase and eAtomBase is used also to handle the type of
+ // mStringBits.
+ uintptr_t mStringBits;
+ union {
+ struct {
+ union {
+ int32_t mInteger;
+ nscolor mColor;
+ uint32_t mEnumValue;
+ int32_t mPercent;
+ mozilla::DeclarationBlock* mCSSDeclaration;
+ mozilla::css::URLValue* mURL;
+ mozilla::css::ImageValue* mImage;
+ nsAttrValue::AtomArray* mAtomArray;
+ nsIntMargin* mIntMargin;
+ const nsSVGAngle* mSVGAngle;
+ const nsSVGIntegerPair* mSVGIntegerPair;
+ const nsSVGLength2* mSVGLength;
+ const mozilla::SVGLengthList* mSVGLengthList;
+ const mozilla::SVGNumberList* mSVGNumberList;
+ const nsSVGNumberPair* mSVGNumberPair;
+ const mozilla::SVGPathData* mSVGPathData;
+ const mozilla::SVGPointList* mSVGPointList;
+ const mozilla::SVGAnimatedPreserveAspectRatio* mSVGPreserveAspectRatio;
+ const mozilla::SVGStringList* mSVGStringList;
+ const mozilla::SVGTransformList* mSVGTransformList;
+ const nsSVGViewBox* mSVGViewBox;
+ };
+ 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;
+
+ 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;
+ }
+
+ 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
+{
+ NS_PRECONDITION(Type() == eInteger, "wrong type");
+ return (BaseType() == eIntegerBase)
+ ? GetIntInternal()
+ : GetMiscContainer()->mValue.mInteger;
+}
+
+inline int16_t
+nsAttrValue::GetEnumValue() const
+{
+ NS_PRECONDITION(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 float
+nsAttrValue::GetPercentValue() const
+{
+ NS_PRECONDITION(Type() == ePercent, "wrong type");
+ return ((BaseType() == eIntegerBase)
+ ? GetIntInternal()
+ : GetMiscContainer()->mValue.mPercent)
+ / 100.0f;
+}
+
+inline nsAttrValue::AtomArray*
+nsAttrValue::GetAtomArrayValue() const
+{
+ NS_PRECONDITION(Type() == eAtomArray, "wrong type");
+ return GetMiscContainer()->mValue.mAtomArray;
+}
+
+inline mozilla::DeclarationBlock*
+nsAttrValue::GetCSSDeclarationValue() const
+{
+ NS_PRECONDITION(Type() == eCSSDeclaration, "wrong type");
+ return GetMiscContainer()->mValue.mCSSDeclaration;
+}
+
+inline mozilla::css::URLValue*
+nsAttrValue::GetURLValue() const
+{
+ NS_PRECONDITION(Type() == eURL, "wrong type");
+ return GetMiscContainer()->mValue.mURL;
+}
+
+inline mozilla::css::ImageValue*
+nsAttrValue::GetImageValue() const
+{
+ NS_PRECONDITION(Type() == eImage, "wrong type");
+ return GetMiscContainer()->mValue.mImage;
+}
+
+inline double
+nsAttrValue::GetDoubleValue() const
+{
+ NS_PRECONDITION(Type() == eDoubleValue, "wrong type");
+ return GetMiscContainer()->mDoubleValue;
+}
+
+inline bool
+nsAttrValue::GetIntMarginValue(nsIntMargin& aMargin) const
+{
+ NS_PRECONDITION(Type() == eIntMarginValue, "wrong type");
+ nsIntMargin* m = GetMiscContainer()->mValue.mIntMargin;
+ if (!m)
+ return false;
+ aMargin = *m;
+ return true;
+}
+
+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;
+}
+
+#endif
diff --git a/dom/base/nsAttrValueOrString.cpp b/dom/base/nsAttrValueOrString.cpp
new file mode 100644
index 000000000..e2e1fff2b
--- /dev/null
+++ b/dom/base/nsAttrValueOrString.cpp
@@ -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/. */
+
+#include "nsAttrValueOrString.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 000000000..df0dac17d
--- /dev/null
+++ b/dom/base/nsAttrValueOrString.h
@@ -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/. */
+
+/*
+ * 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 TakeParsedValue(nsAttrValue& aValue)
+ {
+ mStoredAttrValue.SwapValueWith(aValue);
+ mAttrValue = &mStoredAttrValue;
+ mStringPtr = nullptr;
+ }
+ /**
+ * If TakeParsedValue has been called, returns the value that it set.
+ */
+ nsAttrValue* GetStoredAttrValue()
+ {
+ return mAttrValue == &mStoredAttrValue ? &mStoredAttrValue : nullptr;
+ }
+ const nsAttrValue* GetAttrValue() { return mAttrValue; }
+
+ /**
+ * 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);
+ }
+
+protected:
+ const nsAttrValue* mAttrValue;
+ mutable const nsAString* mStringPtr;
+ mutable nsCheapString mCheapString;
+ nsAttrValue mStoredAttrValue;
+};
+
+#endif // nsAttrValueOrString_h___
diff --git a/dom/base/nsCCUncollectableMarker.cpp b/dom/base/nsCCUncollectableMarker.cpp
new file mode 100644
index 000000000..861cda521
--- /dev/null
+++ b/dom/base/nsCCUncollectableMarker.cpp
@@ -0,0 +1,550 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDocument.h"
+#include "XULDocument.h"
+#include "nsIWindowMediator.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebNavigation.h"
+#include "nsISHistory.h"
+#include "nsISHEntry.h"
+#include "nsISHContainer.h"
+#include "nsITabChild.h"
+#include "nsIWindowWatcher.h"
+#include "mozilla/Services.h"
+#include "nsIXULWindow.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsJSEnvironment.h"
+#include "nsInProcessTabChildGlobal.h"
+#include "nsFrameLoader.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ProcessGlobal.h"
+#include "xpcpublic.h"
+#include "nsObserverService.h"
+#include "nsFocusManager.h"
+#include "nsIInterfaceRequestorUtils.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;
+#ifdef MOZ_XUL
+#include "nsXULPrototypeCache.h"
+#endif
+
+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
+MarkUserData(void* aNode, nsIAtom* aKey, void* aValue, void* aData)
+{
+ nsIDocument* d = static_cast<nsINode*>(aNode)->GetUncomposedDoc();
+ if (d && nsCCUncollectableMarker::InGeneration(d->GetMarkedCCGeneration())) {
+ Element::MarkUserData(aNode, aKey, aValue, aData);
+ }
+}
+
+static void
+MarkChildMessageManagers(nsIMessageBroadcaster* aMM)
+{
+ aMM->MarkForCC();
+
+ uint32_t tabChildCount = 0;
+ aMM->GetChildCount(&tabChildCount);
+ for (uint32_t j = 0; j < tabChildCount; ++j) {
+ nsCOMPtr<nsIMessageListenerManager> childMM;
+ aMM->GetChildAt(j, getter_AddRefs(childMM));
+ if (!childMM) {
+ continue;
+ }
+
+ nsCOMPtr<nsIMessageBroadcaster> strongNonLeafMM = do_QueryInterface(childMM);
+ nsIMessageBroadcaster* nonLeafMM = strongNonLeafMM;
+
+ nsCOMPtr<nsIMessageSender> strongTabMM = do_QueryInterface(childMM);
+ nsIMessageSender* tabMM = strongTabMM;
+
+ strongNonLeafMM = nullptr;
+ strongTabMM = 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 =
+ static_cast<nsFrameMessageManager*>(tabMM)->GetCallback();
+ if (cb) {
+ nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
+ EventTarget* et = fl->GetTabChildGlobalAsEventTarget();
+ if (!et) {
+ continue;
+ }
+ static_cast<nsInProcessTabChildGlobal*>(et)->MarkForCC();
+ EventListenerManager* elm = et->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+ }
+ }
+}
+
+static void
+MarkMessageManagers()
+{
+ if (nsFrameMessageManager::GetChildProcessManager()) {
+ // ProcessGlobal's MarkForCC marks also ChildProcessManager.
+ ProcessGlobal* pg = ProcessGlobal::Get();
+ if (pg) {
+ pg->MarkForCC();
+ }
+ }
+
+ // The global message manager only exists in the root process.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+ nsCOMPtr<nsIMessageBroadcaster> strongGlobalMM =
+ do_GetService("@mozilla.org/globalmessagemanager;1");
+ if (!strongGlobalMM) {
+ return;
+ }
+ nsIMessageBroadcaster* globalMM = strongGlobalMM;
+ strongGlobalMM = nullptr;
+ MarkChildMessageManagers(globalMM);
+
+ if (nsFrameMessageManager::sParentProcessManager) {
+ nsFrameMessageManager::sParentProcessManager->MarkForCC();
+ uint32_t childCount = 0;
+ nsFrameMessageManager::sParentProcessManager->GetChildCount(&childCount);
+ for (uint32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIMessageListenerManager> childMM;
+ nsFrameMessageManager::sParentProcessManager->
+ GetChildAt(i, getter_AddRefs(childMM));
+ if (!childMM) {
+ continue;
+ }
+ nsIMessageListenerManager* child = childMM;
+ childMM = nullptr;
+ child->MarkForCC();
+ }
+ }
+ if (nsFrameMessageManager::sSameProcessParentManager) {
+ nsFrameMessageManager::sSameProcessParentManager->MarkForCC();
+ }
+}
+
+void
+MarkContentViewer(nsIContentViewer* aViewer, bool aCleanupJS,
+ bool aPrepareForCC)
+{
+ if (!aViewer) {
+ return;
+ }
+
+ nsIDocument *doc = aViewer->GetDocument();
+ if (doc &&
+ doc->GetMarkedCCGeneration() != nsCCUncollectableMarker::sGeneration) {
+ doc->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration);
+ if (aCleanupJS) {
+ EventListenerManager* elm = doc->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+ nsCOMPtr<EventTarget> win = do_QueryInterface(doc->GetInnerWindow());
+ if (win) {
+ elm = win->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+ static_cast<nsGlobalWindow*>(win.get())->UnmarkGrayTimers();
+ }
+ } else if (aPrepareForCC) {
+ // Unfortunately we need to still mark user data just before running CC so
+ // that it has the right generation.
+ doc->PropertyTable(DOM_USER_DATA)->
+ EnumerateAll(MarkUserData, &nsCCUncollectableMarker::sGeneration);
+ }
+ }
+ 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,
+ bool aPrepareForCC);
+
+void
+MarkSHEntry(nsISHEntry* aSHEntry, bool aCleanupJS, bool aPrepareForCC)
+{
+ if (!aSHEntry) {
+ return;
+ }
+
+ nsCOMPtr<nsIContentViewer> cview;
+ aSHEntry->GetContentViewer(getter_AddRefs(cview));
+ MarkContentViewer(cview, aCleanupJS, aPrepareForCC);
+
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ int32_t i = 0;
+ while (NS_SUCCEEDED(aSHEntry->ChildShellAt(i++, getter_AddRefs(child))) &&
+ child) {
+ MarkDocShell(child, aCleanupJS, aPrepareForCC);
+ }
+
+ nsCOMPtr<nsISHContainer> shCont = do_QueryInterface(aSHEntry);
+ int32_t count;
+ shCont->GetChildCount(&count);
+ for (i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> childEntry;
+ shCont->GetChildAt(i, getter_AddRefs(childEntry));
+ MarkSHEntry(childEntry, aCleanupJS, aPrepareForCC);
+ }
+
+}
+
+void
+MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS, bool aPrepareForCC)
+{
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(aNode);
+ if (!shell) {
+ return;
+ }
+
+ nsCOMPtr<nsIContentViewer> cview;
+ shell->GetContentViewer(getter_AddRefs(cview));
+ MarkContentViewer(cview, aCleanupJS, aPrepareForCC);
+
+ nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(shell);
+ nsCOMPtr<nsISHistory> history;
+ webNav->GetSessionHistory(getter_AddRefs(history));
+ if (history) {
+ int32_t i, historyCount;
+ history->GetCount(&historyCount);
+ for (i = 0; i < historyCount; ++i) {
+ nsCOMPtr<nsISHEntry> shEntry;
+ history->GetEntryAtIndex(i, false, getter_AddRefs(shEntry));
+
+ MarkSHEntry(shEntry, aCleanupJS, aPrepareForCC);
+ }
+ }
+
+ int32_t i, childCount;
+ aNode->GetChildCount(&childCount);
+ for (i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ aNode->GetChildAt(i, getter_AddRefs(child));
+ MarkDocShell(child, aCleanupJS, aPrepareForCC);
+ }
+}
+
+void
+MarkWindowList(nsISimpleEnumerator* aWindowList, bool aCleanupJS,
+ bool aPrepareForCC)
+{
+ 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, aPrepareForCC);
+
+ nsCOMPtr<nsITabChild> tabChild =
+ rootDocShell ? rootDocShell->GetTabChild() : nullptr;
+ if (tabChild) {
+ nsCOMPtr<nsIContentFrameMessageManager> mm;
+ tabChild->GetMessageManager(getter_AddRefs(mm));
+ 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 there has been a GC.
+ bool cleanupJS =
+ nsJSContext::CleanupsSinceLastGC() == 0 &&
+ !strcmp(aTopic, "cycle-collector-forget-skippable");
+
+ 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, prepareForCC);
+ }
+
+ nsCOMPtr<nsIWindowWatcher> ww =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ if (ww) {
+ rv = ww->GetWindowEnumerator(getter_AddRefs(windowList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MarkWindowList(windowList, cleanupJS, prepareForCC);
+ }
+
+ nsCOMPtr<nsIAppShellService> appShell =
+ do_GetService(NS_APPSHELLSERVICE_CONTRACTID);
+ if (appShell) {
+ nsCOMPtr<nsIXULWindow> hw;
+ appShell->GetHiddenWindow(getter_AddRefs(hw));
+ if (hw) {
+ nsCOMPtr<nsIDocShell> shell;
+ hw->GetDocShell(getter_AddRefs(shell));
+ MarkDocShell(shell, cleanupJS, prepareForCC);
+ }
+ bool hasHiddenPrivateWindow = false;
+ appShell->GetHasHiddenPrivateWindow(&hasHiddenPrivateWindow);
+ if (hasHiddenPrivateWindow) {
+ appShell->GetHiddenPrivateWindow(getter_AddRefs(hw));
+ if (hw) {
+ nsCOMPtr<nsIDocShell> shell;
+ hw->GetDocShell(getter_AddRefs(shell));
+ MarkDocShell(shell, cleanupJS, prepareForCC);
+ }
+ }
+ }
+
+#ifdef MOZ_XUL
+ nsXULPrototypeCache* xulCache = nsXULPrototypeCache::GetInstance();
+ if (xulCache) {
+ xulCache->MarkInCCGeneration(sGeneration);
+ }
+#endif
+
+ enum ForgetSkippableCleanupState
+ {
+ eInitial = 0,
+ eUnmarkJSEventListeners = 1,
+ eUnmarkMessageManagers = 2,
+ eUnmarkStrongObservers = 3,
+ eUnmarkJSHolders = 4,
+ eDone = 5
+ };
+
+ static_assert(eDone == NS_MAJOR_FORGET_SKIPPABLE_CALLS,
+ "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, uint32_t aGCNumber, bool aIsShutdownGC)
+{
+#ifdef MOZ_XUL
+ // Mark the scripts held in the XULPrototypeCache. This is required to keep
+ // the JS script in the cache live across GC.
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::MaybeGetInstance();
+ if (cache) {
+ if (aIsShutdownGC) {
+ cache->FlushScripts();
+ } else {
+ cache->MarkInGC(aTrc);
+ }
+ }
+#endif
+
+ if (!nsCCUncollectableMarker::sGeneration) {
+ return;
+ }
+
+ if (nsFrameMessageManager::GetChildProcessManager()) {
+ nsIContentProcessMessageManager* pg = ProcessGlobal::Get();
+ if (pg) {
+ mozilla::TraceScriptHolder(pg, aTrc);
+ }
+ }
+
+ // Mark globals of active windows black.
+ nsGlobalWindow::WindowByIdTable* windowsById =
+ nsGlobalWindow::GetWindowsTable();
+ if (windowsById) {
+ for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
+ nsGlobalWindow* window = iter.Data();
+ if (window->GetDocShell() && window->IsOuterWindow()) {
+ window->TraceGlobalJSObject(aTrc);
+ EventListenerManager* elm = window->GetExistingListenerManager();
+ if (elm) {
+ elm->TraceListeners(aTrc);
+ }
+
+ if (window->IsRootOuterWindow()) {
+ // In child process trace all the TabChildGlobals.
+ // Since there is one root outer window per TabChildGlobal, we need
+ // to look for only those windows, not all.
+ nsIDocShell* ds = window->GetDocShell();
+ if (ds) {
+ nsCOMPtr<nsITabChild> tabChild = ds->GetTabChild();
+ if (tabChild) {
+ nsCOMPtr<nsIContentFrameMessageManager> mm;
+ tabChild->GetMessageManager(getter_AddRefs(mm));
+ nsCOMPtr<EventTarget> et = do_QueryInterface(mm);
+ if (et) {
+ nsCOMPtr<nsISupports> tabChildAsSupports =
+ do_QueryInterface(tabChild);
+ mozilla::TraceScriptHolder(tabChildAsSupports, aTrc);
+ EventListenerManager* elm = et->GetExistingListenerManager();
+ if (elm) {
+ elm->TraceListeners(aTrc);
+ }
+ // As of now there isn't an easy way to trace message listeners.
+ }
+ }
+ }
+ }
+
+#ifdef MOZ_XUL
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc && doc->IsXULDocument()) {
+ XULDocument* xulDoc = static_cast<XULDocument*>(doc);
+ xulDoc->TraceProtos(aTrc, aGCNumber);
+ }
+#endif
+ }
+ }
+ }
+}
diff --git a/dom/base/nsCCUncollectableMarker.h b/dom/base/nsCCUncollectableMarker.h
new file mode 100644
index 000000000..98959efa8
--- /dev/null
+++ b/dom/base/nsCCUncollectableMarker.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 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() {}
+ ~nsCCUncollectableMarker() {}
+};
+
+namespace mozilla {
+namespace dom {
+void TraceBlackJS(JSTracer* aTrc, uint32_t aGCNumber, bool aIsShutdownGC);
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/base/nsCaseTreatment.h b/dom/base/nsCaseTreatment.h
new file mode 100644
index 000000000..8a46da26f
--- /dev/null
+++ b/dom/base/nsCaseTreatment.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 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 000000000..96c4e0eff
--- /dev/null
+++ b/dom/base/nsChildContentList.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 nsChildContentList_h__
+#define nsChildContentList_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 nsIDOMNodeList interface (a list of children of
+ * the content), by holding a reference to the content and delegating GetLength
+ * and Item to its existing child list.
+ * @see nsIDOMNodeList
+ */
+class nsChildContentList final : public nsINodeList
+{
+public:
+ explicit nsChildContentList(nsINode* aNode)
+ : mNode(aNode)
+ {
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(nsChildContentList)
+
+ // nsWrapperCache
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsIDOMNodeList interface
+ NS_DECL_NSIDOMNODELIST
+
+ // nsINodeList interface
+ virtual int32_t IndexOf(nsIContent* aContent) override;
+ virtual nsIContent* Item(uint32_t aIndex) override;
+
+ void DropReference()
+ {
+ mNode = nullptr;
+ }
+
+ virtual nsINode* GetParentObject() override
+ {
+ return mNode;
+ }
+
+private:
+ ~nsChildContentList() {}
+
+ // The node whose children make up the list.
+ // This is a non-owning ref which is safe because it's set to nullptr by
+ // DropReference() by the node slots get destroyed.
+ nsINode* MOZ_NON_OWNING_REF mNode;
+};
+
+#endif /* nsChildContentList_h__ */
diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp
new file mode 100644
index 000000000..6a2fb10ed
--- /dev/null
+++ b/dom/base/nsContentAreaDragDrop.cpp
@@ -0,0 +1,895 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMUIEvent.h"
+#include "nsISelection.h"
+#include "nsISelectionController.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMDragEvent.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMRange.h"
+#include "nsIFormControl.h"
+#include "nsIDOMHTMLAreaElement.h"
+#include "nsIDOMHTMLAnchorElement.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 "nsIWebNavigation.h"
+#include "nsIDocShell.h"
+#include "nsIContent.h"
+#include "nsIImageLoadingContent.h"
+#include "nsITextControlElement.h"
+#include "nsUnicharUtils.h"
+#include "nsIURL.h"
+#include "nsIDocument.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrincipal.h"
+#include "nsIDocShellTreeItem.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 "TabParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "nsVariant.h"
+
+using namespace mozilla::dom;
+
+class MOZ_STACK_CLASS DragDataProducer
+{
+public:
+ DragDataProducer(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aTarget,
+ nsIContent* aSelectionTargetNode,
+ bool aIsAltKeyPressed);
+ nsresult Produce(DataTransfer* aDataTransfer,
+ bool* aCanDrag,
+ nsISelection** aSelection,
+ nsIContent** aDragNode);
+
+private:
+ void AddString(DataTransfer* aDataTransfer,
+ const nsAString& aFlavor,
+ const nsAString& aData,
+ nsIPrincipal* aPrincipal);
+ nsresult AddStringsToDataTransfer(nsIContent* aDragNode,
+ DataTransfer* aDataTransfer);
+ static nsresult GetDraggableSelectionData(nsISelection* inSelection,
+ nsIContent* inRealTargetNode,
+ nsIContent **outImageOrLinkNode,
+ bool* outDragSelectedText);
+ static already_AddRefed<nsIContent> FindParentLinkNode(nsIContent* inNode);
+ static MOZ_MUST_USE nsresult
+ GetAnchorURL(nsIContent* inNode, nsAString& outURL);
+ static void GetNodeString(nsIContent* inNode, nsAString & outNodeString);
+ 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;
+ 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,
+ nsISelection** aSelection,
+ nsIContent** aDragNode)
+{
+ NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG);
+
+ *aCanDrag = true;
+
+ DragDataProducer
+ provider(aWindow, aTarget, aSelectionTargetNode, aIsAltKeyPressed);
+ return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode);
+}
+
+
+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(nsAString& inSourceURIString,
+ nsIFile* inDestFile,
+ bool isPrivate)
+{
+ nsCOMPtr<nsIURI> sourceURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(sourceURI), inSourceURIString);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
+ if (!sourceURL) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ 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->SavePrivacyAwareURI(sourceURI, nullptr, nullptr,
+ mozilla::net::RP_Default,
+ nullptr, nullptr,
+ inDestFile, isPrivate);
+}
+
+// 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,
+ uint32_t *aDataLen)
+{
+ NS_ENSURE_ARG_POINTER(aData && aDataLen);
+ *aData = nullptr;
+ *aDataLen = 0;
+
+ 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;
+ uint32_t dataSize = 0;
+ aTransferable->GetTransferData(kFilePromiseURLMime,
+ getter_AddRefs(tmp), &dataSize);
+ nsCOMPtr<nsISupportsString> supportsString =
+ do_QueryInterface(tmp);
+ if (!supportsString)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString sourceURLString;
+ supportsString->GetData(sourceURLString);
+ if (sourceURLString.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ aTransferable->GetTransferData(kFilePromiseDestFilename,
+ getter_AddRefs(tmp), &dataSize);
+ supportsString = do_QueryInterface(tmp);
+ if (!supportsString)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString targetFilename;
+ supportsString->GetData(targetFilename);
+ if (targetFilename.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ // get the target directory from the kFilePromiseDirectoryMime
+ // flavor
+ nsCOMPtr<nsISupports> dirPrimitive;
+ dataSize = 0;
+ aTransferable->GetTransferData(kFilePromiseDirectoryMime,
+ getter_AddRefs(dirPrimitive), &dataSize);
+ 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(&isPrivate);
+
+ rv = SaveURIToFile(sourceURLString, file, isPrivate);
+ // send back an nsIFile
+ if (NS_SUCCEEDED(rv)) {
+ CallQueryInterface(file, aData);
+ *aDataLen = sizeof(nsIFile*);
+ }
+ }
+
+ return rv;
+}
+
+DragDataProducer::DragDataProducer(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aTarget,
+ nsIContent* aSelectionTargetNode,
+ bool aIsAltKeyPressed)
+ : mWindow(aWindow),
+ mTarget(aTarget),
+ mSelectionTargetNode(aSelectionTargetNode),
+ mIsAltKeyPressed(aIsAltKeyPressed),
+ mIsAnchor(false)
+{
+}
+
+
+//
+// FindParentLinkNode
+//
+// Finds the parent with the given link tag starting at |inNode|. If
+// it gets up to the root without finding it, we stop looking and
+// return null.
+//
+already_AddRefed<nsIContent>
+DragDataProducer::FindParentLinkNode(nsIContent* inNode)
+{
+ nsIContent* content = inNode;
+ if (!content) {
+ // That must have been the document node; nothing else to do here;
+ return nullptr;
+ }
+
+ for (; content; content = content->GetParent()) {
+ if (nsContentUtils::IsDraggableLink(content)) {
+ nsCOMPtr<nsIContent> ret = content;
+ return ret.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+
+//
+// GetAnchorURL
+//
+nsresult
+DragDataProducer::GetAnchorURL(nsIContent* inNode, nsAString& outURL)
+{
+ nsCOMPtr<nsIURI> linkURI;
+ if (!inNode || !inNode->IsLink(getter_AddRefs(linkURI))) {
+ // Not a link
+ outURL.Truncate();
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ nsresult rv = linkURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(spec, outURL);
+ 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(NS_LITERAL_STRING("<a href=\"") +
+ inURL +
+ NS_LITERAL_STRING("\">") +
+ inText +
+ NS_LITERAL_STRING("</a>") );
+
+ outLinkText = linkText;
+}
+
+
+//
+// GetNodeString
+//
+// Gets the text associated with a node
+//
+void
+DragDataProducer::GetNodeString(nsIContent* inNode,
+ nsAString & outNodeString)
+{
+ nsCOMPtr<nsINode> node = inNode;
+
+ outNodeString.Truncate();
+
+ // use a range to get the text-equivalent of the node
+ nsCOMPtr<nsIDocument> doc = node->OwnerDoc();
+ mozilla::ErrorResult rv;
+ RefPtr<nsRange> range = doc->CreateRange(rv);
+ if (range) {
+ range->SelectNode(*node, rv);
+ range->ToString(outNodeString);
+ }
+}
+
+nsresult
+DragDataProducer::Produce(DataTransfer* aDataTransfer,
+ bool* aCanDrag,
+ nsISelection** aSelection,
+ nsIContent** aDragNode)
+{
+ NS_PRECONDITION(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.
+ nsCOMPtr<nsISelection> selection;
+ nsIContent* editingElement = mSelectionTargetNode->IsEditable() ?
+ mSelectionTargetNode->GetEditingHost() : nullptr;
+ nsCOMPtr<nsITextControlElement> textControl =
+ nsITextControlElement::GetTextControlElementFromEditingHost(editingElement);
+ if (textControl) {
+ nsISelectionController* selcon = textControl->GetSelectionController();
+ if (selcon) {
+ selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
+ }
+
+ 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;
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti = mWindow->GetDocShell();
+ const bool isChromeShell =
+ dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome;
+
+ // In chrome shells, only allow dragging inside editable areas.
+ if (isChromeShell && !editingElement) {
+ nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(mTarget);
+ if (flo) {
+ RefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
+ if (fl) {
+ TabParent* tp = static_cast<TabParent*>(fl->GetRemoteBrowser());
+ if (tp) {
+ // We have a TabParent, so it may have data for dnd in case the child
+ // process started a dnd session.
+ tp->AddInitialDnDDataTo(aDataTransfer);
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ if (isChromeShell && textControl) {
+ // Only use the selection if the target node is in the selection.
+ bool selectionContainsTarget = false;
+ nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(mSelectionTargetNode);
+ selection->ContainsNode(targetNode, false, &selectionContainsTarget);
+ if (!selectionContainsTarget)
+ 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->GetType() != NS_FORM_OBJECT) {
+ *aCanDrag = false;
+ return NS_OK;
+ }
+
+ draggedNode = mTarget;
+ }
+
+ nsCOMPtr<nsIDOMHTMLAreaElement> area; // client-side image map
+ nsCOMPtr<nsIImageLoadingContent> image;
+ nsCOMPtr<nsIDOMHTMLAnchorElement> link;
+
+ 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 = FindParentLinkNode(draggedNode);
+ if (parentLink && mIsAltKeyPressed) {
+ *aCanDrag = false;
+ return NS_OK;
+ }
+
+ area = do_QueryInterface(draggedNode);
+ image = do_QueryInterface(draggedNode);
+ link = do_QueryInterface(draggedNode);
+ }
+
+ {
+ // set for linked images, and links
+ nsCOMPtr<nsIContent> linkNode;
+
+ if (area) {
+ // use the alt text (or, if missing, the href) as the title
+ HTMLAreaElement* areaElem = static_cast<HTMLAreaElement*>(area.get());
+ areaElem->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString);
+ if (mTitleString.IsEmpty()) {
+ // this can be a relative link
+ areaElem->GetAttribute(NS_LITERAL_STRING("href"), mTitleString);
+ }
+
+ // we'll generate HTML like <a href="absurl">alt text</a>
+ mIsAnchor = true;
+
+ // gives an absolute link
+ nsresult rv = GetAnchorURL(draggedNode, mUrlString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHtmlString.AssignLiteral("<a href=\"");
+ mHtmlString.Append(mUrlString);
+ mHtmlString.AppendLiteral("\">");
+ mHtmlString.Append(mTitleString);
+ mHtmlString.AppendLiteral("</a>");
+
+ dragNode = draggedNode;
+ } else if (image) {
+ mIsAnchor = true;
+ // 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));
+ if (imageURI) {
+ nsAutoCString spec;
+ rv = imageURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(spec, mUrlString);
+ }
+
+ nsCOMPtr<nsIDOMElement> imageElement(do_QueryInterface(image));
+ // XXXbz Shouldn't we use the "title" attr for title? Using
+ // "alt" seems very wrong....
+ if (imageElement) {
+ imageElement->GetAttribute(NS_LITERAL_STRING("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));
+
+ nsCOMPtr<nsIMIMEService> mimeService =
+ do_GetService("@mozilla.org/mime;1");
+
+ // Fix the file extension in the URL if necessary
+ if (imgRequest && mimeService) {
+ nsCOMPtr<nsIURI> imgUri;
+ imgRequest->GetURI(getter_AddRefs(imgUri));
+
+ nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
+
+ if (imgUrl) {
+ nsAutoCString extension;
+ imgUrl->GetFileExtension(extension);
+
+ nsXPIDLCString mimeType;
+ imgRequest->GetMimeType(getter_Copies(mimeType));
+
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
+ getter_AddRefs(mimeInfo));
+
+ if (mimeInfo) {
+ nsAutoCString spec;
+ rv = imgUrl->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass out the image source string
+ CopyUTF8toUTF16(spec, mImageSourceString);
+
+ bool validExtension;
+ if (extension.IsEmpty() ||
+ NS_FAILED(mimeInfo->ExtensionExists(extension,
+ &validExtension)) ||
+ !validExtension) {
+ // Fix the file extension in the URL
+ nsresult rv = imgUrl->Clone(getter_AddRefs(imgUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ imgUrl = do_QueryInterface(imgUri);
+
+ nsAutoCString primaryExtension;
+ mimeInfo->GetPrimaryExtension(primaryExtension);
+
+ imgUrl->SetFileExtension(primaryExtension);
+ }
+
+ nsAutoCString fileName;
+ imgUrl->GetFileName(fileName);
+
+ NS_UnescapeURL(fileName);
+
+ // make the filename safe for the filesystem
+ fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
+ '-');
+
+ CopyUTF8toUTF16(fileName, mImageDestFileName);
+
+ // and the image object
+ mImage = img;
+ }
+ }
+ }
+
+ 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 = do_QueryInterface(draggedNode);
+ }
+ dragNode = nodeToSerialize;
+ } else if (link) {
+ // set linkNode. The code below will handle this
+ linkNode = do_QueryInterface(link); // XXX test this
+ GetNodeString(draggedNode, mTitleString);
+ } 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) {
+ mIsAnchor = true;
+ rv = GetAnchorURL(linkNode, mUrlString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dragNode = linkNode;
+ }
+ }
+ }
+
+ if (nodeToSerialize || *aSelection) {
+ mHtmlString.Truncate();
+ mContextString.Truncate();
+ mInfoString.Truncate();
+ mTitleString.Truncate();
+
+ nsCOMPtr<nsIDocument> doc = mWindow->GetDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ // 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;
+ uint32_t dataSize;
+ rv = transferable->GetTransferData(kHTMLMime, getter_AddRefs(supports),
+ &dataSize);
+ data = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv)) {
+ data->GetData(mHtmlString);
+ }
+ rv = transferable->GetTransferData(kHTMLContext, getter_AddRefs(supports),
+ &dataSize);
+ data = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv)) {
+ data->GetData(mContextString);
+ }
+ rv = transferable->GetTransferData(kHTMLInfo, getter_AddRefs(supports),
+ &dataSize);
+ data = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv)) {
+ data->GetData(mInfoString);
+ }
+ rv = transferable->GetTransferData(kUnicodeMime, getter_AddRefs(supports),
+ &dataSize);
+ 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)
+{
+ RefPtr<nsVariantCC> variant = new nsVariantCC();
+ variant->SetAsAString(aData);
+ aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal);
+}
+
+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("\r\n", ' ');
+ dragData += title;
+
+ AddString(aDataTransfer, NS_LITERAL_STRING(kURLMime), dragData, principal);
+ AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal);
+ AddString(aDataTransfer, NS_LITERAL_STRING(kURLDescriptionMime), mTitleString, principal);
+ AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal);
+ }
+
+ // add a special flavor for the html context data
+ if (!mContextString.IsEmpty())
+ AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), mContextString, principal);
+
+ // add a special flavor if we have html info data
+ if (!mInfoString.IsEmpty())
+ AddString(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), mInfoString, principal);
+
+ // add the full html
+ if (!mHtmlString.IsEmpty())
+ AddString(aDataTransfer, NS_LITERAL_STRING(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(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(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(kFilePromiseMime),
+ variant, 0, principal);
+ }
+
+ AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseURLMime),
+ mImageSourceString, principal);
+ AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseDestFilename),
+ mImageDestFileName, principal);
+
+ // if not an anchor, add the image url
+ if (!mIsAnchor) {
+ AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal);
+ AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal);
+ }
+ }
+
+ return NS_OK;
+}
+
+// note that this can return NS_OK, but a null out param (by design)
+// static
+nsresult
+DragDataProducer::GetDraggableSelectionData(nsISelection* inSelection,
+ nsIContent* inRealTargetNode,
+ nsIContent **outImageOrLinkNode,
+ bool* outDragSelectedText)
+{
+ NS_ENSURE_ARG(inSelection);
+ NS_ENSURE_ARG(inRealTargetNode);
+ NS_ENSURE_ARG_POINTER(outImageOrLinkNode);
+
+ *outImageOrLinkNode = nullptr;
+ *outDragSelectedText = false;
+
+ bool selectionContainsTarget = false;
+
+ bool isCollapsed = false;
+ inSelection->GetIsCollapsed(&isCollapsed);
+ if (!isCollapsed) {
+ nsCOMPtr<nsIDOMNode> realTargetNode = do_QueryInterface(inRealTargetNode);
+ inSelection->ContainsNode(realTargetNode, false,
+ &selectionContainsTarget);
+
+ if (selectionContainsTarget) {
+ // track down the anchor node, if any, for the url
+ nsCOMPtr<nsIDOMNode> selectionStart;
+ inSelection->GetAnchorNode(getter_AddRefs(selectionStart));
+
+ nsCOMPtr<nsIDOMNode> selectionEnd;
+ inSelection->GetFocusNode(getter_AddRefs(selectionEnd));
+
+ // 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) {
+ bool hasChildren;
+ selectionStart->HasChildNodes(&hasChildren);
+ if (hasChildren) {
+ // see if just one node is selected
+ int32_t anchorOffset, focusOffset;
+ inSelection->GetAnchorOffset(&anchorOffset);
+ inSelection->GetFocusOffset(&focusOffset);
+ if (abs(anchorOffset - focusOffset) == 1) {
+ nsCOMPtr<nsIContent> selStartContent =
+ do_QueryInterface(selectionStart);
+
+ if (selStartContent) {
+ int32_t childOffset =
+ (anchorOffset < focusOffset) ? anchorOffset : focusOffset;
+ nsIContent *childContent =
+ selStartContent->GetChildAt(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 000000000..b62132efa
--- /dev/null
+++ b/dom/base/nsContentAreaDragDrop.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 nsContentAreaDragDrop_h__
+#define nsContentAreaDragDrop_h__
+
+
+#include "nsCOMPtr.h"
+
+#include "nsIDOMEventListener.h"
+#include "nsITransferable.h"
+
+class nsPIDOMWindowOuter;
+class nsISelection;
+class nsITransferable;
+class nsIContent;
+class nsIFile;
+
+namespace mozilla {
+namespace dom {
+class DataTransfer;
+} // namespace dom
+} // namespace mozilla
+
+//
+// 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.
+ */
+ static nsresult GetDragData(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aTarget,
+ nsIContent* aSelectionTargetNode,
+ bool aIsAltKeyPressed,
+ mozilla::dom::DataTransfer* aDataTransfer,
+ bool* aCanDrag,
+ nsISelection** aSelection,
+ nsIContent** aDragNode);
+};
+
+// 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() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFLAVORDATAPROVIDER
+
+ nsresult SaveURIToFile(nsAString& inSourceURIString,
+ nsIFile* inDestFile, bool isPrivate);
+};
+
+
+#endif /* nsContentAreaDragDrop_h__ */
+
diff --git a/dom/base/nsContentCID.h b/dom/base/nsContentCID.h
new file mode 100644
index 000000000..d897859b9
--- /dev/null
+++ b/dom/base/nsContentCID.h
@@ -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/. */
+
+#ifndef nsContentCID_h__
+#define nsContentCID_h__
+
+#define NS_DOC_ENCODER_CONTRACTID_BASE \
+"@mozilla.org/layout/documentEncoder;1?type="
+
+#define NS_HTMLCOPY_ENCODER_CONTRACTID \
+"@mozilla.org/layout/htmlCopyEncoder;1"
+
+// {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_HTMLDOCUMENT_CID \
+{ /* 5d0fcdd0-4daa-11d2-b328-00805f8a3859 */ \
+ 0x5d0fcdd0, 0x4daa, 0x11d2, \
+ {0xb3, 0x28, 0x00, 0x80, 0x5f, 0x8a, 0x38, 0x59}}
+
+#define NS_XMLDOCUMENT_CID \
+{ /* a6cf9063-15b3-11d2-932e-00805f8add32 */ \
+ 0xa6cf9063, 0x15b3, 0x11d2, \
+ {0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32}}
+
+#define NS_IMAGEDOCUMENT_CID \
+{ /* e11a6080-4daa-11d2-b328-00805f8a3859 */ \
+ 0xe11a6080, 0x4daa, 0x11d2, \
+ {0xb3, 0x28, 0x00, 0x80, 0x5f, 0x8a, 0x38, 0x59}}
+
+// {e7ba1480-1dea-11d3-830f-00104bed045e}
+#define NS_TEXT_ENCODER_CID \
+{ 0xe7ba1480, 0x1dea, 0x11d3, {0x83, 0x0f, 0x00, 0x10, 0x4b, 0xed, 0x04, 0x5e} }
+
+ // {7f915b01-98fc-11d4-8eb0-a803f80ff1bc}
+#define NS_HTMLCOPY_TEXT_ENCODER_CID \
+{ 0x7f915b01, 0x98fc, 0x11d4, { 0x8e, 0xb0, 0xa8, 0x03, 0xf8, 0x0f, 0xf1, 0xbc } }
+
+#define NS_NAMESPACEMANAGER_CID \
+{ /* d9783472-8fe9-11d2-9d3c-0060088f9ff7 */ \
+ 0xd9783472, 0x8fe9, 0x11d2, \
+ {0x9d, 0x3c, 0x00, 0x60, 0x08, 0x8f, 0x9f, 0xf7}}
+
+#define NS_CONTENTITERATOR_CID \
+{/* {a6cf90e3-15b3-11d2-932e-00805f8add32}*/ \
+ 0xa6cf90e3, 0x15b3, 0x11d2, {0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32 } }
+
+#define NS_PRECONTENTITERATOR_CID \
+{/* {80D7E247-D4B8-45d7-BB59-6F1DD56F384C} */ \
+ 0x80d7e247, 0xd4b8, 0x45d7, { 0xbb, 0x59, 0x6f, 0x1d, 0xd5, 0x6f, 0x38, 0x4c } }
+
+#define NS_SUBTREEITERATOR_CID \
+{/* {a6cf90e5-15b3-11d2-932e-00805f8add32}*/ \
+ 0xa6cf90e5, 0x15b3, 0x11d2, {0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32 } }
+
+// {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 } }
+
+// {1F5C1721-7DC3-11d3-BF87-00105A1B0627}
+#define NS_XULCONTROLLERS_CID \
+{ 0x1f5c1721, 0x7dc3, 0x11d3, { 0xbf, 0x87, 0x0, 0x10, 0x5a, 0x1b, 0x6, 0x27 } }
+
+
+// {BFD05264-834C-11d2-8EAC-00805F29F371}
+#define NS_XULSORTSERVICE_CID \
+{ 0xbfd05264, 0x834c, 0x11d2, { 0x8e, 0xac, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x71 } }
+
+// {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 } }
+
+// {541AFCB2-A9A3-11d2-8EC5-00805F29F370}
+#define NS_XULDOCUMENT_CID \
+{ 0x541afcb2, 0xa9a3, 0x11d2, { 0x8e, 0xc5, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x70 } }
+
+#define NS_SVGDOCUMENT_CID \
+{ /* b7f44954-1dd1-11b2-8c2e-c2feab4186bc */ \
+ 0xb7f44954, 0x11d1, 0x11b2, \
+ {0x8c, 0x2e, 0xc2, 0xfe, 0xab, 0x41, 0x86, 0xbc}}
+
+// {d899a152-9412-46b2-b651-2e71c5c2f05f}
+#define NS_VIDEODOCUMENT_CID \
+{ 0xd899a152, 0x9412, 0x46b2, \
+ { 0xb6, 0x51, 0x2e, 0x71, 0xc5, 0xc2, 0xf0, 0x5f } }
+
+#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"
+
+// {f96f5ec9-755b-447e-b1f3-717d1a84bb41}
+#define NS_PLUGINDOCUMENT_CID \
+{ 0xf96f5ec9, 0x755b, 0x447e, { 0xb1, 0xf3, 0x71, 0x7d, 0x1a, 0x84, 0xbb, 0x41 } }
+
+// {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 000000000..9576d9ba8
--- /dev/null
+++ b/dom/base/nsContentCreatorFunctions.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 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 nsIContent;
+class imgRequestProxy;
+class nsGenericHTMLElement;
+
+namespace mozilla {
+namespace dom {
+class Element;
+class NodeInfo;
+} // namespace dom
+} // namespace mozilla
+
+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,
+ const nsAString* aIs = 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);
+
+#ifdef MOZ_XUL
+nsresult
+NS_NewXULElement(mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+void
+NS_TrustedNewXULElement(nsIContent** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+#endif
+
+nsresult
+NS_NewSVGElement(mozilla::dom::Element** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser);
+
+nsresult
+NS_NewGenConImageContent(nsIContent** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ imgRequestProxy* aImageRequest);
+
+#endif // nsContentCreatorFunctions_h__
diff --git a/dom/base/nsContentIterator.cpp b/dom/base/nsContentIterator.cpp
new file mode 100644
index 000000000..287de7722
--- /dev/null
+++ b/dom/base/nsContentIterator.cpp
@@ -0,0 +1,1553 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/DebugOnly.h"
+#include "nsISupports.h"
+#include "nsIDOMNodeList.h"
+#include "nsIContentIterator.h"
+#include "nsRange.h"
+#include "nsIContent.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsContentUtils.h"
+#include "nsINode.h"
+#include "nsCycleCollectionParticipant.h"
+
+using mozilla::DebugOnly;
+
+// couple of utility static functs
+
+///////////////////////////////////////////////////////////////////////////
+// NodeToParentOffset: returns the node's parent and offset.
+//
+
+static nsINode*
+NodeToParentOffset(nsINode* aNode, int32_t* aOffset)
+{
+ *aOffset = 0;
+
+ nsINode* parent = aNode->GetParentNode();
+
+ if (parent) {
+ *aOffset = parent->IndexOf(aNode);
+ NS_WARNING_ASSERTION(*aOffset >= 0, "bad offset");
+ }
+
+ return parent;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// NodeIsInTraversalRange: returns true if content is visited during
+// the traversal of the range in the specified mode.
+//
+static bool
+NodeIsInTraversalRange(nsINode* aNode, bool aIsPreMode,
+ nsINode* aStartNode, int32_t aStartOffset,
+ nsINode* aEndNode, int32_t aEndOffset)
+{
+ if (NS_WARN_IF(!aStartNode) || NS_WARN_IF(!aEndNode) || 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 == aStartNode || aNode == aEndNode) {
+ if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
+ return true; // text node or something
+ }
+ if (!aNode->HasChildren()) {
+ MOZ_ASSERT(aNode != aStartNode || !aStartOffset,
+ "aStartNode doesn't have children and not a data node, "
+ "aStartOffset should be 0");
+ MOZ_ASSERT(aNode != aEndNode || !aEndOffset,
+ "aStartNode doesn't have children and not a data node, "
+ "aStartOffset should be 0");
+ return true;
+ }
+ }
+
+ nsINode* parent = aNode->GetParentNode();
+ if (!parent) {
+ return false;
+ }
+
+ int32_t indx = parent->IndexOf(aNode);
+ NS_WARNING_ASSERTION(indx != -1, "bad indx");
+
+ if (!aIsPreMode) {
+ ++indx;
+ }
+
+ return nsContentUtils::ComparePoints(aStartNode, aStartOffset,
+ parent, indx) <= 0 &&
+ nsContentUtils::ComparePoints(aEndNode, aEndOffset,
+ parent, indx) >= 0;
+}
+
+
+
+/*
+ * A simple iterator class for traversing the content in "close tag" order
+ */
+class nsContentIterator : public nsIContentIterator
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsContentIterator)
+
+ explicit nsContentIterator(bool aPre);
+
+ // nsIContentIterator interface methods ------------------------------
+
+ virtual nsresult Init(nsINode* aRoot) override;
+
+ virtual nsresult Init(nsIDOMRange* aRange) override;
+
+ virtual void First() override;
+
+ virtual void Last() override;
+
+ virtual void Next() override;
+
+ virtual void Prev() override;
+
+ virtual nsINode* GetCurrentNode() override;
+
+ virtual bool IsDone() override;
+
+ virtual nsresult PositionAt(nsINode* aCurNode) override;
+
+protected:
+ virtual ~nsContentIterator();
+
+ // Recursively get the deepest first/last child of aRoot. This will return
+ // aRoot itself if it has no children.
+ nsINode* GetDeepFirstChild(nsINode* aRoot,
+ nsTArray<int32_t>* aIndexes = nullptr);
+ nsIContent* GetDeepFirstChild(nsIContent* aRoot,
+ nsTArray<int32_t>* aIndexes = nullptr);
+ nsINode* GetDeepLastChild(nsINode* aRoot,
+ nsTArray<int32_t>* aIndexes = nullptr);
+ nsIContent* GetDeepLastChild(nsIContent* aRoot,
+ nsTArray<int32_t>* aIndexes = nullptr);
+
+ // 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.
+ nsIContent* GetNextSibling(nsINode* aNode,
+ nsTArray<int32_t>* aIndexes = nullptr);
+ nsIContent* GetPrevSibling(nsINode* aNode,
+ nsTArray<int32_t>* aIndexes = nullptr);
+
+ nsINode* NextNode(nsINode* aNode, nsTArray<int32_t>* aIndexes = nullptr);
+ nsINode* PrevNode(nsINode* aNode, nsTArray<int32_t>* aIndexes = nullptr);
+
+ // WARNING: This function is expensive
+ nsresult RebuildIndexStack();
+
+ void MakeEmpty();
+
+ virtual void LastRelease();
+
+ nsCOMPtr<nsINode> mCurNode;
+ nsCOMPtr<nsINode> mFirst;
+ nsCOMPtr<nsINode> mLast;
+ nsCOMPtr<nsINode> mCommonParent;
+
+ // used by nsContentIterator to cache indices
+ AutoTArray<int32_t, 8> mIndexes;
+
+ // used by nsSubtreeIterator to cache indices. Why put them in the base
+ // class? Because otherwise I have to duplicate the routines GetNextSibling
+ // etc across both classes, with slight variations for caching. Or
+ // alternately, create a base class for the cache itself and have all the
+ // cache manipulation go through a vptr. I think this is the best space and
+ // speed combo, even though it's ugly.
+ int32_t mCachedIndex;
+ // another note about mCachedIndex: why should the subtree iterator use a
+ // trivial cached index instead of the mre robust array of indicies (which is
+ // what the basic content iterator uses)? The reason is that subtree
+ // iterators do not do much transitioning between parents and children. They
+ // tend to stay at the same level. In fact, you can prove (though I won't
+ // attempt it here) that they change levels at most n+m times, where n is the
+ // height of the parent hierarchy from the range start to the common
+ // ancestor, and m is the the height of the parent hierarchy from the range
+ // end to the common ancestor. If we used the index array, we would pay the
+ // price up front for n, and then pay the cost for m on the fly later on.
+ // With the simple cache, we only "pay as we go". Either way, we call
+ // IndexOf() once for each change of level in the hierarchy. Since a trivial
+ // index is much simpler, we use it for the subtree iterator.
+
+ bool mIsDone;
+ bool mPre;
+
+private:
+
+ // no copies or assigns FIX ME
+ nsContentIterator(const nsContentIterator&);
+ nsContentIterator& operator=(const nsContentIterator&);
+
+};
+
+
+/******************************************************
+ * repository cruft
+ ******************************************************/
+
+already_AddRefed<nsIContentIterator>
+NS_NewContentIterator()
+{
+ nsCOMPtr<nsIContentIterator> iter = new nsContentIterator(false);
+ return iter.forget();
+}
+
+
+already_AddRefed<nsIContentIterator>
+NS_NewPreContentIterator()
+{
+ nsCOMPtr<nsIContentIterator> iter = new nsContentIterator(true);
+ return iter.forget();
+}
+
+
+/******************************************************
+ * XPCOM cruft
+ ******************************************************/
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentIterator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsContentIterator,
+ LastRelease())
+
+NS_INTERFACE_MAP_BEGIN(nsContentIterator)
+ NS_INTERFACE_MAP_ENTRY(nsIContentIterator)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentIterator)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsContentIterator)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsContentIterator,
+ mCurNode,
+ mFirst,
+ mLast,
+ mCommonParent)
+
+void
+nsContentIterator::LastRelease()
+{
+ mCurNode = nullptr;
+ mFirst = nullptr;
+ mLast = nullptr;
+ mCommonParent = nullptr;
+}
+
+/******************************************************
+ * constructor/destructor
+ ******************************************************/
+
+nsContentIterator::nsContentIterator(bool aPre) :
+ // don't need to explicitly initialize |nsCOMPtr|s, they will automatically
+ // be nullptr
+ mCachedIndex(0), mIsDone(false), mPre(aPre)
+{
+}
+
+
+nsContentIterator::~nsContentIterator()
+{
+}
+
+
+/******************************************************
+ * Init routines
+ ******************************************************/
+
+
+nsresult
+nsContentIterator::Init(nsINode* aRoot)
+{
+ if (NS_WARN_IF(!aRoot)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mIsDone = false;
+ mIndexes.Clear();
+
+ if (mPre) {
+ mFirst = aRoot;
+ mLast = GetDeepLastChild(aRoot);
+ NS_WARNING_ASSERTION(mLast, "GetDeepLastChild returned null");
+ } else {
+ mFirst = GetDeepFirstChild(aRoot);
+ NS_WARNING_ASSERTION(mFirst, "GetDeepFirstChild returned null");
+ mLast = aRoot;
+ }
+
+ mCommonParent = aRoot;
+ mCurNode = mFirst;
+ RebuildIndexStack();
+ return NS_OK;
+}
+
+nsresult
+nsContentIterator::Init(nsIDOMRange* aDOMRange)
+{
+ if (NS_WARN_IF(!aDOMRange)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsRange* range = static_cast<nsRange*>(aDOMRange);
+
+ mIsDone = false;
+
+ // get common content parent
+ mCommonParent = range->GetCommonAncestor();
+ if (NS_WARN_IF(!mCommonParent)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // get the start node and offset
+ int32_t startIndx = range->StartOffset();
+ NS_WARNING_ASSERTION(startIndx >= 0, "bad startIndx");
+ nsINode* startNode = range->GetStartParent();
+ if (NS_WARN_IF(!startNode)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // get the end node and offset
+ int32_t endIndx = range->EndOffset();
+ NS_WARNING_ASSERTION(endIndx >= 0, "bad endIndx");
+ nsINode* endNode = range->GetEndParent();
+ if (NS_WARN_IF(!endNode)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool startIsData = startNode->IsNodeOfType(nsINode::eDATA_NODE);
+
+ // short circuit when start node == end node
+ if (startNode == endNode) {
+ // 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 (!startIsData && startIndx == endIndx) {
+ MakeEmpty();
+ return NS_OK;
+ }
+
+ if (startIsData) {
+ // It's a character data node.
+ mFirst = startNode->AsContent();
+ mLast = mFirst;
+ mCurNode = mFirst;
+
+ DebugOnly<nsresult> rv = RebuildIndexStack();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RebuildIndexStack failed");
+ return NS_OK;
+ }
+ }
+
+ // Find first node in range.
+
+ nsIContent* cChild = nullptr;
+
+ // Valid start indices are 0 <= startIndx <= childCount. That means if start
+ // node has no children, only offset 0 is valid.
+ if (!startIsData && uint32_t(startIndx) < startNode->GetChildCount()) {
+ cChild = startNode->GetChildAt(startIndx);
+ NS_WARNING_ASSERTION(cChild, "GetChildAt returned null");
+ }
+
+ if (!cChild) {
+ // No children (possibly a <br> or text node), or index is after last child.
+
+ if (mPre) {
+ // 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 startIndx == 0, it means the node
+ // has no children, and the node may be <br> or something. We don't skip
+ // the node in this case in order to address bug 1215798.
+ if (!startIsData && startIndx) {
+ mFirst = GetNextSibling(startNode);
+ NS_WARNING_ASSERTION(mFirst, "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 (mFirst &&
+ NS_WARN_IF(!NodeIsInTraversalRange(mFirst, mPre, startNode,
+ startIndx, endNode, endIndx))) {
+ mFirst = nullptr;
+ }
+ } else {
+ mFirst = startNode->AsContent();
+ }
+ } else {
+ // post-order
+ if (NS_WARN_IF(!startNode->IsContent())) {
+ // What else can we do?
+ mFirst = nullptr;
+ } else {
+ mFirst = startNode->AsContent();
+ }
+ }
+ } else {
+ if (mPre) {
+ mFirst = cChild;
+ } else {
+ // post-order
+ mFirst = GetDeepFirstChild(cChild);
+ NS_WARNING_ASSERTION(mFirst, "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 (mFirst &&
+ !NodeIsInTraversalRange(mFirst, mPre, startNode, startIndx,
+ endNode, endIndx)) {
+ mFirst = nullptr;
+ }
+ }
+ }
+
+
+ // Find last node in range.
+
+ bool endIsData = endNode->IsNodeOfType(nsINode::eDATA_NODE);
+
+ if (endIsData || !endNode->HasChildren() || endIndx == 0) {
+ if (mPre) {
+ if (NS_WARN_IF(!endNode->IsContent())) {
+ // Not much else to do here...
+ mLast = nullptr;
+ } else {
+ // If the end node is an empty 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).
+ if (!endIsData && !endNode->HasChildren() && !endIndx) {
+ mLast = PrevNode(endNode);
+ NS_WARNING_ASSERTION(mLast, "PrevNode returned null");
+ if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre,
+ startNode, startIndx,
+ endNode, endIndx))) {
+ mLast = nullptr;
+ }
+ } else {
+ mLast = endNode->AsContent();
+ }
+ }
+ } else {
+ // 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 (!endIsData) {
+ mLast = GetPrevSibling(endNode);
+ NS_WARNING_ASSERTION(mLast, "GetPrevSibling returned null");
+
+ if (!NodeIsInTraversalRange(mLast, mPre,
+ startNode, startIndx,
+ endNode, endIndx)) {
+ mLast = nullptr;
+ }
+ } else {
+ mLast = endNode->AsContent();
+ }
+ }
+ } else {
+ int32_t indx = endIndx;
+
+ cChild = endNode->GetChildAt(--indx);
+
+ if (NS_WARN_IF(!cChild)) {
+ // No child at offset!
+ NS_NOTREACHED("nsContentIterator::nsContentIterator");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mPre) {
+ mLast = GetDeepLastChild(cChild);
+ NS_WARNING_ASSERTION(mLast, "GetDeepLastChild returned null");
+
+ if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre,
+ startNode, startIndx,
+ endNode, endIndx))) {
+ mLast = nullptr;
+ }
+ } else {
+ // post-order
+ mLast = cChild;
+ }
+ }
+
+ // If either first or last is null, they both have to be null!
+
+ if (!mFirst || !mLast) {
+ mFirst = nullptr;
+ mLast = nullptr;
+ }
+
+ mCurNode = mFirst;
+ mIsDone = !mCurNode;
+
+ if (!mCurNode) {
+ mIndexes.Clear();
+ } else {
+ DebugOnly<nsresult> rv = RebuildIndexStack();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RebuildIndexStack failed");
+ }
+
+ return NS_OK;
+}
+
+
+/******************************************************
+ * Helper routines
+ ******************************************************/
+// WARNING: This function is expensive
+nsresult
+nsContentIterator::RebuildIndexStack()
+{
+ // Make sure we start at the right indexes on the stack! Build array up
+ // to common parent of start and end. Perhaps it's too many entries, but
+ // that's far better than too few.
+ nsINode* parent;
+ nsINode* current;
+
+ mIndexes.Clear();
+ current = mCurNode;
+ if (!current) {
+ return NS_OK;
+ }
+
+ while (current != mCommonParent) {
+ parent = current->GetParentNode();
+
+ if (NS_WARN_IF(!parent)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIndexes.InsertElementAt(0, parent->IndexOf(current));
+
+ current = parent;
+ }
+
+ return NS_OK;
+}
+
+void
+nsContentIterator::MakeEmpty()
+{
+ mCurNode = nullptr;
+ mFirst = nullptr;
+ mLast = nullptr;
+ mCommonParent = nullptr;
+ mIsDone = true;
+ mIndexes.Clear();
+}
+
+nsINode*
+nsContentIterator::GetDeepFirstChild(nsINode* aRoot,
+ nsTArray<int32_t>* aIndexes)
+{
+ if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
+ return aRoot;
+ }
+ // We can't pass aRoot itself to the full GetDeepFirstChild, because that
+ // will only take nsIContent and aRoot might be a document. Pass aRoot's
+ // child, but be sure to preserve aIndexes.
+ if (aIndexes) {
+ aIndexes->AppendElement(0);
+ }
+ return GetDeepFirstChild(aRoot->GetFirstChild(), aIndexes);
+}
+
+nsIContent*
+nsContentIterator::GetDeepFirstChild(nsIContent* aRoot,
+ nsTArray<int32_t>* aIndexes)
+{
+ if (NS_WARN_IF(!aRoot)) {
+ return nullptr;
+ }
+
+ nsIContent* node = aRoot;
+ nsIContent* child = node->GetFirstChild();
+
+ while (child) {
+ if (aIndexes) {
+ // Add this node to the stack of indexes
+ aIndexes->AppendElement(0);
+ }
+ node = child;
+ child = node->GetFirstChild();
+ }
+
+ return node;
+}
+
+nsINode*
+nsContentIterator::GetDeepLastChild(nsINode* aRoot,
+ nsTArray<int32_t>* aIndexes)
+{
+ if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) {
+ return aRoot;
+ }
+ // We can't pass aRoot itself to the full GetDeepLastChild, because that will
+ // only take nsIContent and aRoot might be a document. Pass aRoot's child,
+ // but be sure to preserve aIndexes.
+ if (aIndexes) {
+ aIndexes->AppendElement(aRoot->GetChildCount() - 1);
+ }
+ return GetDeepLastChild(aRoot->GetLastChild(), aIndexes);
+}
+
+nsIContent*
+nsContentIterator::GetDeepLastChild(nsIContent* aRoot,
+ nsTArray<int32_t>* aIndexes)
+{
+ if (NS_WARN_IF(!aRoot)) {
+ return nullptr;
+ }
+
+ nsIContent* node = aRoot;
+ int32_t numChildren = node->GetChildCount();
+
+ while (numChildren) {
+ nsIContent* child = node->GetChildAt(--numChildren);
+
+ if (aIndexes) {
+ // Add this node to the stack of indexes
+ aIndexes->AppendElement(numChildren);
+ }
+ numChildren = child->GetChildCount();
+ node = child;
+ }
+
+ return node;
+}
+
+// Get the next sibling, or parent's next sibling, or grandpa's next sibling...
+nsIContent*
+nsContentIterator::GetNextSibling(nsINode* aNode,
+ nsTArray<int32_t>* aIndexes)
+{
+ if (NS_WARN_IF(!aNode)) {
+ return nullptr;
+ }
+
+ nsINode* parent = aNode->GetParentNode();
+ if (NS_WARN_IF(!parent)) {
+ return nullptr;
+ }
+
+ int32_t indx = 0;
+
+ NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(),
+ "ContentIterator stack underflow");
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ // use the last entry on the Indexes array for the current index
+ indx = (*aIndexes)[aIndexes->Length()-1];
+ } else {
+ indx = mCachedIndex;
+ }
+ NS_WARNING_ASSERTION(indx >= 0, "bad indx");
+
+ // reverify that the index of the current node hasn't changed.
+ // not super cheap, but a lot cheaper than IndexOf(), and still O(1).
+ // ignore result this time - the index may now be out of range.
+ nsIContent* sib = parent->GetChildAt(indx);
+ if (sib != aNode) {
+ // someone changed our index - find the new index the painful way
+ indx = parent->IndexOf(aNode);
+ NS_WARNING_ASSERTION(indx >= 0, "bad indx");
+ }
+
+ // indx is now canonically correct
+ if ((sib = parent->GetChildAt(++indx))) {
+ // update index cache
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ aIndexes->ElementAt(aIndexes->Length()-1) = indx;
+ } else {
+ mCachedIndex = indx;
+ }
+ } else {
+ if (parent != mCommonParent) {
+ if (aIndexes) {
+ // pop node off the stack, go up one level and return parent or fail.
+ // Don't leave the index empty, especially if we're
+ // returning nullptr. This confuses other parts of the code.
+ if (aIndexes->Length() > 1) {
+ aIndexes->RemoveElementAt(aIndexes->Length()-1);
+ }
+ }
+ }
+
+ // ok to leave cache out of date here if parent == mCommonParent?
+ sib = GetNextSibling(parent, aIndexes);
+ }
+
+ return sib;
+}
+
+// Get the prev sibling, or parent's prev sibling, or grandpa's prev sibling...
+nsIContent*
+nsContentIterator::GetPrevSibling(nsINode* aNode,
+ nsTArray<int32_t>* aIndexes)
+{
+ if (NS_WARN_IF(!aNode)) {
+ return nullptr;
+ }
+
+ nsINode* parent = aNode->GetParentNode();
+ if (NS_WARN_IF(!parent)) {
+ return nullptr;
+ }
+
+ int32_t indx = 0;
+
+ NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(),
+ "ContentIterator stack underflow");
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ // use the last entry on the Indexes array for the current index
+ indx = (*aIndexes)[aIndexes->Length()-1];
+ } else {
+ indx = mCachedIndex;
+ }
+
+ // reverify that the index of the current node hasn't changed
+ // ignore result this time - the index may now be out of range.
+ nsIContent* sib = parent->GetChildAt(indx);
+ if (sib != aNode) {
+ // someone changed our index - find the new index the painful way
+ indx = parent->IndexOf(aNode);
+ NS_WARNING_ASSERTION(indx >= 0, "bad indx");
+ }
+
+ // indx is now canonically correct
+ if (indx > 0 && (sib = parent->GetChildAt(--indx))) {
+ // update index cache
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ aIndexes->ElementAt(aIndexes->Length()-1) = indx;
+ } else {
+ mCachedIndex = indx;
+ }
+ } else if (parent != mCommonParent) {
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ // pop node off the stack, go up one level and try again.
+ aIndexes->RemoveElementAt(aIndexes->Length()-1);
+ }
+ return GetPrevSibling(parent, aIndexes);
+ }
+
+ return sib;
+}
+
+nsINode*
+nsContentIterator::NextNode(nsINode* aNode, nsTArray<int32_t>* aIndexes)
+{
+ nsINode* node = aNode;
+
+ // if we are a Pre-order iterator, use pre-order
+ if (mPre) {
+ // if it has children then next node is first child
+ if (node->HasChildren()) {
+ nsIContent* firstChild = node->GetFirstChild();
+ MOZ_ASSERT(firstChild);
+
+ // update cache
+ if (aIndexes) {
+ // push an entry on the index stack
+ aIndexes->AppendElement(0);
+ } else {
+ mCachedIndex = 0;
+ }
+
+ return firstChild;
+ }
+
+ // else next sibling is next
+ return GetNextSibling(node, aIndexes);
+ }
+
+ // 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");
+ mIsDone = true;
+ return node;
+ }
+ nsIContent* sibling = nullptr;
+ int32_t indx = 0;
+
+ // get the cached index
+ NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(),
+ "ContentIterator stack underflow");
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ // use the last entry on the Indexes array for the current index
+ indx = (*aIndexes)[aIndexes->Length()-1];
+ } else {
+ indx = mCachedIndex;
+ }
+
+ // reverify that the index of the current node hasn't changed. not super
+ // cheap, but a lot cheaper than IndexOf(), and still O(1). ignore result
+ // this time - the index may now be out of range.
+ if (indx >= 0) {
+ sibling = parent->GetChildAt(indx);
+ }
+ if (sibling != node) {
+ // someone changed our index - find the new index the painful way
+ indx = parent->IndexOf(node);
+ NS_WARNING_ASSERTION(indx >= 0, "bad indx");
+ }
+
+ // indx is now canonically correct
+ sibling = parent->GetChildAt(++indx);
+ if (sibling) {
+ // update cache
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ // replace an entry on the index stack
+ aIndexes->ElementAt(aIndexes->Length()-1) = indx;
+ } else {
+ mCachedIndex = indx;
+ }
+
+ // next node is sibling's "deep left" child
+ return GetDeepFirstChild(sibling, aIndexes);
+ }
+
+ // else it's the parent, update cache
+ if (aIndexes) {
+ // Pop an entry off the index stack. Don't leave the index empty,
+ // especially if we're returning nullptr. This confuses other parts of the
+ // code.
+ if (aIndexes->Length() > 1) {
+ aIndexes->RemoveElementAt(aIndexes->Length()-1);
+ }
+ } else {
+ // this might be wrong, but we are better off guessing
+ mCachedIndex = 0;
+ }
+
+ return parent;
+}
+
+nsINode*
+nsContentIterator::PrevNode(nsINode* aNode, nsTArray<int32_t>* aIndexes)
+{
+ nsINode* node = aNode;
+
+ // if we are a Pre-order iterator, use pre-order
+ if (mPre) {
+ nsINode* parent = node->GetParentNode();
+ if (NS_WARN_IF(!parent)) {
+ MOZ_ASSERT(parent, "The node is the root node but not the first node");
+ mIsDone = true;
+ return aNode;
+ }
+ nsIContent* sibling = nullptr;
+ int32_t indx = 0;
+
+ // get the cached index
+ NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(),
+ "ContentIterator stack underflow");
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ // use the last entry on the Indexes array for the current index
+ indx = (*aIndexes)[aIndexes->Length()-1];
+ } else {
+ indx = mCachedIndex;
+ }
+
+ // reverify that the index of the current node hasn't changed. not super
+ // cheap, but a lot cheaper than IndexOf(), and still O(1). ignore result
+ // this time - the index may now be out of range.
+ if (indx >= 0) {
+ sibling = parent->GetChildAt(indx);
+ NS_WARNING_ASSERTION(sibling, "GetChildAt returned null");
+ }
+
+ if (sibling != node) {
+ // someone changed our index - find the new index the painful way
+ indx = parent->IndexOf(node);
+ NS_WARNING_ASSERTION(indx >= 0, "bad indx");
+ }
+
+ // indx is now canonically correct
+ if (indx && (sibling = parent->GetChildAt(--indx))) {
+ // update cache
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ // replace an entry on the index stack
+ aIndexes->ElementAt(aIndexes->Length()-1) = indx;
+ } else {
+ mCachedIndex = indx;
+ }
+
+ // prev node is sibling's "deep right" child
+ return GetDeepLastChild(sibling, aIndexes);
+ }
+
+ // else it's the parent, update cache
+ if (aIndexes && !aIndexes->IsEmpty()) {
+ // pop an entry off the index stack
+ aIndexes->RemoveElementAt(aIndexes->Length()-1);
+ } else {
+ // this might be wrong, but we are better off guessing
+ mCachedIndex = 0;
+ }
+ return parent;
+ }
+
+ // post-order
+ int32_t numChildren = node->GetChildCount();
+ NS_WARNING_ASSERTION(numChildren >= 0, "no children");
+
+ // if it has children then prev node is last child
+ if (numChildren) {
+ nsIContent* lastChild = node->GetLastChild();
+ NS_WARNING_ASSERTION(lastChild, "GetLastChild returned null");
+ numChildren--;
+
+ // update cache
+ if (aIndexes) {
+ // push an entry on the index stack
+ aIndexes->AppendElement(numChildren);
+ } else {
+ mCachedIndex = numChildren;
+ }
+
+ return lastChild;
+ }
+
+ // else prev sibling is previous
+ return GetPrevSibling(node, aIndexes);
+}
+
+/******************************************************
+ * ContentIterator routines
+ ******************************************************/
+
+void
+nsContentIterator::First()
+{
+ if (mFirst) {
+ mozilla::DebugOnly<nsresult> rv = PositionAt(mFirst);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
+ }
+
+ mIsDone = mFirst == nullptr;
+}
+
+
+void
+nsContentIterator::Last()
+{
+ // Note that mLast can be nullptr if MakeEmpty() is called in Init() since
+ // at that time, Init() returns NS_OK.
+ if (!mLast) {
+ MOZ_ASSERT(mIsDone);
+ return;
+ }
+
+ mozilla::DebugOnly<nsresult> rv = PositionAt(mLast);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
+
+ mIsDone = mLast == nullptr;
+}
+
+
+void
+nsContentIterator::Next()
+{
+ if (mIsDone || NS_WARN_IF(!mCurNode)) {
+ return;
+ }
+
+ if (mCurNode == mLast) {
+ mIsDone = true;
+ return;
+ }
+
+ mCurNode = NextNode(mCurNode, &mIndexes);
+}
+
+
+void
+nsContentIterator::Prev()
+{
+ if (NS_WARN_IF(mIsDone) || NS_WARN_IF(!mCurNode)) {
+ return;
+ }
+
+ if (mCurNode == mFirst) {
+ mIsDone = true;
+ return;
+ }
+
+ mCurNode = PrevNode(mCurNode, &mIndexes);
+}
+
+
+bool
+nsContentIterator::IsDone()
+{
+ return mIsDone;
+}
+
+
+// Keeping arrays of indexes for the stack of nodes makes PositionAt
+// interesting...
+nsresult
+nsContentIterator::PositionAt(nsINode* aCurNode)
+{
+ if (NS_WARN_IF(!aCurNode)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsINode* newCurNode = aCurNode;
+ nsINode* tempNode = mCurNode;
+
+ mCurNode = aCurNode;
+ // take an early out if this doesn't actually change the position
+ if (mCurNode == tempNode) {
+ mIsDone = false; // paranoia
+ return NS_OK;
+ }
+
+ // Check to see if the node falls within the traversal range.
+
+ nsINode* firstNode = mFirst;
+ nsINode* lastNode = mLast;
+ int32_t firstOffset = 0, lastOffset = 0;
+
+ if (firstNode && lastNode) {
+ if (mPre) {
+ firstNode = NodeToParentOffset(mFirst, &firstOffset);
+ NS_WARNING_ASSERTION(firstNode, "NodeToParentOffset returned null");
+ NS_WARNING_ASSERTION(firstOffset >= 0, "bad firstOffset");
+
+ if (lastNode->GetChildCount()) {
+ lastOffset = 0;
+ } else {
+ lastNode = NodeToParentOffset(mLast, &lastOffset);
+ NS_WARNING_ASSERTION(lastNode, "NodeToParentOffset returned null");
+ NS_WARNING_ASSERTION(lastOffset >= 0, "bad lastOffset");
+ ++lastOffset;
+ }
+ } else {
+ uint32_t numChildren = firstNode->GetChildCount();
+
+ if (numChildren) {
+ firstOffset = numChildren;
+ NS_WARNING_ASSERTION(firstOffset >= 0, "bad firstOffset");
+ } else {
+ firstNode = NodeToParentOffset(mFirst, &firstOffset);
+ NS_WARNING_ASSERTION(firstNode, "NodeToParentOffset returned null");
+ NS_WARNING_ASSERTION(firstOffset >= 0, "bad firstOffset");
+ }
+
+ lastNode = NodeToParentOffset(mLast, &lastOffset);
+ NS_WARNING_ASSERTION(lastNode, "NodeToParentOffset returned null");
+ NS_WARNING_ASSERTION(lastOffset >= 0, "bad lastOffset");
+ ++lastOffset;
+ }
+ }
+
+ // 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(!firstNode) || NS_WARN_IF(!lastNode) ||
+ NS_WARN_IF(!NodeIsInTraversalRange(mCurNode, mPre,
+ firstNode, firstOffset,
+ lastNode, lastOffset)))) {
+ mIsDone = true;
+ return NS_ERROR_FAILURE;
+ }
+
+ // We can be at ANY node in the sequence. Need to regenerate the array of
+ // indexes back to the root or common parent!
+ AutoTArray<nsINode*, 8> oldParentStack;
+ AutoTArray<int32_t, 8> newIndexes;
+
+ // Get a list of the parents up to the root, then compare the new node with
+ // entries in that array until we find a match (lowest common ancestor). If
+ // no match, use IndexOf, take the parent, and repeat. This avoids using
+ // IndexOf() N times on possibly large arrays. We still end up doing it a
+ // fair bit. It's better to use Clone() if possible.
+
+ // we know the depth we're down (though we may not have started at the top).
+ oldParentStack.SetCapacity(mIndexes.Length() + 1);
+
+ // We want to loop mIndexes.Length() + 1 times here, because we want to make
+ // sure we include mCommonParent in the oldParentStack, for use in the next
+ // for loop, and mIndexes only has entries for nodes from tempNode up through
+ // an ancestor of tempNode that's a child of mCommonParent.
+ for (int32_t i = mIndexes.Length() + 1; i > 0 && tempNode; i--) {
+ // Insert at head since we're walking up
+ oldParentStack.InsertElementAt(0, tempNode);
+
+ nsINode* parent = tempNode->GetParentNode();
+
+ if (NS_WARN_IF(!parent)) {
+ // this node has no parent, and thus no index
+ break;
+ }
+
+ if (parent == mCurNode) {
+ // The position was moved to a parent of the current position. All we
+ // need to do is drop some indexes. Shortcut here.
+ mIndexes.RemoveElementsAt(mIndexes.Length() - oldParentStack.Length(),
+ oldParentStack.Length());
+ mIsDone = false;
+ return NS_OK;
+ }
+ tempNode = parent;
+ }
+
+ // Ok. We have the array of old parents. Look for a match.
+ while (newCurNode) {
+ nsINode* parent = newCurNode->GetParentNode();
+
+ if (NS_WARN_IF(!parent)) {
+ // this node has no parent, and thus no index
+ break;
+ }
+
+ int32_t indx = parent->IndexOf(newCurNode);
+ NS_WARNING_ASSERTION(indx >= 0, "bad indx");
+
+ // insert at the head!
+ newIndexes.InsertElementAt(0, indx);
+
+ // look to see if the parent is in the stack
+ indx = oldParentStack.IndexOf(parent);
+ if (indx >= 0) {
+ // ok, the parent IS on the old stack! Rework things. We want
+ // newIndexes to replace all nodes equal to or below the match. Note
+ // that index oldParentStack.Length() - 1 is the last node, which is one
+ // BELOW the last index in the mIndexes stack. In other words, we want
+ // to remove elements starting at index (indx + 1).
+ int32_t numToDrop = oldParentStack.Length() - (1 + indx);
+ if (numToDrop > 0) {
+ mIndexes.RemoveElementsAt(mIndexes.Length() - numToDrop, numToDrop);
+ }
+ mIndexes.AppendElements(newIndexes);
+
+ break;
+ }
+ newCurNode = parent;
+ }
+
+ // phew!
+
+ mIsDone = false;
+ return NS_OK;
+}
+
+nsINode*
+nsContentIterator::GetCurrentNode()
+{
+ if (mIsDone) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(mCurNode, "Null current node in an iterator that's not done!");
+
+ return mCurNode;
+}
+
+
+
+
+
+/*====================================================================================*/
+/*====================================================================================*/
+
+
+
+
+
+
+/******************************************************
+ * nsContentSubtreeIterator
+ ******************************************************/
+
+
+/*
+ * A simple iterator class for traversing the content in "top subtree" order
+ */
+class nsContentSubtreeIterator : public nsContentIterator
+{
+public:
+ nsContentSubtreeIterator() : nsContentIterator(false) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsContentSubtreeIterator, nsContentIterator)
+
+ // nsContentIterator overrides ------------------------------
+
+ virtual nsresult Init(nsINode* aRoot) override;
+
+ virtual nsresult Init(nsIDOMRange* aRange) override;
+
+ virtual void Next() override;
+
+ virtual void Prev() override;
+
+ virtual nsresult PositionAt(nsINode* aCurNode) override;
+
+ // Must override these because we don't do PositionAt
+ virtual void First() override;
+
+ // Must override these because we don't do PositionAt
+ virtual void Last() override;
+
+protected:
+ virtual ~nsContentSubtreeIterator() {}
+
+ // 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);
+
+ // no copy's or assigns FIX ME
+ nsContentSubtreeIterator(const nsContentSubtreeIterator&);
+ nsContentSubtreeIterator& operator=(const nsContentSubtreeIterator&);
+
+ virtual void LastRelease() override;
+
+ RefPtr<nsRange> mRange;
+
+ // these arrays all typically are used and have elements
+ AutoTArray<nsIContent*, 8> mEndNodes;
+ AutoTArray<int32_t, 8> mEndOffsets;
+};
+
+NS_IMPL_ADDREF_INHERITED(nsContentSubtreeIterator, nsContentIterator)
+NS_IMPL_RELEASE_INHERITED(nsContentSubtreeIterator, nsContentIterator)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsContentSubtreeIterator)
+NS_INTERFACE_MAP_END_INHERITING(nsContentIterator)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsContentSubtreeIterator, nsContentIterator,
+ mRange)
+
+void
+nsContentSubtreeIterator::LastRelease()
+{
+ mRange = nullptr;
+ nsContentIterator::LastRelease();
+}
+
+/******************************************************
+ * repository cruft
+ ******************************************************/
+
+already_AddRefed<nsIContentIterator>
+NS_NewContentSubtreeIterator()
+{
+ nsCOMPtr<nsIContentIterator> iter = new nsContentSubtreeIterator();
+ return iter.forget();
+}
+
+
+
+/******************************************************
+ * Init routines
+ ******************************************************/
+
+
+nsresult
+nsContentSubtreeIterator::Init(nsINode* aRoot)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+nsresult
+nsContentSubtreeIterator::Init(nsIDOMRange* aRange)
+{
+ MOZ_ASSERT(aRange);
+
+ mIsDone = false;
+
+ mRange = static_cast<nsRange*>(aRange);
+
+ // get the start node and offset, convert to nsINode
+ mCommonParent = mRange->GetCommonAncestor();
+ nsINode* startParent = mRange->GetStartParent();
+ int32_t startOffset = mRange->StartOffset();
+ nsINode* endParent = mRange->GetEndParent();
+ int32_t endOffset = mRange->EndOffset();
+ MOZ_ASSERT(mCommonParent && startParent && endParent);
+ // Bug 767169
+ MOZ_ASSERT(uint32_t(startOffset) <= startParent->Length() &&
+ uint32_t(endOffset) <= endParent->Length());
+
+ // short circuit when start node == end node
+ if (startParent == endParent) {
+ nsINode* child = startParent->GetFirstChild();
+
+ if (!child || startOffset == endOffset) {
+ // Text node, empty container, or collapsed
+ MakeEmpty();
+ return NS_OK;
+ }
+ }
+
+ // cache ancestors
+ nsContentUtils::GetAncestorsAndOffsets(endParent->AsDOMNode(), endOffset,
+ &mEndNodes, &mEndOffsets);
+
+ nsIContent* firstCandidate = nullptr;
+ nsIContent* lastCandidate = nullptr;
+
+ // find first node in range
+ int32_t offset = mRange->StartOffset();
+
+ nsINode* node = nullptr;
+ if (!startParent->GetChildCount()) {
+ // no children, start at the node itself
+ node = startParent;
+ } else {
+ nsIContent* child = startParent->GetChildAt(offset);
+ if (!child) {
+ // offset after last child
+ node = startParent;
+ } else {
+ firstCandidate = child;
+ }
+ }
+
+ if (!firstCandidate) {
+ // then firstCandidate is next node after node
+ firstCandidate = GetNextSibling(node);
+
+ if (!firstCandidate) {
+ MakeEmpty();
+ return NS_OK;
+ }
+ }
+
+ firstCandidate = GetDeepFirstChild(firstCandidate);
+
+ // confirm that this first possible contained node is indeed contained. Else
+ // we have a range that does not fully contain any node.
+
+ bool nodeBefore, nodeAfter;
+ MOZ_ALWAYS_SUCCEEDS(
+ nsRange::CompareNodeToRange(firstCandidate, mRange, &nodeBefore, &nodeAfter));
+
+ if (nodeBefore || nodeAfter) {
+ MakeEmpty();
+ return NS_OK;
+ }
+
+ // 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.
+ mFirst = GetTopAncestorInRange(firstCandidate);
+
+ // now to find the last node
+ offset = mRange->EndOffset();
+ int32_t numChildren = endParent->GetChildCount();
+
+ if (offset > numChildren) {
+ // Can happen for text nodes
+ offset = numChildren;
+ }
+ if (!offset || !numChildren) {
+ node = endParent;
+ } else {
+ lastCandidate = endParent->GetChildAt(--offset);
+ NS_ASSERTION(lastCandidate,
+ "tree traversal trouble in nsContentSubtreeIterator::Init");
+ }
+
+ if (!lastCandidate) {
+ // then lastCandidate is prev node before node
+ lastCandidate = GetPrevSibling(node);
+ }
+
+ if (!lastCandidate) {
+ MakeEmpty();
+ return NS_OK;
+ }
+
+ lastCandidate = GetDeepLastChild(lastCandidate);
+
+ // confirm that this last possible contained node is indeed contained. Else
+ // we have a range that does not fully contain any node.
+
+ MOZ_ALWAYS_SUCCEEDS(
+ nsRange::CompareNodeToRange(lastCandidate, mRange, &nodeBefore, &nodeAfter));
+
+ if (nodeBefore || nodeAfter) {
+ MakeEmpty();
+ return NS_OK;
+ }
+
+ // 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.
+ mLast = GetTopAncestorInRange(lastCandidate);
+
+ mCurNode = mFirst;
+
+ return NS_OK;
+}
+
+/****************************************************************
+ * nsContentSubtreeIterator overrides of ContentIterator routines
+ ****************************************************************/
+
+// we can't call PositionAt in a subtree iterator...
+void
+nsContentSubtreeIterator::First()
+{
+ mIsDone = mFirst == nullptr;
+
+ mCurNode = mFirst;
+}
+
+// we can't call PositionAt in a subtree iterator...
+void
+nsContentSubtreeIterator::Last()
+{
+ mIsDone = mLast == nullptr;
+
+ mCurNode = mLast;
+}
+
+
+void
+nsContentSubtreeIterator::Next()
+{
+ if (mIsDone || !mCurNode) {
+ return;
+ }
+
+ if (mCurNode == mLast) {
+ mIsDone = true;
+ return;
+ }
+
+ nsINode* nextNode = GetNextSibling(mCurNode);
+ NS_ASSERTION(nextNode, "No next sibling!?! This could mean deadlock!");
+
+ int32_t i = mEndNodes.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 = mEndNodes.IndexOf(nextNode);
+ }
+
+ mCurNode = nextNode;
+
+ // This shouldn't be needed, but since our selection code can put us
+ // in a situation where mLast is in generated content, we need this
+ // to stop the iterator when we've walked past past the last node!
+ mIsDone = mCurNode == nullptr;
+}
+
+
+void
+nsContentSubtreeIterator::Prev()
+{
+ // Prev should be optimized to use the mStartNodes, just as Next
+ // uses mEndNodes.
+ if (mIsDone || !mCurNode) {
+ return;
+ }
+
+ if (mCurNode == mFirst) {
+ mIsDone = true;
+ return;
+ }
+
+ // If any of these function calls return null, so will all succeeding ones,
+ // so mCurNode will wind up set to null.
+ nsINode* prevNode = GetDeepFirstChild(mCurNode);
+
+ prevNode = PrevNode(prevNode);
+
+ prevNode = GetDeepLastChild(prevNode);
+
+ mCurNode = GetTopAncestorInRange(prevNode);
+
+ // This shouldn't be needed, but since our selection code can put us
+ // in a situation where mFirst is in generated content, we need this
+ // to stop the iterator when we've walked past past the first node!
+ mIsDone = mCurNode == nullptr;
+}
+
+
+nsresult
+nsContentSubtreeIterator::PositionAt(nsINode* aCurNode)
+{
+ NS_ERROR("Not implemented!");
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+ * nsContentSubtreeIterator helper routines
+ ****************************************************************/
+
+nsIContent*
+nsContentSubtreeIterator::GetTopAncestorInRange(nsINode* aNode)
+{
+ 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
+ bool nodeBefore, nodeAfter;
+ nsresult res = nsRange::CompareNodeToRange(aNode, mRange,
+ &nodeBefore, &nodeAfter);
+ NS_ASSERTION(NS_SUCCEEDED(res) && !nodeBefore && !nodeAfter,
+ "aNode isn't in mRange, or something else weird happened");
+ if (NS_FAILED(res) || nodeBefore || nodeAfter) {
+ 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;
+ }
+ MOZ_ALWAYS_SUCCEEDS(
+ nsRange::CompareNodeToRange(parent, mRange, &nodeBefore, &nodeAfter));
+
+ if (nodeBefore || nodeAfter) {
+ return content;
+ }
+ content = parent;
+ }
+
+ MOZ_CRASH("This should only be possible if aNode was null");
+}
diff --git a/dom/base/nsContentList.cpp b/dom/base/nsContentList.cpp
new file mode 100644
index 000000000..09e949009
--- /dev/null
+++ b/dom/base/nsContentList.cpp
@@ -0,0 +1,1077 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMHTMLDocument, etc).
+ */
+
+#include "nsContentList.h"
+#include "nsIContent.h"
+#include "nsIDOMNode.h"
+#include "nsIDocument.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"
+
+// Form related includes
+#include "nsIDOMHTMLFormElement.h"
+
+#include "PLDHashTable.h"
+
+#ifdef DEBUG_CONTENT_LIST
+#include "nsIContentIterator.h"
+#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()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_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_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsBaseContentList)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList)
+ if (nsCCUncollectableMarker::sGeneration && tmp->IsBlack()) {
+ 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->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsBaseContentList)
+ return nsCCUncollectableMarker::sGeneration && tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+#define NS_CONTENT_LIST_INTERFACES(_class) \
+ NS_INTERFACE_TABLE_ENTRY(_class, nsINodeList) \
+ NS_INTERFACE_TABLE_ENTRY(_class, nsIDOMNodeList)
+
+// QueryInterface implementation for nsBaseContentList
+NS_INTERFACE_TABLE_HEAD(nsBaseContentList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(nsBaseContentList, nsINodeList, nsIDOMNodeList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsBaseContentList)
+NS_INTERFACE_MAP_END
+
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBaseContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBaseContentList)
+
+
+NS_IMETHODIMP
+nsBaseContentList::GetLength(uint32_t* aLength)
+{
+ *aLength = mElements.Length();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsISupports *tmp = Item(aIndex);
+
+ if (!tmp) {
+ *aReturn = nullptr;
+
+ return NS_OK;
+ }
+
+ return CallQueryInterface(tmp, aReturn);
+}
+
+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);
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSimpleContentList, nsBaseContentList,
+ mRoot)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(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 NodeListBinding::Wrap(cx, this, aGivenProto);
+}
+
+// Hashtable for storing nsContentLists
+static PLDHashTable* gContentListHashTable;
+
+#define RECENTLY_USED_CONTENT_LIST_CACHE_SIZE 31
+static nsContentList*
+ sRecentlyUsedContentLists[RECENTLY_USED_CONTENT_LIST_CACHE_SIZE] = {};
+
+static MOZ_ALWAYS_INLINE uint32_t
+RecentlyUsedCacheIndex(const nsContentListKey& aKey)
+{
+ return aKey.GetHash() % RECENTLY_USED_CONTENT_LIST_CACHE_SIZE;
+}
+
+struct ContentListHashEntry : public PLDHashEntryHdr
+{
+ nsContentList* mContentList;
+};
+
+static PLDHashNumber
+ContentListHashtableHashKey(const void *key)
+{
+ const nsContentListKey* list = static_cast<const nsContentListKey *>(key);
+ return list->GetHash();
+}
+
+static bool
+ContentListHashtableMatchEntry(const PLDHashEntryHdr *entry, const void *key)
+{
+ const ContentListHashEntry *e =
+ static_cast<const ContentListHashEntry *>(entry);
+ const nsContentList* list = e->mContentList;
+ const nsContentListKey* ourKey = static_cast<const nsContentListKey *>(key);
+
+ return list->MatchesKey(*ourKey);
+}
+
+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());
+ uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(hashKey);
+ nsContentList* cachedList = sRecentlyUsedContentLists[recentlyUsedCacheIndex];
+ if (cachedList && cachedList->MatchesKey(hashKey)) {
+ list = cachedList;
+ return list.forget();
+ }
+
+ static const PLDHashTableOps hash_table_ops =
+ {
+ ContentListHashtableHashKey,
+ ContentListHashtableMatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub
+ };
+
+ // Initialize the hashtable if needed.
+ if (!gContentListHashTable) {
+ gContentListHashTable =
+ new PLDHashTable(&hash_table_ops, sizeof(ContentListHashEntry));
+ }
+
+ // First we look in our hashtable. Then we create a content list if needed
+ auto entry = static_cast<ContentListHashEntry*>
+ (gContentListHashTable->Add(&hashKey, fallible));
+ if (entry)
+ list = entry->mContentList;
+
+ if (!list) {
+ // We need to create a ContentList and add it to our new entry, if
+ // we have an entry
+ nsCOMPtr<nsIAtom> xmlAtom = NS_Atomize(aTagname);
+ nsCOMPtr<nsIAtom> 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->mContentList = list;
+ }
+ }
+
+ sRecentlyUsedContentLists[recentlyUsedCacheIndex] = list;
+ return list.forget();
+}
+
+#ifdef DEBUG
+const nsCacheableFuncStringContentList::ContentListType
+ nsCacheableFuncStringNodeList::sType = nsCacheableFuncStringContentList::eNodeList;
+const nsCacheableFuncStringContentList::ContentListType
+ nsCacheableFuncStringHTMLCollection::sType = nsCacheableFuncStringContentList::eHTMLCollection;
+#endif
+
+JSObject*
+nsCacheableFuncStringNodeList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NodeListBinding::Wrap(cx, this, aGivenProto);
+}
+
+
+JSObject*
+nsCacheableFuncStringHTMLCollection::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLCollectionBinding::Wrap(cx, this, aGivenProto);
+}
+
+// Hashtable for storing nsCacheableFuncStringContentList
+static PLDHashTable* gFuncStringContentListHashTable;
+
+struct FuncStringContentListHashEntry : public PLDHashEntryHdr
+{
+ nsCacheableFuncStringContentList* mContentList;
+};
+
+static PLDHashNumber
+FuncStringContentListHashtableHashKey(const void *key)
+{
+ const nsFuncStringCacheKey* funcStringKey =
+ static_cast<const nsFuncStringCacheKey *>(key);
+ return funcStringKey->GetHash();
+}
+
+static bool
+FuncStringContentListHashtableMatchEntry(const PLDHashEntryHdr *entry,
+ const void *key)
+{
+ const FuncStringContentListHashEntry *e =
+ static_cast<const FuncStringContentListHashEntry *>(entry);
+ const nsFuncStringCacheKey* ourKey =
+ static_cast<const nsFuncStringCacheKey *>(key);
+
+ return e->mContentList->Equals(ourKey);
+}
+
+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;
+
+ static const PLDHashTableOps hash_table_ops =
+ {
+ FuncStringContentListHashtableHashKey,
+ FuncStringContentListHashtableMatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub
+ };
+
+ // Initialize the hashtable if needed.
+ if (!gFuncStringContentListHashTable) {
+ gFuncStringContentListHashTable =
+ new PLDHashTable(&hash_table_ops, sizeof(FuncStringContentListHashEntry));
+ }
+
+ FuncStringContentListHashEntry *entry = nullptr;
+ // First we look in our hashtable. Then we create a content list if needed
+ if (gFuncStringContentListHashTable) {
+ nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString);
+
+ entry = static_cast<FuncStringContentListHashEntry*>
+ (gFuncStringContentListHashTable->Add(&hashKey, fallible));
+ if (entry) {
+ list = entry->mContentList;
+#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->mContentList = list;
+ }
+ }
+
+ // Don't cache these lists globally
+
+ return list.forget();
+}
+
+already_AddRefed<nsContentList>
+NS_GetFuncStringNodeList(nsINode* aRootNode,
+ nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString)
+{
+ return GetFuncStringContentList<nsCacheableFuncStringNodeList>(aRootNode,
+ aFunc,
+ aDestroyFunc,
+ aDataAllocator,
+ aString);
+}
+
+already_AddRefed<nsContentList>
+NS_GetFuncStringHTMLCollection(nsINode* aRootNode,
+ nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString)
+{
+ return GetFuncStringContentList<nsCacheableFuncStringHTMLCollection>(aRootNode,
+ aFunc,
+ aDestroyFunc,
+ aDataAllocator,
+ aString);
+}
+
+// nsContentList implementation
+
+nsContentList::nsContentList(nsINode* aRootNode,
+ int32_t aMatchNameSpaceId,
+ nsIAtom* aHTMLMatchAtom,
+ nsIAtom* aXMLMatchAtom,
+ bool aDeep)
+ : nsBaseContentList(),
+ mRootNode(aRootNode),
+ mMatchNameSpaceId(aMatchNameSpaceId),
+ mHTMLMatchAtom(aHTMLMatchAtom),
+ mXMLMatchAtom(aXMLMatchAtom),
+ mFunc(nullptr),
+ mDestroyFunc(nullptr),
+ mData(nullptr),
+ mState(LIST_DIRTY),
+ mDeep(aDeep),
+ mFuncMayDependOnAttr(false),
+ mIsHTMLDocument(aRootNode->OwnerDoc()->IsHTMLDocument())
+{
+ 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;
+ }
+ 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.
+ nsIDocument* doc = mRootNode->GetUncomposedDoc();
+ mFlushesNeeded = doc && !doc->IsHTMLDocument();
+}
+
+nsContentList::nsContentList(nsINode* aRootNode,
+ nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ void* aData,
+ bool aDeep,
+ nsIAtom* aMatchAtom,
+ int32_t aMatchNameSpaceId,
+ bool aFuncMayDependOnAttr)
+ : nsBaseContentList(),
+ mRootNode(aRootNode),
+ mMatchNameSpaceId(aMatchNameSpaceId),
+ mHTMLMatchAtom(aMatchAtom),
+ mXMLMatchAtom(aMatchAtom),
+ mFunc(aFunc),
+ mDestroyFunc(aDestroyFunc),
+ mData(aData),
+ mState(LIST_DIRTY),
+ mMatchAll(false),
+ mDeep(aDeep),
+ mFuncMayDependOnAttr(aFuncMayDependOnAttr),
+ mIsHTMLDocument(false)
+{
+ NS_ASSERTION(mRootNode, "Must have root");
+ 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.
+ nsIDocument* doc = mRootNode->GetUncomposedDoc();
+ mFlushesNeeded = doc && !doc->IsHTMLDocument();
+}
+
+nsContentList::~nsContentList()
+{
+ RemoveFromHashtable();
+ if (mRootNode) {
+ mRootNode->RemoveMutationObserver(this);
+ }
+
+ if (mDestroyFunc) {
+ // Clean up mData
+ (*mDestroyFunc)(mData);
+ }
+}
+
+JSObject*
+nsContentList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLCollectionBinding::Wrap(cx, this, aGivenProto);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsContentList, nsBaseContentList,
+ nsIHTMLCollection, nsIDOMHTMLCollection,
+ 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
+ nsIDocument* doc = mRootNode->GetUncomposedDoc();
+ if (doc) {
+ // Flush pending content changes Bug 4891.
+ doc->FlushPendingNotifications(Flush_ContentAndNotify);
+ }
+ }
+
+ if (mState != LIST_UP_TO_DATE)
+ PopulateSelf(std::min(aIndex, UINT32_MAX - 1) + 1);
+
+ ASSERT_IN_SYNC;
+ NS_ASSERTION(!mRootNode || mState != LIST_DIRTY,
+ "PopulateSelf left the list in a dirty (useless) state!");
+
+ return mElements.SafeElementAt(aIndex);
+}
+
+Element*
+nsContentList::NamedItem(const nsAString& aName, bool aDoFlush)
+{
+ if (aName.IsEmpty()) {
+ return nullptr;
+ }
+
+ BringSelfUpToDate(aDoFlush);
+
+ uint32_t i, count = mElements.Length();
+
+ // Typically IDs and names are atomized
+ nsCOMPtr<nsIAtom> name = NS_Atomize(aName);
+ NS_ENSURE_TRUE(name, nullptr);
+
+ for (i = 0; i < count; i++) {
+ nsIContent *content = mElements[i];
+ // XXX Should this pass eIgnoreCase?
+ if (content &&
+ ((content->IsHTMLElement() &&
+ content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ name, eCaseMatters)) ||
+ content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
+ name, eCaseMatters))) {
+ return content->AsElement();
+ }
+ }
+
+ return nullptr;
+}
+
+void
+nsContentList::GetSupportedNames(nsTArray<nsString>& aNames)
+{
+ BringSelfUpToDate(true);
+
+ AutoTArray<nsIAtom*, 8> atoms;
+ for (uint32_t i = 0; i < mElements.Length(); ++i) {
+ nsIContent *content = mElements.ElementAt(i);
+ if (content->HasID()) {
+ nsIAtom* id = content->GetID();
+ MOZ_ASSERT(id != nsGkAtoms::_empty,
+ "Empty ids don't get atomized");
+ if (!atoms.Contains(id)) {
+ atoms.AppendElement(id);
+ }
+ }
+
+ nsGenericHTMLElement* el = nsGenericHTMLElement::FromContent(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) {
+ nsIAtom* 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(const 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();
+}
+
+NS_IMETHODIMP
+nsContentList::GetLength(uint32_t* aLength)
+{
+ *aLength = Length(true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsINode* node = Item(aIndex);
+
+ if (node) {
+ return CallQueryInterface(node, aReturn);
+ }
+
+ *aReturn = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentList::NamedItem(const nsAString& aName, nsIDOMNode** aReturn)
+{
+ nsIContent *content = NamedItem(aName, true);
+
+ if (content) {
+ return CallQueryInterface(content, aReturn);
+ }
+
+ *aReturn = nullptr;
+
+ return NS_OK;
+}
+
+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(nsIDocument *aDocument, Element* aElement,
+ int32_t aNameSpaceID, nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ NS_PRECONDITION(aElement, "Must have a content node to work with");
+
+ if (!mFunc || !mFuncMayDependOnAttr || mState == LIST_DIRTY ||
+ !MayContainRelevantNodes(aElement->GetParentNode()) ||
+ !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
+ // Either we're already dirty or this notification doesn't affect
+ // whether we might match aElement.
+ 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.
+ mElements.RemoveElement(aElement);
+ }
+}
+
+void
+nsContentList::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ NS_PRECONDITION(aContainer, "Can't get at the new content if no container!");
+
+ /*
+ * If the state is LIST_DIRTY then we have no useful information in our list
+ * and we want to put off doing work as much as possible.
+ *
+ * Also, if aContainer 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 == LIST_DIRTY ||
+ !nsContentUtils::IsInSameAnonymousTree(mRootNode, aContainer) ||
+ !MayContainRelevantNodes(aContainer) ||
+ (!aFirstNewContent->HasChildren() &&
+ !aFirstNewContent->GetNextSibling() &&
+ !MatchSelf(aFirstNewContent))) {
+ return;
+ }
+
+ /*
+ * We want to handle the case of ContentAppended by sometimes
+ * appending the content to our list, not just setting state to
+ * LIST_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.
+ */
+
+ int32_t count = aContainer->GetChildCount();
+
+ if (count > 0) {
+ uint32_t ourCount = mElements.Length();
+ bool appendToList = false;
+ if (ourCount == 0) {
+ appendToList = true;
+ } else {
+ nsIContent* ourLastContent = mElements[ourCount - 1];
+ /*
+ * We want to append instead of invalidating if the first thing
+ * that got appended comes after ourLastContent.
+ */
+ if (nsContentUtils::PositionIsBefore(ourLastContent, aFirstNewContent)) {
+ appendToList = true;
+ }
+ }
+
+
+ if (!appendToList) {
+ // 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 == LIST_LAZY) // be 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(aContainer)) {
+ if (cur->IsElement() && Match(cur->AsElement())) {
+ mElements.AppendElement(cur);
+ }
+ }
+ } else {
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ if (cur->IsElement() && Match(cur->AsElement())) {
+ mElements.AppendElement(cur);
+ }
+ }
+ }
+
+ ASSERT_IN_SYNC;
+ }
+}
+
+void
+nsContentList::ContentInserted(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ // Note that aContainer can be null here if we are inserting into
+ // the document itself; any attempted optimizations to this method
+ // should deal with that.
+ if (mState != LIST_DIRTY &&
+ MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
+ MatchSelf(aChild)) {
+ SetDirty();
+ }
+
+ ASSERT_IN_SYNC;
+}
+
+void
+nsContentList::ContentRemoved(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ // Note that aContainer can be null here if we are removing from
+ // the document itself; any attempted optimizations to this method
+ // should deal with that.
+ if (mState != LIST_DIRTY &&
+ MayContainRelevantNodes(NODE_FROM(aContainer, aDocument)) &&
+ 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)
+{
+ NS_PRECONDITION(aContent, "Can't match null stuff, you know");
+ NS_PRECONDITION(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)
+{
+ if (!mRootNode) {
+ return;
+ }
+
+ ASSERT_IN_SYNC;
+
+ uint32_t count = mElements.Length();
+ NS_ASSERTION(mState != LIST_DIRTY || count == 0,
+ "Reset() not called when setting state to LIST_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] : 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 = LIST_UP_TO_DATE;
+ else
+ mState = LIST_LAZY;
+
+ ASSERT_IN_SYNC;
+}
+
+void
+nsContentList::RemoveFromHashtable()
+{
+ if (mFunc) {
+ // This can't be in the table anyway
+ return;
+ }
+
+ nsDependentAtomString str(mXMLMatchAtom);
+ nsContentListKey key(mRootNode, mMatchNameSpaceId, str, mIsHTMLDocument);
+ uint32_t recentlyUsedCacheIndex = RecentlyUsedCacheIndex(key);
+ if (sRecentlyUsedContentLists[recentlyUsedCacheIndex] == this) {
+ sRecentlyUsedContentLists[recentlyUsedCacheIndex] = nullptr;
+ }
+
+ if (!gContentListHashTable)
+ return;
+
+ gContentListHashTable->Remove(&key);
+
+ if (gContentListHashTable->EntryCount() == 0) {
+ delete gContentListHashTable;
+ gContentListHashTable = nullptr;
+ }
+}
+
+void
+nsContentList::BringSelfUpToDate(bool aDoFlush)
+{
+ if (mRootNode && aDoFlush && mFlushesNeeded) {
+ // XXX sXBL/XBL2 issue
+ nsIDocument* doc = mRootNode->GetUncomposedDoc();
+ if (doc) {
+ // Flush pending content changes Bug 4891.
+ doc->FlushPendingNotifications(Flush_ContentAndNotify);
+ }
+ }
+
+ if (mState != LIST_UP_TO_DATE)
+ PopulateSelf(uint32_t(-1));
+
+ ASSERT_IN_SYNC;
+ NS_ASSERTION(!mRootNode || mState == LIST_UP_TO_DATE,
+ "PopulateSelf dod not bring content list up to date!");
+}
+
+nsCacheableFuncStringContentList::~nsCacheableFuncStringContentList()
+{
+ RemoveFromFuncStringHashtable();
+}
+
+void
+nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable()
+{
+ if (!gFuncStringContentListHashTable) {
+ return;
+ }
+
+ nsFuncStringCacheKey key(mRootNode, mFunc, mString);
+ gFuncStringContentListHashTable->Remove(&key);
+
+ if (gFuncStringContentListHashTable->EntryCount() == 0) {
+ delete gFuncStringContentListHashTable;
+ gFuncStringContentListHashTable = nullptr;
+ }
+}
+
+#ifdef DEBUG_CONTENT_LIST
+void
+nsContentList::AssertInSync()
+{
+ if (mState == LIST_DIRTY) {
+ return;
+ }
+
+ if (!mRootNode) {
+ NS_ASSERTION(mElements.Length() == 0 && mState == LIST_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;
+ if (mRootNode->IsNodeOfType(nsINode::eDOCUMENT)) {
+ root = static_cast<nsIDocument*>(mRootNode)->GetRootElement();
+ }
+ else {
+ root = static_cast<nsIContent*>(mRootNode);
+ }
+
+ nsCOMPtr<nsIContentIterator> iter;
+ if (mDeep) {
+ iter = NS_NewPreContentIterator();
+ iter->Init(root);
+ iter->First();
+ }
+
+ uint32_t cnt = 0, index = 0;
+ while (true) {
+ if (cnt == mElements.Length() && mState == LIST_LAZY) {
+ break;
+ }
+
+ nsIContent *cur = mDeep ? iter->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) {
+ iter->Next();
+ }
+ }
+
+ NS_ASSERTION(cnt == mElements.Length(), "Too few elements");
+}
+#endif
diff --git a/dom/base/nsContentList.h b/dom/base/nsContentList.h
new file mode 100644
index 000000000..3878074b2
--- /dev/null
+++ b/dom/base/nsContentList.h
@@ -0,0 +1,587 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMHTMLDocument, 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 "nsIDOMNodeList.h"
+#include "nsINodeList.h"
+#include "nsStubMutationObserver.h"
+#include "nsIAtom.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsNameSpaceManager.h"
+#include "nsWrapperCache.h"
+#include "nsHashKeys.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+
+class nsBaseContentList : public nsINodeList
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsIDOMNodeList
+ NS_DECL_NSIDOMNODELIST
+
+ // nsINodeList
+ virtual int32_t IndexOf(nsIContent* aContent) override;
+ virtual nsIContent* Item(uint32_t aIndex) override;
+
+ uint32_t Length() const {
+ return mElements.Length();
+ }
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(nsBaseContentList)
+
+ void AppendElement(nsIContent *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);
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+ override = 0;
+
+ void SetCapacity(uint32_t aCapacity)
+ {
+ mElements.SetCapacity(aCapacity);
+ }
+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()
+ {
+ }
+
+ nsTArray< nsCOMPtr<nsIContent> > 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)
+
+ virtual nsINode* GetParentObject() override
+ {
+ return mRoot;
+ }
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ virtual ~nsSimpleContentList() {}
+
+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 nsIDocument.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)
+ : mRootNode(aContentListKey.mRootNode),
+ mMatchNameSpaceId(aContentListKey.mMatchNameSpaceId),
+ mTagname(aContentListKey.mTagname),
+ mIsHTMLDocument(aContentListKey.mIsHTMLDocument),
+ mHash(aContentListKey.mHash)
+ {
+ }
+
+ 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;
+};
+
+/**
+ * LIST_UP_TO_DATE means that the list is up to date and need not do
+ * any walking to be able to answer any questions anyone may have.
+ */
+#define LIST_UP_TO_DATE 0
+/**
+ * LIST_DIRTY means that the list contains no useful information and
+ * if anyone asks it anything it will have to populate itself before
+ * answering.
+ */
+#define LIST_DIRTY 1
+/**
+ * LIST_LAZY means that 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.
+ */
+#define LIST_LAZY 2
+
+/**
+ * Class that implements a live NodeList that matches Elements in the
+ * tree based on some criterion.
+ */
+class nsContentList : public nsBaseContentList,
+ public nsIHTMLCollection,
+ public nsStubMutationObserver
+{
+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.
+ */
+ nsContentList(nsINode* aRootNode,
+ int32_t aMatchNameSpaceId,
+ nsIAtom* aHTMLMatchAtom,
+ nsIAtom* aXMLMatchAtom,
+ bool aDeep = 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.
+ */
+ nsContentList(nsINode* aRootNode,
+ nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ void* aData,
+ bool aDeep = true,
+ nsIAtom* aMatchAtom = nullptr,
+ int32_t aMatchNameSpaceId = kNameSpaceID_None,
+ bool aFuncMayDependOnAttr = true);
+
+ // nsWrapperCache
+ using nsWrapperCache::GetWrapperPreserveColor;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+protected:
+ virtual ~nsContentList();
+
+ virtual JSObject* GetWrapperPreserveColorInternal() override
+ {
+ return nsWrapperCache::GetWrapperPreserveColor();
+ }
+public:
+
+ // nsIDOMHTMLCollection
+ NS_DECL_NSIDOMHTMLCOLLECTION
+
+ // nsBaseContentList overrides
+ virtual int32_t IndexOf(nsIContent *aContent, bool aDoFlush) override;
+ virtual int32_t IndexOf(nsIContent* aContent) override;
+ virtual nsINode* GetParentObject() override
+ {
+ return mRootNode;
+ }
+
+ virtual nsIContent* Item(uint32_t aIndex) override;
+ virtual mozilla::dom::Element* GetElementAt(uint32_t index) override;
+ virtual mozilla::dom::Element*
+ GetFirstNamedElement(const nsAString& aName, bool& aFound) override
+ {
+ mozilla::dom::Element* item = NamedItem(aName, true);
+ aFound = !!item;
+ return item;
+ }
+ virtual void GetSupportedNames(nsTArray<nsString>& aNames) override;
+
+ // nsContentList public methods
+ uint32_t Length(bool aDoFlush);
+ nsIContent* Item(uint32_t aIndex, bool aDoFlush);
+ mozilla::dom::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.
+ NS_PRECONDITION(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 = LIST_DIRTY;
+ Reset();
+ }
+
+protected:
+ /**
+ * Returns whether the element matches our criterion
+ *
+ * @param aElement the element to attempt to match
+ * @return whether we match
+ */
+ bool Match(mozilla::dom::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)
+ */
+ void PopulateSelf(uint32_t aNeededLength);
+
+ /**
+ * @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.
+ */
+ virtual void RemoveFromCaches() override
+ {
+ RemoveFromHashtable();
+ }
+
+ nsINode* mRootNode; // Weak ref
+ int32_t mMatchNameSpaceId;
+ nsCOMPtr<nsIAtom> mHTMLMatchAtom;
+ nsCOMPtr<nsIAtom> mXMLMatchAtom;
+
+ /**
+ * Function to use to determine whether a piece of content matches
+ * our criterion
+ */
+ nsContentListMatchFunc mFunc;
+ /**
+ * Cleanup closure data with this.
+ */
+ nsContentListDestroyFunc mDestroyFunc;
+ /**
+ * Closure data to pass to mFunc when we call it
+ */
+ void* mData;
+ /**
+ * The current state of the list (possible values are:
+ * LIST_UP_TO_DATE, LIST_LAZY, LIST_DIRTY
+ */
+ uint8_t mState;
+
+ // The booleans have to use uint8_t to pack with mState, because MSVC won't
+ // pack different typedefs together. Once we no longer have to worry about
+ // flushes in XML documents, we can go back to using bool for the
+ // booleans.
+
+ /**
+ * True if we are looking for elements named "*"
+ */
+ uint8_t mMatchAll : 1;
+ /**
+ * Whether to actually descend the tree. If this is false, we won't
+ * consider grandkids of mRootNode.
+ */
+ uint8_t mDeep : 1;
+ /**
+ * Whether the return value of mFunc could depend on the values of
+ * attributes.
+ */
+ uint8_t mFuncMayDependOnAttr : 1;
+ /**
+ * Whether we actually need to flush to get our state correct.
+ */
+ uint8_t 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.
+ */
+ uint8_t mIsHTMLDocument : 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;
+ }
+
+#ifdef DEBUG
+ enum ContentListType {
+ eNodeList,
+ eHTMLCollection
+ };
+ ContentListType mType;
+#endif
+
+protected:
+ nsCacheableFuncStringContentList(nsINode* aRootNode,
+ nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString) :
+ nsContentList(aRootNode, aFunc, aDestroyFunc, nullptr),
+ mString(aString)
+ {
+ mData = (*aDataAllocator)(aRootNode, &mString);
+ MOZ_ASSERT(mData);
+ }
+
+ virtual void RemoveFromCaches() override {
+ RemoveFromFuncStringHashtable();
+ }
+ void RemoveFromFuncStringHashtable();
+
+ nsString mString;
+};
+
+class nsCacheableFuncStringNodeList
+ : public nsCacheableFuncStringContentList
+{
+public:
+ nsCacheableFuncStringNodeList(nsINode* aRootNode,
+ nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString)
+ : nsCacheableFuncStringContentList(aRootNode, aFunc, aDestroyFunc,
+ aDataAllocator, aString)
+ {
+#ifdef DEBUG
+ mType = eNodeList;
+#endif
+ }
+
+ virtual 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)
+ {
+#ifdef DEBUG
+ mType = eHTMLCollection;
+#endif
+ }
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+#ifdef DEBUG
+ static const ContentListType sType;
+#endif
+};
+
+#endif // nsContentList_h___
diff --git a/dom/base/nsContentListDeclarations.h b/dom/base/nsContentListDeclarations.h
new file mode 100644
index 000000000..db3a09036
--- /dev/null
+++ b/dom/base/nsContentListDeclarations.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 nsContentListDeclarations_h
+#define nsContentListDeclarations_h
+
+#include <stdint.h>
+#include "nsCOMPtr.h"
+
+class nsContentList;
+class nsIAtom;
+class nsIContent;
+class nsINode;
+// Can't use nsStringFwd.h because that's internal-API-only.
+class nsString;
+class nsAString;
+
+// 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.
+typedef bool (*nsContentListMatchFunc)(nsIContent* aContent,
+ int32_t aNamespaceID,
+ nsIAtom* aAtom,
+ void* aData);
+
+typedef void (*nsContentListDestroyFunc)(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.
+ */
+typedef void* (*nsFuncStringContentListDataAllocator)(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);
+
+already_AddRefed<nsContentList>
+NS_GetFuncStringNodeList(nsINode* aRootNode,
+ nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ nsFuncStringContentListDataAllocator aDataAllocator,
+ const nsAString& aString);
+already_AddRefed<nsContentList>
+NS_GetFuncStringHTMLCollection(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 000000000..fd04b1013
--- /dev/null
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -0,0 +1,858 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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>
+#ifdef MOZ_WIDGET_GONK
+#include "GonkPermission.h"
+#endif // MOZ_WIDGET_GONK
+#include "nsCOMPtr.h"
+#include "nsIDOMElement.h"
+#include "nsIPrincipal.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/dom/TabChild.h"
+#include "mozilla/dom/TabParent.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 "nsIDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsWeakPtr.h"
+#include "ScriptSettings.h"
+
+using mozilla::Unused; // <snicker>
+using namespace mozilla::dom;
+using namespace mozilla;
+
+#define kVisibilityChange "visibilitychange"
+
+class VisibilityChangeListener final : public nsIDOMEventListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ explicit VisibilityChangeListener(nsPIDOMWindowInner* aWindow);
+
+ void RemoveListener();
+ void SetCallback(nsIContentPermissionRequestCallback* aCallback);
+ already_AddRefed<nsIContentPermissionRequestCallback> GetCallback();
+
+private:
+ virtual ~VisibilityChangeListener() {}
+
+ nsWeakPtr mWindow;
+ nsCOMPtr<nsIContentPermissionRequestCallback> mCallback;
+};
+
+NS_IMPL_ISUPPORTS(VisibilityChangeListener, nsIDOMEventListener)
+
+VisibilityChangeListener::VisibilityChangeListener(nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+
+ mWindow = do_GetWeakReference(aWindow);
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ if (doc) {
+ doc->AddSystemEventListener(NS_LITERAL_STRING(kVisibilityChange),
+ /* listener */ this,
+ /* use capture */ true,
+ /* wants untrusted */ false);
+ }
+
+}
+
+NS_IMETHODIMP
+VisibilityChangeListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+ if (!type.EqualsLiteral(kVisibilityChange)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc =
+ do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
+ MOZ_ASSERT(doc);
+
+ if (mCallback) {
+ mCallback->NotifyVisibility(!doc->Hidden());
+ }
+
+ return NS_OK;
+}
+
+void
+VisibilityChangeListener::RemoveListener()
+{
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow);
+ if (!window) {
+ return;
+ }
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(window->GetExtantDoc());
+ if (target) {
+ target->RemoveSystemEventListener(NS_LITERAL_STRING(kVisibilityChange),
+ /* listener */ this,
+ /* use capture */ true);
+ }
+}
+
+void
+VisibilityChangeListener::SetCallback(nsIContentPermissionRequestCallback *aCallback)
+{
+ mCallback = aCallback;
+}
+
+already_AddRefed<nsIContentPermissionRequestCallback>
+VisibilityChangeListener::GetCallback()
+{
+ nsCOMPtr<nsIContentPermissionRequestCallback> callback = mCallback;
+ return callback.forget();
+}
+
+namespace mozilla {
+namespace dom {
+
+class ContentPermissionRequestParent : public PContentPermissionRequestParent
+{
+ public:
+ ContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
+ Element* element,
+ const IPC::Principal& principal);
+ virtual ~ContentPermissionRequestParent();
+
+ bool IsBeingDestroyed();
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<Element> mElement;
+ RefPtr<nsContentPermissionRequestProxy> mProxy;
+ nsTArray<PermissionRequest> mRequests;
+
+ private:
+ virtual bool Recvprompt();
+ virtual bool RecvNotifyVisibility(const bool& aIsVisible);
+ virtual bool RecvDestroy();
+ virtual void ActorDestroy(ActorDestroyReason why);
+};
+
+ContentPermissionRequestParent::ContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
+ Element* aElement,
+ const IPC::Principal& aPrincipal)
+{
+ MOZ_COUNT_CTOR(ContentPermissionRequestParent);
+
+ mPrincipal = aPrincipal;
+ mElement = aElement;
+ mRequests = aRequests;
+}
+
+ContentPermissionRequestParent::~ContentPermissionRequestParent()
+{
+ MOZ_COUNT_DTOR(ContentPermissionRequestParent);
+}
+
+bool
+ContentPermissionRequestParent::Recvprompt()
+{
+ mProxy = new nsContentPermissionRequestProxy();
+ if (NS_FAILED(mProxy->Init(mRequests, this))) {
+ mProxy->Cancel();
+ }
+ return true;
+}
+
+bool
+ContentPermissionRequestParent::RecvNotifyVisibility(const bool& aIsVisible)
+{
+ if (!mProxy) {
+ return false;
+ }
+ mProxy->NotifyVisibility(aIsVisible);
+ return true;
+}
+
+bool
+ContentPermissionRequestParent::RecvDestroy()
+{
+ Unused << PContentPermissionRequestParent::Send__delete__(this);
+ return true;
+}
+
+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 nsACString& aAccess,
+ const nsTArray<nsString>& aOptions)
+{
+ mType = aType;
+ mAccess = aAccess;
+ mOptions = aOptions;
+}
+
+ContentPermissionType::~ContentPermissionType()
+{
+}
+
+NS_IMETHODIMP
+ContentPermissionType::GetType(nsACString& aType)
+{
+ aType = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionType::GetAccess(nsACString& aAccess)
+{
+ aAccess = mAccess;
+ 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, false);
+ 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].access(),
+ aSrcArray[i].options());
+ aDesArray->AppendElement(cpt, false);
+ }
+ return len;
+}
+
+/* static */ uint32_t
+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;
+ nsAutoCString access;
+ cpt->GetType(type);
+ cpt->GetAccess(access);
+
+ 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, access, options));
+ }
+ return len;
+}
+
+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 nsACString& aAccess,
+ const nsTArray<nsString>& aOptions,
+ nsIArray** aTypesArray)
+{
+ nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ RefPtr<ContentPermissionType> permType = new ContentPermissionType(aType,
+ aAccess,
+ aOptions);
+ types->AppendElement(permType, false);
+ types.forget(aTypesArray);
+
+ return NS_OK;
+}
+
+/* static */ PContentPermissionRequestParent*
+nsContentPermissionUtils::CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
+ Element* element,
+ const IPC::Principal& principal,
+ const TabId& aTabId)
+{
+ PContentPermissionRequestParent* parent =
+ new ContentPermissionRequestParent(aRequests, element, principal);
+ ContentPermissionRequestParentMap()[parent] = aTabId;
+
+ return parent;
+}
+
+/* static */ nsresult
+nsContentPermissionUtils::AskPermission(nsIContentPermissionRequest* aRequest,
+ nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(!aWindow || aWindow->IsInnerWindow());
+ 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.
+
+ TabChild* child = TabChild::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);
+
+ req->IPDLAddRef();
+ ContentChild::GetSingleton()->SendPContentPermissionRequestConstructor(
+ req,
+ permArray,
+ IPC::Principal(principal),
+ 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 Move(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 Move(childArray);
+}
+
+/* static */ void
+nsContentPermissionUtils::NotifyRemoveContentPermissionRequestChild(
+ PContentPermissionRequestChild* aChild)
+{
+ auto it = ContentPermissionRequestChildMap().find(aChild);
+ MOZ_ASSERT(it != ContentPermissionRequestChildMap().end());
+
+ ContentPermissionRequestChildMap().erase(it);
+}
+
+NS_IMPL_ISUPPORTS(nsContentPermissionRequester, nsIContentPermissionRequester)
+
+nsContentPermissionRequester::nsContentPermissionRequester(nsPIDOMWindowInner* aWindow)
+ : mWindow(do_GetWeakReference(aWindow))
+ , mListener(new VisibilityChangeListener(aWindow))
+{
+}
+
+nsContentPermissionRequester::~nsContentPermissionRequester()
+{
+ mListener->RemoveListener();
+ mListener = nullptr;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequester::GetVisibility(nsIContentPermissionRequestCallback* aCallback)
+{
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow);
+ if (!window) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocShell> docshell = window->GetDocShell();
+ if (!docshell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isActive = false;
+ docshell->GetIsActive(&isActive);
+ aCallback->NotifyVisibility(isActive);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequester::SetOnVisibilityChange(nsIContentPermissionRequestCallback* aCallback)
+{
+ mListener->SetCallback(aCallback);
+
+ if (!aCallback) {
+ mListener->RemoveListener();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequester::GetOnVisibilityChange(nsIContentPermissionRequestCallback** aCallback)
+{
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ nsCOMPtr<nsIContentPermissionRequestCallback> callback = mListener->GetCallback();
+ callback.forget(aCallback);
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+NS_IMPL_ISUPPORTS(nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy,
+ nsIContentPermissionRequester)
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy
+ ::GetVisibility(nsIContentPermissionRequestCallback* aCallback)
+{
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ mGetCallback = aCallback;
+ mWaitGettingResult = true;
+ Unused << mParent->SendGetVisibility();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy
+ ::SetOnVisibilityChange(nsIContentPermissionRequestCallback* aCallback)
+{
+ mOnChangeCallback = aCallback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy
+ ::GetOnVisibilityChange(nsIContentPermissionRequestCallback** aCallback)
+{
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ nsCOMPtr<nsIContentPermissionRequestCallback> callback = mOnChangeCallback;
+ callback.forget(aCallback);
+ return NS_OK;
+}
+
+void
+nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy
+ ::NotifyVisibilityResult(const bool& aIsVisible)
+{
+ if (mWaitGettingResult) {
+ MOZ_ASSERT(mGetCallback);
+ mWaitGettingResult = false;
+ mGetCallback->NotifyVisibility(aIsVisible);
+ return;
+ }
+
+ if (mOnChangeCallback) {
+ mOnChangeCallback->NotifyVisibility(aIsVisible);
+ }
+}
+
+nsContentPermissionRequestProxy::nsContentPermissionRequestProxy()
+{
+ MOZ_COUNT_CTOR(nsContentPermissionRequestProxy);
+}
+
+nsContentPermissionRequestProxy::~nsContentPermissionRequestProxy()
+{
+ MOZ_COUNT_DTOR(nsContentPermissionRequestProxy);
+}
+
+nsresult
+nsContentPermissionRequestProxy::Init(const nsTArray<PermissionRequest>& requests,
+ ContentPermissionRequestParent* parent)
+{
+ NS_ASSERTION(parent, "null parent");
+ mParent = parent;
+ mPermissionRequests = requests;
+ mRequester = new nsContentPermissionRequesterProxy(mParent);
+
+ 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()
+{
+ mRequester = nullptr;
+ 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_ADDREF(*aRequestingPrincipal = mParent->mPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetElement(nsIDOMElement * *aRequestingElement)
+{
+ NS_ENSURE_ARG_POINTER(aRequestingElement);
+ if (mParent == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(mParent->mElement);
+ elem.forget(aRequestingElement);
+ 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);
+ mParent = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::Allow(JS::HandleValue 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;
+ if (aChoices.isNullOrUndefined()) {
+ // No choice is specified.
+ } else if (aChoices.isObject()) {
+ // Iterate through all permission types.
+ for (uint32_t i = 0; i < mPermissionRequests.Length(); ++i) {
+ nsCString type = mPermissionRequests[i].type();
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> obj(cx, &aChoices.toObject());
+ JSAutoCompartment ac(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;
+ }
+ choices.AppendElement(PermissionChoice(type, choice));
+ }
+ }
+ } else {
+ MOZ_ASSERT(false, "SelectedChoices should be undefined or an JS object");
+ return NS_ERROR_FAILURE;
+ }
+
+ Unused << mParent->SendNotifyResult(true, choices);
+ mParent = nullptr;
+ return NS_OK;
+}
+
+void
+nsContentPermissionRequestProxy::NotifyVisibility(const bool& aIsVisible)
+{
+ MOZ_ASSERT(mRequester);
+
+ mRequester->NotifyVisibilityResult(aIsVisible);
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+ NS_ENSURE_ARG_POINTER(aRequester);
+
+ RefPtr<nsContentPermissionRequesterProxy> requester = mRequester;
+ requester.forget(aRequester);
+ return NS_OK;
+}
+
+// RemotePermissionRequest
+
+NS_IMPL_ISUPPORTS(RemotePermissionRequest, nsIContentPermissionRequestCallback);
+
+RemotePermissionRequest::RemotePermissionRequest(
+ nsIContentPermissionRequest* aRequest,
+ nsPIDOMWindowInner* aWindow)
+ : mRequest(aRequest)
+ , mWindow(aWindow)
+ , mIPCOpen(false)
+ , mDestroyed(false)
+{
+ mListener = new VisibilityChangeListener(mWindow);
+ mListener->SetCallback(this);
+}
+
+RemotePermissionRequest::~RemotePermissionRequest()
+{
+ MOZ_ASSERT(!mIPCOpen, "Protocol must not be open when RemotePermissionRequest is destroyed.");
+}
+
+void
+RemotePermissionRequest::DoCancel()
+{
+ NS_ASSERTION(mRequest, "We need a request");
+ mRequest->Cancel();
+}
+
+void
+RemotePermissionRequest::DoAllow(JS::HandleValue aChoices)
+{
+ NS_ASSERTION(mRequest, "We need a request");
+ mRequest->Allow(aChoices);
+}
+
+// PContentPermissionRequestChild
+bool
+RemotePermissionRequest::RecvNotifyResult(const bool& aAllow,
+ InfallibleTArray<PermissionChoice>&& aChoices)
+{
+ Destroy();
+
+ if (aAllow && mWindow->IsCurrentInnerWindow()) {
+ // Use 'undefined' if no choice is provided.
+ if (aChoices.IsEmpty()) {
+ DoAllow(JS::UndefinedHandleValue);
+ return true;
+ }
+
+ // Convert choices to a JS val if any.
+ // {"type1": "choice1", "type2": "choiceA"}
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mWindow))) {
+ return true; // 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 false;
+ }
+ }
+ JS::RootedValue val(cx, JS::ObjectValue(*obj));
+ DoAllow(val);
+ } else {
+ DoCancel();
+ }
+ return true;
+}
+
+bool
+RemotePermissionRequest::RecvGetVisibility()
+{
+ nsCOMPtr<nsIDocShell> docshell = mWindow->GetDocShell();
+ if (!docshell) {
+ return false;
+ }
+
+ bool isActive = false;
+ docshell->GetIsActive(&isActive);
+ Unused << SendNotifyVisibility(isActive);
+ return true;
+}
+
+void
+RemotePermissionRequest::Destroy()
+{
+ if (!IPCOpen()) {
+ return;
+ }
+ Unused << this->SendDestroy();
+ mListener->RemoveListener();
+ mListener = nullptr;
+ mDestroyed = true;
+}
+
+NS_IMETHODIMP
+RemotePermissionRequest::NotifyVisibility(bool isVisible)
+{
+ if (!IPCOpen()) {
+ return NS_OK;
+ }
+
+ Unused << SendNotifyVisibility(isVisible);
+ return NS_OK;
+}
diff --git a/dom/base/nsContentPermissionHelper.h b/dom/base/nsContentPermissionHelper.h
new file mode 100644
index 000000000..af15744af
--- /dev/null
+++ b/dom/base/nsContentPermissionHelper.h
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMEventListener.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;
+class VisibilityChangeListener;
+
+// Forward declare IPC::Principal here which is defined in
+// PermissionMessageUtils.h. Include this file will transitively includes
+// "windows.h" and it defines
+// #define CreateEvent CreateEventW
+// #define LoadImage LoadImageW
+// That will mess up windows build.
+namespace IPC {
+class Principal;
+} // namespace IPC
+
+namespace mozilla {
+namespace 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 nsACString& aAccess,
+ const nsTArray<nsString>& aOptions);
+
+protected:
+ virtual ~ContentPermissionType();
+
+ nsCString mType;
+ nsCString mAccess;
+ nsTArray<nsString> mOptions;
+};
+
+class nsContentPermissionUtils
+{
+public:
+ static uint32_t
+ ConvertPermissionRequestToArray(nsTArray<PermissionRequest>& aSrcArray,
+ nsIMutableArray* aDesArray);
+
+ static uint32_t
+ ConvertArrayToPermissionRequest(nsIArray* aSrcArray,
+ nsTArray<PermissionRequest>& aDesArray);
+
+ static nsresult
+ CreatePermissionArray(const nsACString& aType,
+ const nsACString& aAccess,
+ const nsTArray<nsString>& aOptions,
+ nsIArray** aTypesArray);
+
+ static PContentPermissionRequestParent*
+ CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
+ Element* element,
+ const IPC::Principal& principal,
+ 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);
+};
+
+class nsContentPermissionRequester final : public nsIContentPermissionRequester
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPERMISSIONREQUESTER
+
+ explicit nsContentPermissionRequester(nsPIDOMWindowInner* aWindow);
+
+private:
+ virtual ~nsContentPermissionRequester();
+
+ nsWeakPtr mWindow;
+ RefPtr<VisibilityChangeListener> mListener;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+using mozilla::dom::ContentPermissionRequestParent;
+
+class nsContentPermissionRequestProxy : public nsIContentPermissionRequest
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPERMISSIONREQUEST
+
+ nsContentPermissionRequestProxy();
+
+ nsresult Init(const nsTArray<mozilla::dom::PermissionRequest>& requests,
+ ContentPermissionRequestParent* parent);
+
+ void OnParentDestroyed();
+
+ void NotifyVisibility(const bool& aIsVisible);
+
+private:
+ class nsContentPermissionRequesterProxy final : public nsIContentPermissionRequester {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPERMISSIONREQUESTER
+
+ explicit nsContentPermissionRequesterProxy(ContentPermissionRequestParent* aParent)
+ : mParent(aParent)
+ , mWaitGettingResult(false) {}
+
+ void NotifyVisibilityResult(const bool& aIsVisible);
+
+ private:
+ virtual ~nsContentPermissionRequesterProxy() {}
+
+ ContentPermissionRequestParent* mParent;
+ bool mWaitGettingResult;
+ nsCOMPtr<nsIContentPermissionRequestCallback> mGetCallback;
+ nsCOMPtr<nsIContentPermissionRequestCallback> mOnChangeCallback;
+ };
+
+ virtual ~nsContentPermissionRequestProxy();
+
+ // Non-owning pointer to the ContentPermissionRequestParent object which owns this proxy.
+ ContentPermissionRequestParent* mParent;
+ nsTArray<mozilla::dom::PermissionRequest> mPermissionRequests;
+ RefPtr<nsContentPermissionRequesterProxy> mRequester;
+};
+
+/**
+ * RemotePermissionRequest will send a prompt ipdl request to b2g process.
+ */
+class RemotePermissionRequest final : public nsIContentPermissionRequestCallback
+ , public mozilla::dom::PContentPermissionRequestChild
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPERMISSIONREQUESTCALLBACK
+
+ RemotePermissionRequest(nsIContentPermissionRequest* aRequest,
+ nsPIDOMWindowInner* aWindow);
+
+ // It will be called when prompt dismissed.
+ virtual bool RecvNotifyResult(const bool &aAllow,
+ InfallibleTArray<PermissionChoice>&& aChoices) override;
+
+ virtual bool RecvGetVisibility() override;
+
+ void IPDLAddRef()
+ {
+ mIPCOpen = true;
+ AddRef();
+ }
+
+ void IPDLRelease()
+ {
+ mIPCOpen = false;
+ Release();
+ }
+
+ void Destroy();
+
+ bool IPCOpen() const { return mIPCOpen && !mDestroyed; }
+
+private:
+ virtual ~RemotePermissionRequest();
+
+ void DoAllow(JS::HandleValue aChoices);
+ void DoCancel();
+
+ nsCOMPtr<nsIContentPermissionRequest> mRequest;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ bool mIPCOpen;
+ bool mDestroyed;
+ RefPtr<VisibilityChangeListener> mListener;
+};
+
+#endif // nsContentPermissionHelper_h
+
diff --git a/dom/base/nsContentPolicy.cpp b/dom/base/nsContentPolicy.cpp
new file mode 100644
index 000000000..337debcea
--- /dev/null
+++ b/dom/base/nsContentPolicy.cpp
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim: ft=cpp tw=78 sw=4 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 "nsIDocShell.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMWindow.h"
+#include "nsIContent.h"
+#include "nsILoadContext.h"
+#include "nsCOMArray.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+
+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)
+ , mSimplePolicies(NS_SIMPLECONTENTPOLICY_CATEGORY)
+{
+}
+
+nsContentPolicy::~nsContentPolicy()
+{
+}
+
+#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,
+ SCPMethod simplePolicyMethod,
+ nsContentPolicyType contentType,
+ nsIURI *contentLocation,
+ nsIURI *requestingLocation,
+ nsISupports *requestingContext,
+ const nsACString &mimeType,
+ nsISupports *extra,
+ nsIPrincipal *requestPrincipal,
+ int16_t *decision)
+{
+ //sanity-check passed-through parameters
+ NS_PRECONDITION(decision, "Null out pointer");
+ WARN_IF_URI_UNINITIALIZED(contentLocation, "Request URI");
+ WARN_IF_URI_UNINITIALIZED(requestingLocation, "Requesting URI");
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIDOMNode> node(do_QueryInterface(requestingContext));
+ nsCOMPtr<nsIDOMWindow> window(do_QueryInterface(requestingContext));
+ NS_ASSERTION(!requestingContext || node || window,
+ "Context should be a DOM node or a DOM window!");
+ }
+#endif
+
+ /*
+ * There might not be a requestinglocation. This can happen for
+ * iframes with an image as src. Get the uri from the dom node.
+ * See bug 254510
+ */
+ if (!requestingLocation) {
+ nsCOMPtr<nsIDocument> doc;
+ nsCOMPtr<nsIContent> node = do_QueryInterface(requestingContext);
+ if (node) {
+ doc = node->OwnerDoc();
+ }
+ if (!doc) {
+ doc = do_QueryInterface(requestingContext);
+ }
+ if (doc) {
+ requestingLocation = doc->GetDocumentURI();
+ }
+ }
+
+ nsContentPolicyType externalType =
+ nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
+
+ nsCOMPtr<nsIContentPolicy> mixedContentBlocker =
+ do_GetService(NS_MIXEDCONTENTBLOCKER_CONTRACTID);
+
+ nsCOMPtr<nsIContentPolicy> cspService =
+ do_GetService(CSPSERVICE_CONTRACTID);
+
+ /*
+ * Enumerate mPolicies and ask each of them, taking the logical AND of
+ * their permissions.
+ */
+ nsresult rv;
+ nsCOMArray<nsIContentPolicy> entries;
+ mPolicies.GetEntries(entries);
+ int32_t count = entries.Count();
+ for (int32_t i = 0; i < count; i++) {
+ /* check the appropriate policy */
+ // Send internal content policy type to CSP and mixed content blocker
+ nsContentPolicyType type = externalType;
+ if (mixedContentBlocker == entries[i] || cspService == entries[i]) {
+ type = contentType;
+ }
+ rv = (entries[i]->*policyMethod)(type, contentLocation,
+ requestingLocation, requestingContext,
+ mimeType, extra, requestPrincipal,
+ decision);
+
+ if (NS_SUCCEEDED(rv) && NS_CP_REJECTED(*decision)) {
+ /* policy says no, no point continuing to check */
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIDOMElement> topFrameElement;
+ bool isTopLevel = true;
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext)) {
+ window = node->OwnerDoc()->GetWindow();
+ } else {
+ window = do_QueryInterface(requestingContext);
+ }
+
+ if (window) {
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
+ if (loadContext) {
+ loadContext->GetTopFrameElement(getter_AddRefs(topFrameElement));
+ }
+
+ MOZ_ASSERT(window->IsOuterWindow());
+
+ if (topFrameElement) {
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = window->GetScriptableTop();
+ isTopLevel = topWindow == window;
+ } else {
+ // If we don't have a top frame element, then requestingContext is
+ // part of the top-level XUL document. Presumably it's the <browser>
+ // element that content is being loaded into, so we call it the
+ // topFrameElement.
+ topFrameElement = do_QueryInterface(requestingContext);
+ isTopLevel = true;
+ }
+ }
+
+ nsCOMArray<nsISimpleContentPolicy> simpleEntries;
+ mSimplePolicies.GetEntries(simpleEntries);
+ count = simpleEntries.Count();
+ for (int32_t i = 0; i < count; i++) {
+ /* check the appropriate policy */
+ rv = (simpleEntries[i]->*simplePolicyMethod)(externalType, contentLocation,
+ requestingLocation,
+ topFrameElement, isTopLevel,
+ mimeType, extra, requestPrincipal,
+ 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> <Ref:%s> result=%s", \
+ contentLocation ? contentLocation->GetSpecOrDefault().get() \
+ : "None", \
+ requestingLocation ? requestingLocation->GetSpecOrDefault().get()\
+ : "None", \
+ resultName) \
+ ); \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsContentPolicy::ShouldLoad(uint32_t contentType,
+ nsIURI *contentLocation,
+ nsIURI *requestingLocation,
+ nsISupports *requestingContext,
+ const nsACString &mimeType,
+ nsISupports *extra,
+ nsIPrincipal *requestPrincipal,
+ int16_t *decision)
+{
+ // ShouldProcess does not need a content location, but we do
+ NS_PRECONDITION(contentLocation, "Must provide request location");
+ nsresult rv = CheckPolicy(&nsIContentPolicy::ShouldLoad,
+ &nsISimpleContentPolicy::ShouldLoad,
+ contentType,
+ contentLocation, requestingLocation,
+ requestingContext, mimeType, extra,
+ requestPrincipal, decision);
+ LOG_CHECK("ShouldLoad");
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsContentPolicy::ShouldProcess(uint32_t contentType,
+ nsIURI *contentLocation,
+ nsIURI *requestingLocation,
+ nsISupports *requestingContext,
+ const nsACString &mimeType,
+ nsISupports *extra,
+ nsIPrincipal *requestPrincipal,
+ int16_t *decision)
+{
+ nsresult rv = CheckPolicy(&nsIContentPolicy::ShouldProcess,
+ &nsISimpleContentPolicy::ShouldProcess,
+ contentType,
+ contentLocation, requestingLocation,
+ requestingContext, mimeType, extra,
+ requestPrincipal, decision);
+ LOG_CHECK("ShouldProcess");
+
+ return rv;
+}
diff --git a/dom/base/nsContentPolicy.h b/dom/base/nsContentPolicy.h
new file mode 100644
index 000000000..06b01ac68
--- /dev/null
+++ b/dom/base/nsContentPolicy.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim: ft=cpp ts=8 sw=4 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 __nsContentPolicy_h__
+#define __nsContentPolicy_h__
+
+#include "nsIContentPolicy.h"
+#include "nsISimpleContentPolicy.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;
+ nsCategoryCache<nsISimpleContentPolicy> mSimplePolicies;
+
+ //Helper type for CheckPolicy
+ typedef
+ NS_STDCALL_FUNCPROTO(nsresult, CPMethod, nsIContentPolicy,
+ ShouldProcess,
+ (uint32_t, nsIURI*, nsIURI*, nsISupports*,
+ const nsACString &, nsISupports*, nsIPrincipal*,
+ int16_t*));
+
+ typedef
+ NS_STDCALL_FUNCPROTO(nsresult, SCPMethod, nsISimpleContentPolicy,
+ ShouldProcess,
+ (uint32_t, nsIURI*, nsIURI*, nsIDOMElement*, bool,
+ const nsACString &, nsISupports*, nsIPrincipal*,
+ int16_t*));
+
+ //Helper method that applies policyMethod across all policies in mPolicies
+ // with the given parameters
+ nsresult CheckPolicy(CPMethod policyMethod, SCPMethod simplePolicyMethod,
+ nsContentPolicyType contentType,
+ nsIURI *aURI, nsIURI *origURI,
+ nsISupports *requestingContext,
+ const nsACString &mimeGuess, nsISupports *extra,
+ nsIPrincipal *requestPrincipal,
+ 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 000000000..ed0544226
--- /dev/null
+++ b/dom/base/nsContentPolicyUtils.h
@@ -0,0 +1,334 @@
+/* -*- 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/. */
+
+/*
+ * 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 "nsContentUtils.h"
+#include "nsIContentPolicy.h"
+#include "nsIContent.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURI.h"
+#include "nsServiceManagerUtils.h"
+
+//XXXtw sadly, this makes consumers of nsContentPolicyUtils depend on widget
+#include "nsIDocument.h"
+#include "nsPIDOMWindow.h"
+
+class nsACString;
+class nsIPrincipal;
+
+#define NS_CONTENTPOLICY_CONTRACTID "@mozilla.org/layout/content-policy;1"
+#define NS_CONTENTPOLICY_CATEGORY "content-policy"
+#define NS_SIMPLECONTENTPOLICY_CATEGORY "simple-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(uint32_t 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_REFRESH );
+ CASE_RETURN( TYPE_XBL );
+ 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 );
+ default:
+ return "<Unknown Type>";
+ }
+}
+
+#undef CASE_RETURN
+
+/* 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 (contentType, contentLocation, requestOrigin, \
+ context, mimeType, extra, originPrincipal, \
+ 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 (contentType, contentLocation, requestOrigin, \
+ context, mimeType, extra, originPrincipal, \
+ 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_AND_DATA(action) \
+ nsCOMPtr<nsIURI> requestOrigin; \
+ PR_BEGIN_MACRO \
+ if (originPrincipal) { \
+ nsCOMPtr<nsIScriptSecurityManager> secMan = aSecMan; \
+ if (!secMan) { \
+ secMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); \
+ } \
+ if (secMan) { \
+ bool isSystem; \
+ nsresult rv = secMan->IsSystemPrincipal(originPrincipal, \
+ &isSystem); \
+ NS_ENSURE_SUCCESS(rv, rv); \
+ if (isSystem && contentType != nsIContentPolicy::TYPE_DOCUMENT) { \
+ *decision = nsIContentPolicy::ACCEPT; \
+ nsCOMPtr<nsINode> n = do_QueryInterface(context); \
+ if (!n) { \
+ nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(context);\
+ n = win ? win->GetExtantDoc() : nullptr; \
+ } \
+ if (n) { \
+ nsIDocument* d = n->OwnerDoc(); \
+ if (d->IsLoadedAsData() || d->IsBeingUsedAsImage() || \
+ d->IsResourceDoc()) { \
+ nsCOMPtr<nsIContentPolicy> dataPolicy = \
+ do_GetService( \
+ "@mozilla.org/data-document-content-policy;1"); \
+ if (dataPolicy) { \
+ nsContentPolicyType externalType = \
+ nsContentUtils::InternalContentPolicyTypeToExternal(contentType);\
+ dataPolicy-> action (externalType, contentLocation, \
+ requestOrigin, context, \
+ mimeType, extra, \
+ originPrincipal, decision); \
+ } \
+ } \
+ } \
+ return NS_OK; \
+ } \
+ } \
+ nsresult rv = originPrincipal->GetURI(getter_AddRefs(requestOrigin)); \
+ NS_ENSURE_SUCCESS(rv, rv); \
+ } \
+ PR_END_MACRO
+
+/**
+ * Alias for calling ShouldLoad on the content policy service. Parameters are
+ * the same as nsIContentPolicy::shouldLoad, except for the originPrincipal
+ * parameter, which should be non-null if possible, and the last two
+ * parameters, which can be used to pass in pointer to some useful services if
+ * the caller already has them. The origin URI to pass to shouldLoad will be
+ * the URI of originPrincipal, unless originPrincipal is null (in which case a
+ * null origin URI will be passed).
+ */
+inline nsresult
+NS_CheckContentLoadPolicy(uint32_t contentType,
+ nsIURI *contentLocation,
+ nsIPrincipal *originPrincipal,
+ nsISupports *context,
+ const nsACString &mimeType,
+ nsISupports *extra,
+ int16_t *decision,
+ nsIContentPolicy *policyService = nullptr,
+ nsIScriptSecurityManager* aSecMan = nullptr)
+{
+ CHECK_PRINCIPAL_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. Parameters
+ * are the same as nsIContentPolicy::shouldLoad, except for the originPrincipal
+ * parameter, which should be non-null if possible, and the last two
+ * parameters, which can be used to pass in pointer to some useful services if
+ * the caller already has them. The origin URI to pass to shouldLoad will be
+ * the URI of originPrincipal, unless originPrincipal is null (in which case a
+ * null origin URI will be passed).
+ */
+inline nsresult
+NS_CheckContentProcessPolicy(uint32_t contentType,
+ nsIURI *contentLocation,
+ nsIPrincipal *originPrincipal,
+ nsISupports *context,
+ const nsACString &mimeType,
+ nsISupports *extra,
+ int16_t *decision,
+ nsIContentPolicy *policyService = nullptr,
+ nsIScriptSecurityManager* aSecMan = nullptr)
+{
+ CHECK_PRINCIPAL_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 (which also QIs to nsIDOMNode), so
+ // try that first
+ nsCOMPtr<nsIDocument> 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 000000000..3d6f069d2
--- /dev/null
+++ b/dom/base/nsContentSink.cpp
@@ -0,0 +1,1621 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsScriptLoader.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "nsStyleLinkElement.h"
+#include "nsIDocShell.h"
+#include "nsILoadContext.h"
+#include "nsCPrefetchService.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsIProtocolHandler.h"
+#include "nsIHttpChannel.h"
+#include "nsIContent.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsViewManager.h"
+#include "nsIAtom.h"
+#include "nsGkAtoms.h"
+#include "nsNetCID.h"
+#include "nsIOfflineCacheUpdate.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheContainer.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsICookieService.h"
+#include "nsContentUtils.h"
+#include "nsNodeInfoManager.h"
+#include "nsIAppShell.h"
+#include "nsIWidget.h"
+#include "nsWidgetsCID.h"
+#include "nsIDOMNode.h"
+#include "mozAutoDocUpdate.h"
+#include "nsIWebNavigation.h"
+#include "nsGenericHTMLElement.h"
+#include "nsHTMLDNSPrefetch.h"
+#include "nsIObserverService.h"
+#include "mozilla/Preferences.h"
+#include "nsParserConstants.h"
+#include "nsSandboxFlags.h"
+
+using namespace mozilla;
+
+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_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(mCSSLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mScriptLoader)
+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(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)
+ , mBeganUpdate(0)
+ , mLayoutStarted(0)
+ , mDynamicLowerValue(0)
+ , mParsing(0)
+ , mDroppedTimer(0)
+ , mDeferredLayoutStart(0)
+ , mDeferredFlushTags(0)
+ , mIsDocumentObserver(0)
+ , mRunsToCompletion(0)
+ , 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);
+ }
+}
+
+bool nsContentSink::sNotifyOnTimer;
+int32_t nsContentSink::sBackoffCount;
+int32_t nsContentSink::sNotificationInterval;
+int32_t nsContentSink::sInteractiveDeflectCount;
+int32_t nsContentSink::sPerfDeflectCount;
+int32_t nsContentSink::sPendingEventMode;
+int32_t nsContentSink::sEventProbeRate;
+int32_t nsContentSink::sInteractiveParseTime;
+int32_t nsContentSink::sPerfParseTime;
+int32_t nsContentSink::sInteractiveTime;
+int32_t nsContentSink::sInitialPerfTime;
+int32_t nsContentSink::sEnablePerfMode;
+
+void
+nsContentSink::InitializeStatics()
+{
+ Preferences::AddBoolVarCache(&sNotifyOnTimer,
+ "content.notify.ontimer", true);
+ // -1 means never.
+ Preferences::AddIntVarCache(&sBackoffCount,
+ "content.notify.backoffcount", -1);
+ // The gNotificationInterval has a dramatic effect on how long it
+ // takes to initially display content for slow connections.
+ // The current value provides good
+ // incremental display of content without causing an increase
+ // in page load time. If this value is set below 1/10 of second
+ // it starts to impact page load performance.
+ // see bugzilla bug 72138 for more info.
+ Preferences::AddIntVarCache(&sNotificationInterval,
+ "content.notify.interval", 120000);
+ Preferences::AddIntVarCache(&sInteractiveDeflectCount,
+ "content.sink.interactive_deflect_count", 0);
+ Preferences::AddIntVarCache(&sPerfDeflectCount,
+ "content.sink.perf_deflect_count", 200);
+ Preferences::AddIntVarCache(&sPendingEventMode,
+ "content.sink.pending_event_mode", 1);
+ Preferences::AddIntVarCache(&sEventProbeRate,
+ "content.sink.event_probe_rate", 1);
+ Preferences::AddIntVarCache(&sInteractiveParseTime,
+ "content.sink.interactive_parse_time", 3000);
+ Preferences::AddIntVarCache(&sPerfParseTime,
+ "content.sink.perf_parse_time", 360000);
+ Preferences::AddIntVarCache(&sInteractiveTime,
+ "content.sink.interactive_time", 750000);
+ Preferences::AddIntVarCache(&sInitialPerfTime,
+ "content.sink.initial_perf_time", 2000000);
+ Preferences::AddIntVarCache(&sEnablePerfMode,
+ "content.sink.enable_perf_mode", 0);
+}
+
+nsresult
+nsContentSink::Init(nsIDocument* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel)
+{
+ NS_PRECONDITION(aDoc, "null ptr");
+ NS_PRECONDITION(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 = sBackoffCount;
+
+ if (sEnablePerfMode != 0) {
+ mDynamicLowerValue = sEnablePerfMode == 1;
+ FavorPerformanceHint(!mDynamicLowerValue, 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentSink::StyleSheetLoaded(StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus)
+{
+ NS_ASSERTION(!mRunsToCompletion, "How come a fragment parser observed sheets?");
+ if (!aWasAlternate) {
+ NS_ASSERTION(mPendingSheetCount > 0, "How'd that happen?");
+ --mPendingSheetCount;
+
+ if (mPendingSheetCount == 0 &&
+ (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();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsContentSink::ProcessHTTPHeaders(nsIChannel* aChannel)
+{
+ nsCOMPtr<nsIHttpChannel> httpchannel(do_QueryInterface(aChannel));
+
+ if (!httpchannel) {
+ return NS_OK;
+ }
+
+ // 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(NS_LITERAL_CSTRING("link"),
+ linkHeader);
+ if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) {
+ mDocument->SetHeaderData(nsGkAtoms::link,
+ NS_ConvertASCIItoUTF16(linkHeader));
+
+ NS_ASSERTION(!mProcessLinkHeaderEvent.get(),
+ "Already dispatched an event?");
+
+ mProcessLinkHeaderEvent =
+ NewNonOwningRunnableMethod(this,
+ &nsContentSink::DoProcessLinkHeader);
+ rv = NS_DispatchToCurrentThread(mProcessLinkHeaderEvent.get());
+ if (NS_FAILED(rv)) {
+ mProcessLinkHeaderEvent.Forget();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsContentSink::ProcessHeaderData(nsIAtom* aHeader, const nsAString& aValue,
+ nsIContent* aContent)
+{
+ nsresult rv = NS_OK;
+ // necko doesn't process headers coming in from the parser
+
+ mDocument->SetHeaderData(aHeader, aValue);
+
+ if (aHeader == nsGkAtoms::setcookie) {
+ // Note: Necko already handles cookies set via the channel. We can't just
+ // call SetCookie on the channel because we want to do some security checks
+ // here.
+ nsCOMPtr<nsICookieService> cookieServ =
+ do_GetService(NS_COOKIESERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Get a URI from the document principal
+
+ // We use the original codebase in case the codebase was changed
+ // by SetDomain
+
+ // Note that a non-codebase principal (eg the system principal) will return
+ // a null URI.
+ nsCOMPtr<nsIURI> codebaseURI;
+ rv = mDocument->NodePrincipal()->GetURI(getter_AddRefs(codebaseURI));
+ NS_ENSURE_TRUE(codebaseURI, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ if (mParser) {
+ mParser->GetChannel(getter_AddRefs(channel));
+ }
+
+ rv = cookieServ->SetCookieString(codebaseURI,
+ nullptr,
+ NS_ConvertUTF16toUTF8(aValue).get(),
+ channel);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ else if (aHeader == nsGkAtoms::msthemecompatible) {
+ // Disable theming for the presshell if the value is no.
+ // XXXbz don't we want to support this as an HTTP header too?
+ nsAutoString value(aValue);
+ if (value.LowerCaseEqualsLiteral("no")) {
+ nsIPresShell* shell = mDocument->GetShell();
+ if (shell) {
+ shell->DisableThemeSupport();
+ }
+ }
+ }
+
+ return rv;
+}
+
+
+void
+nsContentSink::DoProcessLinkHeader()
+{
+ nsAutoString value;
+ mDocument->GetHeaderData(nsGkAtoms::link, value);
+ ProcessLinkHeader(value);
+}
+
+// check whether the Link header field applies to the context resource
+// see <http://tools.ietf.org/html/rfc5988#section-5.2>
+
+bool
+nsContentSink::LinkContextIsOurDocument(const nsSubstring& aAnchor)
+{
+ if (aAnchor.IsEmpty()) {
+ // anchor parameter not present or empty -> same document reference
+ return true;
+ }
+
+ nsIURI* docUri = mDocument->GetDocumentURI();
+
+ // 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 = docUri->CloneIgnoringRef(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;
+}
+
+// Decode a parameter value using the encoding defined in RFC 5987 (in place)
+//
+// charset "'" [ language ] "'" value-chars
+//
+// returns true when decoding happened successfully (otherwise leaves
+// passed value alone)
+bool
+nsContentSink::Decode5987Format(nsAString& aEncoded) {
+
+ nsresult rv;
+ nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsAutoCString asciiValue;
+
+ const char16_t* encstart = aEncoded.BeginReading();
+ const char16_t* encend = aEncoded.EndReading();
+
+ // create a plain ASCII string, aborting if we can't do that
+ // converted form is always shorter than input
+ while (encstart != encend) {
+ if (*encstart > 0 && *encstart < 128) {
+ asciiValue.Append((char)*encstart);
+ } else {
+ return false;
+ }
+ encstart++;
+ }
+
+ nsAutoString decoded;
+ nsAutoCString language;
+
+ rv = mimehdrpar->DecodeRFC5987Param(asciiValue, language, decoded);
+ if (NS_FAILED(rv))
+ return false;
+
+ aEncoded = decoded;
+ return true;
+}
+
+nsresult
+nsContentSink::ProcessLinkHeader(const nsAString& aLinkData)
+{
+ nsresult rv = NS_OK;
+
+ // keep track where we are within the header field
+ bool seenParameters = false;
+
+ // parse link content and call process style link
+ nsAutoString href;
+ nsAutoString rel;
+ nsAutoString title;
+ nsAutoString titleStar;
+ nsAutoString type;
+ nsAutoString media;
+ nsAutoString anchor;
+ nsAutoString crossOrigin;
+
+ crossOrigin.SetIsVoid(true);
+
+ // copy to work buffer
+ nsAutoString stringList(aLinkData);
+
+ // put an extra null at the end
+ stringList.Append(kNullCh);
+
+ char16_t* start = stringList.BeginWriting();
+ char16_t* end = start;
+ char16_t* last = start;
+ char16_t endCh;
+
+ while (*start != kNullCh) {
+ // skip leading space
+ while ((*start != kNullCh) && nsCRT::IsAsciiSpace(*start)) {
+ ++start;
+ }
+
+ end = start;
+ last = end - 1;
+
+ bool wasQuotedString = false;
+
+ // look for semicolon or comma
+ while (*end != kNullCh && *end != kSemicolon && *end != kComma) {
+ char16_t ch = *end;
+
+ if (ch == kQuote || ch == kLessThan) {
+ // quoted string
+
+ char16_t quote = ch;
+ if (quote == kLessThan) {
+ quote = kGreaterThan;
+ }
+
+ wasQuotedString = (ch == kQuote);
+
+ char16_t* closeQuote = (end + 1);
+
+ // seek closing quote
+ while (*closeQuote != kNullCh && quote != *closeQuote) {
+ // in quoted-string, "\" is an escape character
+ if (wasQuotedString && *closeQuote == kBackSlash && *(closeQuote + 1) != kNullCh) {
+ ++closeQuote;
+ }
+
+ ++closeQuote;
+ }
+
+ if (quote == *closeQuote) {
+ // found closer
+
+ // skip to close quote
+ end = closeQuote;
+
+ last = end - 1;
+
+ ch = *(end + 1);
+
+ if (ch != kNullCh && ch != kSemicolon && ch != kComma) {
+ // end string here
+ *(++end) = kNullCh;
+
+ ch = *(end + 1);
+
+ // keep going until semi or comma
+ while (ch != kNullCh && ch != kSemicolon && ch != kComma) {
+ ++end;
+
+ ch = *(end + 1);
+ }
+ }
+ }
+ }
+
+ ++end;
+ ++last;
+ }
+
+ endCh = *end;
+
+ // end string here
+ *end = kNullCh;
+
+ if (start < end) {
+ if ((*start == kLessThan) && (*last == kGreaterThan)) {
+ *last = kNullCh;
+
+ // first instance of <...> wins
+ // also, do not allow hrefs after the first param was seen
+ if (href.IsEmpty() && !seenParameters) {
+ href = (start + 1);
+ href.StripWhitespace();
+ }
+ } else {
+ char16_t* equals = start;
+ seenParameters = true;
+
+ while ((*equals != kNullCh) && (*equals != kEqual)) {
+ equals++;
+ }
+
+ if (*equals != kNullCh) {
+ *equals = kNullCh;
+ nsAutoString attr(start);
+ attr.StripWhitespace();
+
+ char16_t* value = ++equals;
+ while (nsCRT::IsAsciiSpace(*value)) {
+ value++;
+ }
+
+ if ((*value == kQuote) && (*value == *last)) {
+ *last = kNullCh;
+ value++;
+ }
+
+ if (wasQuotedString) {
+ // unescape in-place
+ char16_t* unescaped = value;
+ char16_t *src = value;
+
+ while (*src != kNullCh) {
+ if (*src == kBackSlash && *(src + 1) != kNullCh) {
+ src++;
+ }
+ *unescaped++ = *src++;
+ }
+
+ *unescaped = kNullCh;
+ }
+
+ if (attr.LowerCaseEqualsLiteral("rel")) {
+ if (rel.IsEmpty()) {
+ rel = value;
+ rel.CompressWhitespace();
+ }
+ } else if (attr.LowerCaseEqualsLiteral("title")) {
+ if (title.IsEmpty()) {
+ title = value;
+ title.CompressWhitespace();
+ }
+ } else if (attr.LowerCaseEqualsLiteral("title*")) {
+ if (titleStar.IsEmpty() && !wasQuotedString) {
+ // RFC 5987 encoding; uses token format only, so skip if we get
+ // here with a quoted-string
+ nsAutoString tmp;
+ tmp = value;
+ if (Decode5987Format(tmp)) {
+ titleStar = tmp;
+ titleStar.CompressWhitespace();
+ } else {
+ // header value did not parse, throw it away
+ titleStar.Truncate();
+ }
+ }
+ } else if (attr.LowerCaseEqualsLiteral("type")) {
+ if (type.IsEmpty()) {
+ type = value;
+ type.StripWhitespace();
+ }
+ } else if (attr.LowerCaseEqualsLiteral("media")) {
+ if (media.IsEmpty()) {
+ media = value;
+
+ // The HTML5 spec is formulated in terms of the CSS3 spec,
+ // which specifies that media queries are case insensitive.
+ nsContentUtils::ASCIIToLower(media);
+ }
+ } else if (attr.LowerCaseEqualsLiteral("anchor")) {
+ if (anchor.IsEmpty()) {
+ anchor = value;
+ anchor.StripWhitespace();
+ }
+ } else if (attr.LowerCaseEqualsLiteral("crossorigin")) {
+ if (crossOrigin.IsVoid()) {
+ crossOrigin.SetIsVoid(false);
+ crossOrigin = value;
+ crossOrigin.StripWhitespace();
+ }
+ }
+ }
+ }
+ }
+
+ if (endCh == kComma) {
+ // hit a comma, process what we've got so far
+
+ href.Trim(" \t\n\r\f"); // trim HTML5 whitespace
+ if (!href.IsEmpty() && !rel.IsEmpty()) {
+ rv = ProcessLink(anchor, href, rel,
+ // prefer RFC 5987 variant over non-I18zed version
+ titleStar.IsEmpty() ? title : titleStar,
+ type, media, crossOrigin);
+ }
+
+ href.Truncate();
+ rel.Truncate();
+ title.Truncate();
+ type.Truncate();
+ media.Truncate();
+ anchor.Truncate();
+ crossOrigin.SetIsVoid(true);
+
+ seenParameters = false;
+ }
+
+ start = ++end;
+ }
+
+ href.Trim(" \t\n\r\f"); // trim HTML5 whitespace
+ if (!href.IsEmpty() && !rel.IsEmpty()) {
+ rv = ProcessLink(anchor, href, rel,
+ // prefer RFC 5987 variant over non-I18zed version
+ titleStar.IsEmpty() ? title : titleStar,
+ type, media, crossOrigin);
+ }
+
+ return rv;
+}
+
+
+nsresult
+nsContentSink::ProcessLink(const nsSubstring& aAnchor, const nsSubstring& aHref,
+ const nsSubstring& aRel, const nsSubstring& aTitle,
+ const nsSubstring& aType, const nsSubstring& aMedia,
+ const nsSubstring& aCrossOrigin)
+{
+ uint32_t linkTypes =
+ nsStyleLinkElement::ParseLinkTypes(aRel, mDocument->NodePrincipal());
+
+ // 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 (!LinkContextIsOurDocument(aAnchor)) {
+ return NS_OK;
+ }
+
+ if (!nsContentUtils::PrefetchEnabled(mDocShell)) {
+ return NS_OK;
+ }
+
+ bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH;
+ // prefetch href if relation is "next" or "prefetch"
+ if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) {
+ PrefetchHref(aHref, mDocument, hasPrefetch);
+ }
+
+ if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::eDNS_PREFETCH)) {
+ PrefetchDNS(aHref);
+ }
+
+ if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::ePRECONNECT)) {
+ Preconnect(aHref, aCrossOrigin);
+ }
+
+ // is it a stylesheet link?
+ if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) {
+ return NS_OK;
+ }
+
+ bool isAlternate = linkTypes & nsStyleLinkElement::eALTERNATE;
+ return ProcessStyleLink(nullptr, aHref, isAlternate, aTitle, aType,
+ aMedia);
+}
+
+nsresult
+nsContentSink::ProcessStyleLink(nsIContent* aElement,
+ const nsSubstring& aHref,
+ bool aAlternate,
+ const nsSubstring& aTitle,
+ const nsSubstring& aType,
+ const nsSubstring& aMedia)
+{
+ 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;
+ }
+
+ NS_ASSERTION(!aElement ||
+ aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE,
+ "We only expect processing instructions here");
+
+ nsAutoString integrity;
+ if (aElement) {
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
+ }
+ if (!integrity.IsEmpty()) {
+ MOZ_LOG(dom::SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
+ ("nsContentSink::ProcessStyleLink, integrity=%s",
+ NS_ConvertUTF16toUTF8(integrity).get()));
+ }
+
+ // If this is a fragment parser, we don't want to observe.
+ // We don't support CORS for processing instructions
+ bool isAlternate;
+ rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate,
+ CORS_NONE, mDocument->GetReferrerPolicy(),
+ integrity, mRunsToCompletion ? nullptr : this,
+ &isAlternate);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isAlternate && !mRunsToCompletion) {
+ ++mPendingSheetCount;
+ mScriptLoader->AddParserBlockingScriptExecutionBlocker();
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsContentSink::ProcessMETATag(nsIContent* aContent)
+{
+ NS_ASSERTION(aContent, "missing meta-element");
+
+ nsresult rv = NS_OK;
+
+ // set any HTTP-EQUIV data into document's header data as well as url
+ nsAutoString header;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header);
+ if (!header.IsEmpty()) {
+ // Ignore META REFRESH when document is sandboxed from automatic features.
+ nsContentUtils::ASCIIToLower(header);
+ if (nsGkAtoms::refresh->Equals(header) &&
+ (mDocument->GetSandboxFlags() & SANDBOXED_AUTOMATIC_FEATURES)) {
+ return NS_OK;
+ }
+
+ nsAutoString result;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
+ if (!result.IsEmpty()) {
+ nsCOMPtr<nsIAtom> fieldAtom(NS_Atomize(header));
+ rv = ProcessHeaderData(fieldAtom, result, aContent);
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ nsGkAtoms::handheldFriendly, eIgnoreCase)) {
+ nsAutoString result;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::content, result);
+ if (!result.IsEmpty()) {
+ nsContentUtils::ASCIIToLower(result);
+ mDocument->SetHeaderData(nsGkAtoms::handheldFriendly, result);
+ }
+ }
+
+ return rv;
+}
+
+
+void
+nsContentSink::PrefetchHref(const nsAString &aHref,
+ nsINode *aSource,
+ bool aExplicit)
+{
+ nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
+ if (prefetchService) {
+ // construct URI using document charset
+ const nsACString &charset = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref,
+ charset.IsEmpty() ? nullptr : PromiseFlatCString(charset).get(),
+ mDocument->GetDocBaseURI());
+ if (uri) {
+ nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aSource);
+ prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit);
+ }
+ }
+}
+
+void
+nsContentSink::PrefetchDNS(const nsAString &aHref)
+{
+ nsAutoString hostname;
+
+ if (StringBeginsWith(aHref, NS_LITERAL_STRING("//"))) {
+ 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);
+ }
+ }
+
+ if (!hostname.IsEmpty() && nsHTMLDNSPrefetch::IsAllowed(mDocument)) {
+ nsHTMLDNSPrefetch::PrefetchLow(hostname);
+ }
+}
+
+void
+nsContentSink::Preconnect(const nsAString& aHref, const nsAString& aCrossOrigin)
+{
+ // construct URI using document charset
+ const nsACString& charset = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), aHref,
+ charset.IsEmpty() ? nullptr : PromiseFlatCString(charset).get(),
+ mDocument->GetDocBaseURI());
+
+ if (uri && mDocument) {
+ mDocument->MaybePreconnect(uri, dom::Element::StringToCORSMode(aCrossOrigin));
+ }
+}
+
+nsresult
+nsContentSink::SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache,
+ nsIURI *aManifestURI,
+ bool aFetchedWithHTTPGetOrEquiv,
+ CacheSelectionAction *aAction)
+{
+ nsresult rv;
+
+ *aAction = CACHE_SELECTION_NONE;
+
+ nsCOMPtr<nsIApplicationCacheContainer> applicationCacheDocument =
+ do_QueryInterface(mDocument);
+ NS_ASSERTION(applicationCacheDocument,
+ "mDocument must implement nsIApplicationCacheContainer.");
+
+ if (aLoadApplicationCache) {
+ nsCOMPtr<nsIURI> groupURI;
+ rv = aLoadApplicationCache->GetManifestURI(getter_AddRefs(groupURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool equal = false;
+ rv = groupURI->Equals(aManifestURI, &equal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!equal) {
+ // This is a foreign entry, force a reload to avoid loading the foreign
+ // entry. The entry will be marked as foreign to avoid loading it again.
+
+ *aAction = CACHE_SELECTION_RELOAD;
+ }
+ else {
+ // The http manifest attribute URI is equal to the manifest URI of
+ // the cache the document was loaded from - associate the document with
+ // that cache and invoke the cache update process.
+#ifdef DEBUG
+ nsAutoCString docURISpec, clientID;
+ mDocumentURI->GetAsciiSpec(docURISpec);
+ aLoadApplicationCache->GetClientID(clientID);
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_CALLS,
+ ("Selection: assigning app cache %s to document %s",
+ clientID.get(), docURISpec.get()));
+#endif
+
+ rv = applicationCacheDocument->SetApplicationCache(aLoadApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Document will be added as implicit entry to the cache as part of
+ // the update process.
+ *aAction = CACHE_SELECTION_UPDATE;
+ }
+ }
+ else {
+ // The document was not loaded from an application cache
+ // Here we know the manifest has the same origin as the
+ // document. There is call to CheckMayLoad() on it above.
+
+ if (!aFetchedWithHTTPGetOrEquiv) {
+ // The document was not loaded using HTTP GET or equivalent
+ // method. The spec says to run the cache selection algorithm w/o
+ // the manifest specified.
+ *aAction = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST;
+ }
+ else {
+ // Always do an update in this case
+ *aAction = CACHE_SELECTION_UPDATE;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsContentSink::SelectDocAppCacheNoManifest(nsIApplicationCache *aLoadApplicationCache,
+ nsIURI **aManifestURI,
+ CacheSelectionAction *aAction)
+{
+ *aManifestURI = nullptr;
+ *aAction = CACHE_SELECTION_NONE;
+
+ nsresult rv;
+
+ if (aLoadApplicationCache) {
+ // The document was loaded from an application cache, use that
+ // application cache as the document's application cache.
+ nsCOMPtr<nsIApplicationCacheContainer> applicationCacheDocument =
+ do_QueryInterface(mDocument);
+ NS_ASSERTION(applicationCacheDocument,
+ "mDocument must implement nsIApplicationCacheContainer.");
+
+#ifdef DEBUG
+ nsAutoCString docURISpec, clientID;
+ mDocumentURI->GetAsciiSpec(docURISpec);
+ aLoadApplicationCache->GetClientID(clientID);
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_CALLS,
+ ("Selection, no manifest: assigning app cache %s to document %s",
+ clientID.get(), docURISpec.get()));
+#endif
+
+ rv = applicationCacheDocument->SetApplicationCache(aLoadApplicationCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Return the uri and invoke the update process for the selected
+ // application cache.
+ rv = aLoadApplicationCache->GetManifestURI(aManifestURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aAction = CACHE_SELECTION_UPDATE;
+ }
+
+ return NS_OK;
+}
+
+void
+nsContentSink::ProcessOfflineManifest(nsIContent *aElement)
+{
+ // Only check the manifest for root document nodes.
+ if (aElement != mDocument->GetRootElement()) {
+ return;
+ }
+
+ // Don't bother processing offline manifest for documents
+ // without a docshell
+ if (!mDocShell) {
+ return;
+ }
+
+ // Check for a manifest= attribute.
+ nsAutoString manifestSpec;
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec);
+ ProcessOfflineManifest(manifestSpec);
+}
+
+void
+nsContentSink::ProcessOfflineManifest(const nsAString& aManifestSpec)
+{
+ // Don't bother processing offline manifest for documents
+ // without a docshell
+ if (!mDocShell) {
+ return;
+ }
+
+ // If this document has been interecepted, let's skip the processing of the
+ // manifest.
+ if (nsContentUtils::IsControlledByServiceWorker(mDocument)) {
+ return;
+ }
+
+ // If the docshell's in private browsing mode, we don't want to do any
+ // manifest processing.
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(mDocShell);
+ if (loadContext->UsePrivateBrowsing()) {
+ return;
+ }
+
+ nsresult rv;
+
+ // Grab the application cache the document was loaded from, if any.
+ nsCOMPtr<nsIApplicationCache> applicationCache;
+
+ nsCOMPtr<nsIApplicationCacheChannel> applicationCacheChannel =
+ do_QueryInterface(mDocument->GetChannel());
+ if (applicationCacheChannel) {
+ bool loadedFromApplicationCache;
+ rv = applicationCacheChannel->GetLoadedFromApplicationCache(
+ &loadedFromApplicationCache);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (loadedFromApplicationCache) {
+ rv = applicationCacheChannel->GetApplicationCache(
+ getter_AddRefs(applicationCache));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ }
+
+ if (aManifestSpec.IsEmpty() && !applicationCache) {
+ // Not loaded from an application cache, and no manifest
+ // attribute. Nothing to do here.
+ return;
+ }
+
+ CacheSelectionAction action = CACHE_SELECTION_NONE;
+ nsCOMPtr<nsIURI> manifestURI;
+
+ if (aManifestSpec.IsEmpty()) {
+ action = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST;
+ }
+ else {
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI),
+ aManifestSpec, mDocument,
+ mDocumentURI);
+ if (!manifestURI) {
+ return;
+ }
+
+ // Documents must list a manifest from the same origin
+ rv = mDocument->NodePrincipal()->CheckMayLoad(manifestURI, true, false);
+ if (NS_FAILED(rv)) {
+ action = CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST;
+ }
+ else {
+ // Only continue if the document has permission to use offline APIs or
+ // when preferences indicate to permit it automatically.
+ if (!nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal()) &&
+ !nsContentUtils::MaybeAllowOfflineAppByDefault(mDocument->NodePrincipal()) &&
+ !nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal())) {
+ return;
+ }
+
+ bool fetchedWithHTTPGetOrEquiv = false;
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mDocument->GetChannel()));
+ if (httpChannel) {
+ nsAutoCString method;
+ rv = httpChannel->GetRequestMethod(method);
+ if (NS_SUCCEEDED(rv))
+ fetchedWithHTTPGetOrEquiv = method.EqualsLiteral("GET");
+ }
+
+ rv = SelectDocAppCache(applicationCache, manifestURI,
+ fetchedWithHTTPGetOrEquiv, &action);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ }
+
+ if (action == CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST) {
+ rv = SelectDocAppCacheNoManifest(applicationCache,
+ getter_AddRefs(manifestURI),
+ &action);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ switch (action)
+ {
+ case CACHE_SELECTION_NONE:
+ break;
+ case CACHE_SELECTION_UPDATE: {
+ nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
+ do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
+
+ if (updateService) {
+ nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(mDocument);
+ updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI,
+ mDocument->NodePrincipal(), domdoc);
+ }
+ break;
+ }
+ case CACHE_SELECTION_RELOAD: {
+ // This situation occurs only for toplevel documents, see bottom
+ // of SelectDocAppCache method.
+ // The document has been loaded from a different offline cache group than
+ // the manifest it refers to, i.e. this is a foreign entry, mark it as such
+ // and force a reload to avoid loading it. The next attempt will not
+ // choose it.
+
+ applicationCacheChannel->MarkOfflineCacheEntryAsForeign();
+
+ nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(mDocShell);
+
+ webNav->Stop(nsIWebNavigation::STOP_ALL);
+ webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE);
+ break;
+ }
+ default:
+ NS_ASSERTION(false,
+ "Cache selection algorithm didn't decide on proper action");
+ break;
+ }
+}
+
+void
+nsContentSink::ScrollToRef()
+{
+ mDocument->ScrollToRef();
+}
+
+void
+nsContentSink::StartLayout(bool aIgnorePendingSheets)
+{
+ if (mLayoutStarted) {
+ // Nothing to do here
+ return;
+ }
+
+ mDeferredLayoutStart = true;
+
+ if (!aIgnorePendingSheets && WaitForPendingSheets()) {
+ // Bail out; we'll start layout when the sheets load
+ return;
+ }
+
+ mDeferredLayoutStart = false;
+
+ // 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);
+ nsCOMPtr<nsIPresShell> shell = mDocument->GetShell();
+ // 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 (shell && !shell->DidInitialize()) {
+ nsRect r = shell->GetPresContext()->GetVisibleArea();
+ nsCOMPtr<nsIPresShell> shellGrip = shell;
+ nsresult rv = shell->Initialize(r.width, r.height);
+ 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)
+{
+ if (aContainer->GetUncomposedDoc() != mDocument) {
+ // aContainer is not actually in our document anymore.... Just bail out of
+ // here; notifying on our document for this append would be wrong.
+ return;
+ }
+
+ mInNotification++;
+
+ {
+ // Scope so we call EndUpdate before we decrease mInNotification
+ MOZ_AUTO_DOC_UPDATE(mDocument, UPDATE_CONTENT_MODEL, !mBeganUpdate);
+ nsNodeUtils::ContentAppended(aContainer,
+ aContainer->GetChildAt(aStartIndex),
+ 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 (!sNotifyOnTimer || !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 (sNotifyOnTimer && 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;
+
+ mNotificationTimer = do_CreateInstance("@mozilla.org/timer;1",
+ &result);
+ if (NS_SUCCEEDED(result)) {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::WillInterrupt: setting up timer with "
+ "delay %d", delay));
+
+ result =
+ mNotificationTimer->InitWithCallback(this, delay,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(result)) {
+ mNotificationTimer = nullptr;
+ }
+ }
+ }
+ }
+ } else {
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_REFLOW,
+ ("nsContentSink::WillInterrupt: flushing tags "
+ "unconditionally"));
+ result = FlushTags();
+ }
+#endif
+
+ mParsing = false;
+
+ return result;
+}
+
+nsresult
+nsContentSink::WillResumeImpl()
+{
+ SINK_TRACE(static_cast<LogModule*>(gContentSinkLogModuleInfo),
+ SINK_TRACE_CALLS,
+ ("nsContentSink::WillResume: this=%p", this));
+
+ mParsing = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsContentSink::DidProcessATokenImpl()
+{
+ if (mRunsToCompletion || !mParser) {
+ return NS_OK;
+ }
+
+ // Get the current user event time
+ nsIPresShell *shell = mDocument->GetShell();
+ if (!shell) {
+ // 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 (sPendingEventMode != 0 && !mHasPendingEvent &&
+ (mDeflectedCount % sEventProbeRate) == 0) {
+ nsViewManager* vm = shell->GetViewManager();
+ NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ mHasPendingEvent = widget && widget->HasPendingInputEvent();
+ }
+
+ if (mHasPendingEvent && sPendingEventMode == 2) {
+ return NS_ERROR_HTMLPARSER_INTERRUPTED;
+ }
+
+ // Have we processed enough tokens to check time?
+ if (!mHasPendingEvent &&
+ mDeflectedCount < uint32_t(mDynamicLowerValue ? sInteractiveDeflectCount :
+ sPerfDeflectCount)) {
+ 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::FavorPerformanceHint(bool perfOverStarvation, uint32_t starvationDelay)
+{
+ static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+ nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+ if (appShell)
+ appShell->FavorPerformanceHint(perfOverStarvation, starvationDelay);
+}
+
+void
+nsContentSink::BeginUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType)
+{
+ // 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(nsIDocument *aDocument, nsUpdateType aUpdateType)
+{
+ // 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)
+{
+ if (mDocument) {
+ MOZ_ASSERT(aTerminated ||
+ mDocument->GetReadyStateEnum() ==
+ nsIDocument::READYSTATE_LOADING, "Bad readyState");
+ mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE);
+ }
+
+ if (mScriptLoader) {
+ mScriptLoader->ParsingComplete(aTerminated);
+ }
+
+ 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(mParser.forget());
+
+ if (mDynamicLowerValue) {
+ // Reset the performance hint which was set to FALSE
+ // when mDynamicLowerValue was set.
+ FavorPerformanceHint(true, 0);
+ }
+
+ if (!mRunsToCompletion) {
+ mDocument->UnblockOnload(true);
+ }
+}
+
+bool
+nsContentSink::IsScriptExecutingImpl()
+{
+ return !!mScriptLoader->GetCurrentScript();
+}
+
+nsresult
+nsContentSink::WillParseImpl(void)
+{
+ if (mRunsToCompletion || !mDocument) {
+ return NS_OK;
+ }
+
+ nsIPresShell *shell = mDocument->GetShell();
+ if (!shell) {
+ return NS_OK;
+ }
+
+ uint32_t currentTime = PR_IntervalToMicroseconds(PR_IntervalNow());
+
+ if (sEnablePerfMode == 0) {
+ nsViewManager* vm = shell->GetViewManager();
+ NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);
+ uint32_t lastEventTime;
+ vm->GetLastUserEventTime(lastEventTime);
+
+ bool newDynLower =
+ mDocument->IsInBackgroundWindow() ||
+ ((currentTime - mBeginLoadTime) > uint32_t(sInitialPerfTime) &&
+ (currentTime - lastEventTime) < uint32_t(sInteractiveTime));
+
+ if (mDynamicLowerValue != newDynLower) {
+ FavorPerformanceHint(!newDynLower, 0);
+ mDynamicLowerValue = newDynLower;
+ }
+ }
+
+ mDeflectedCount = 0;
+ mHasPendingEvent = false;
+
+ mCurrentParseEndTime = currentTime +
+ (mDynamicLowerValue ? sInteractiveParseTime : sPerfParseTime);
+
+ return NS_OK;
+}
+
+void
+nsContentSink::WillBuildModelImpl()
+{
+ if (!mRunsToCompletion) {
+ mDocument->BlockOnload();
+
+ mBeginLoadTime = PR_IntervalToMicroseconds(PR_IntervalNow());
+ }
+
+ mDocument->ResetScrolledToRefAlready();
+
+ if (mProcessLinkHeaderEvent.get()) {
+ mProcessLinkHeaderEvent.Revoke();
+
+ DoProcessLinkHeader();
+ }
+}
+
+/* static */
+void
+nsContentSink::NotifyDocElementCreated(nsIDocument* aDoc)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDoc);
+ observerService->
+ NotifyObservers(domDoc, "document-element-inserted",
+ EmptyString().get());
+ }
+
+ nsContentUtils::DispatchChromeEvent(aDoc, aDoc,
+ NS_LITERAL_STRING("DOMDocElementInserted"),
+ true, false);
+}
diff --git a/dom/base/nsContentSink.h b/dom/base/nsContentSink.h
new file mode 100644
index 000000000..b1a758874
--- /dev/null
+++ b/dom/base/nsContentSink.h
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsAutoPtr.h"
+#include "nsGkAtoms.h"
+#include "nsITimer.h"
+#include "nsStubDocumentObserver.h"
+#include "nsIContentSink.h"
+#include "mozilla/Logging.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsThreadUtils.h"
+
+class nsIDocument;
+class nsIURI;
+class nsIChannel;
+class nsIDocShell;
+class nsIAtom;
+class nsIChannel;
+class nsIContent;
+class nsNodeInfoManager;
+class nsScriptLoader;
+class nsIApplicationCache;
+
+namespace mozilla {
+namespace css {
+class Loader;
+} // namespace css
+} // 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) \
+ PR_BEGIN_MACRO \
+ if (SINK_LOG_TEST(_lm, _bit)) { \
+ PR_LogPrint _args; \
+ } \
+ PR_END_MACRO
+
+#else
+#define SINK_TRACE(_lm, _bit, _args)
+#endif
+
+#undef SINK_NO_INCREMENTAL
+
+//----------------------------------------------------------------------
+
+// 1/2 second fudge factor for window creation
+#define NS_DELAY_FOR_WINDOW_CREATION 500000
+
+class nsContentSink : public nsICSSLoaderObserver,
+ public nsSupportsWeakReference,
+ public nsStubDocumentObserver,
+ public nsITimerCallback
+{
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsContentSink,
+ nsICSSLoaderObserver)
+ // nsITimerCallback
+ NS_DECL_NSITIMERCALLBACK
+
+ // nsICSSLoaderObserver
+ NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus) override;
+
+ virtual nsresult ProcessMETATag(nsIContent* aContent);
+
+ // nsIContentSink implementation helpers
+ nsresult WillParseImpl(void);
+ nsresult WillInterruptImpl(void);
+ nsresult WillResumeImpl(void);
+ 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();
+ bool LinkContextIsOurDocument(const nsSubstring& aAnchor);
+ bool Decode5987Format(nsAString& aEncoded);
+
+ static void InitializeStatics();
+
+protected:
+ nsContentSink();
+ virtual ~nsContentSink();
+
+ enum CacheSelectionAction {
+ // There is no offline cache manifest specified by the document,
+ // or the document was loaded from a cache other than the one it
+ // specifies via its manifest attribute and IS NOT a top-level
+ // document, or an error occurred during the cache selection
+ // algorithm.
+ CACHE_SELECTION_NONE = 0,
+
+ // The offline cache manifest must be updated.
+ CACHE_SELECTION_UPDATE = 1,
+
+ // The document was loaded from a cache other than the one it
+ // specifies via its manifest attribute and IS a top-level
+ // document. In this case, the document is marked as foreign in
+ // the cache it was loaded from and must be reloaded from the
+ // correct cache (the one it specifies).
+ CACHE_SELECTION_RELOAD = 2,
+
+ // Some conditions require we must reselect the cache without the manifest
+ CACHE_SELECTION_RESELECT_WITHOUT_MANIFEST = 3
+ };
+
+ nsresult Init(nsIDocument* aDoc, nsIURI* aURI,
+ nsISupports* aContainer, nsIChannel* aChannel);
+
+ nsresult ProcessHTTPHeaders(nsIChannel* aChannel);
+ nsresult ProcessHeaderData(nsIAtom* aHeader, const nsAString& aValue,
+ nsIContent* aContent = nullptr);
+ nsresult ProcessLinkHeader(const nsAString& aLinkData);
+ nsresult ProcessLink(const nsSubstring& aAnchor,
+ const nsSubstring& aHref, const nsSubstring& aRel,
+ const nsSubstring& aTitle, const nsSubstring& aType,
+ const nsSubstring& aMedia, const nsSubstring& aCrossOrigin);
+
+ virtual nsresult ProcessStyleLink(nsIContent* aElement,
+ const nsSubstring& aHref,
+ bool aAlternate,
+ const nsSubstring& aTitle,
+ const nsSubstring& aType,
+ const nsSubstring& aMedia);
+
+ void PrefetchHref(const nsAString &aHref, nsINode *aSource,
+ bool aExplicit);
+
+ // 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);
+
+ // There is an offline cache manifest attribute specified and the
+ // document is allowed to use the offline cache. Process the cache
+ // selection algorithm for this document and the manifest. Result is
+ // an action that must be taken on the manifest, see
+ // CacheSelectionAction enum above.
+ //
+ // @param aLoadApplicationCache
+ // The application cache from which the load originated, if
+ // any.
+ // @param aManifestURI
+ // The manifest URI listed in the document.
+ // @param aFetchedWithHTTPGetOrEquiv
+ // TRUE if this was fetched using the HTTP GET method.
+ // @param aAction
+ // Out parameter, returns the action that should be performed
+ // by the calling function.
+ nsresult SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache,
+ nsIURI *aManifestURI,
+ bool aFetchedWithHTTPGetOrEquiv,
+ CacheSelectionAction *aAction);
+
+ // There is no offline cache manifest attribute specified. Process
+ // the cache selection algorithm w/o the manifest. Result is an
+ // action that must be taken, see CacheSelectionAction enum
+ // above. In case the offline cache manifest has to be updated the
+ // manifest URI is returned in aManifestURI.
+ //
+ // @param aLoadApplicationCache
+ // The application cache from which the load originated, if
+ // any.
+ // @param aManifestURI
+ // Out parameter, returns the manifest URI of the cache that
+ // was selected.
+ // @param aAction
+ // Out parameter, returns the action that should be performed
+ // by the calling function.
+ nsresult SelectDocAppCacheNoManifest(nsIApplicationCache *aLoadApplicationCache,
+ nsIURI **aManifestURI,
+ CacheSelectionAction *aAction);
+
+public:
+ // Searches for the offline cache manifest attribute and calls one
+ // of the above defined methods to select the document's application
+ // cache, let it be associated with the document and eventually
+ // schedule the cache update process.
+ // This method MUST be called with the empty string as the argument
+ // when there is no manifest attribute!
+ void ProcessOfflineManifest(const nsAString& aManifestSpec);
+
+ // Extracts the manifest attribute from the element if it is the root
+ // element and calls the above method.
+ void ProcessOfflineManifest(nsIContent *aElement);
+
+ // 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.
+ 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(nsIDocument* aDoc);
+
+protected:
+ void
+ FavorPerformanceHint(bool perfOverStarvation, uint32_t starvationDelay);
+
+ inline int32_t GetNotificationInterval()
+ {
+ if (mDynamicLowerValue) {
+ return 1000;
+ }
+
+ return sNotificationInterval;
+ }
+
+ virtual nsresult FlushTags() = 0;
+
+ // 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; }
+
+ void DoProcessLinkHeader();
+
+ void StopDeflecting() {
+ mDeflectedCount = sPerfDeflectCount;
+ }
+
+protected:
+
+ nsCOMPtr<nsIDocument> mDocument;
+ RefPtr<nsParserBase> mParser;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIDocShell> mDocShell;
+ RefPtr<mozilla::css::Loader> mCSSLoader;
+ RefPtr<nsNodeInfoManager> mNodeInfoManager;
+ RefPtr<nsScriptLoader> 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;
+
+ // Have we already called BeginUpdate for this set of content changes?
+ uint8_t mBeganUpdate : 1;
+ 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;
+
+ //
+ // -- 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;
+
+ // Do we notify based on time?
+ static bool sNotifyOnTimer;
+ // Back off timer notification after count.
+ static int32_t sBackoffCount;
+ // Notification interval in microseconds
+ static int32_t sNotificationInterval;
+ // How many times to deflect in interactive/perf modes
+ static int32_t sInteractiveDeflectCount;
+ static int32_t sPerfDeflectCount;
+ // 0 = don't check for pending events
+ // 1 = don't deflect if there are pending events
+ // 2 = bail if there are pending events
+ static int32_t sPendingEventMode;
+ // How often to probe for pending events. 1=every token
+ static int32_t sEventProbeRate;
+ // How long to stay off the event loop in interactive/perf modes
+ static int32_t sInteractiveParseTime;
+ static int32_t sPerfParseTime;
+ // How long to be in interactive mode after an event
+ static int32_t sInteractiveTime;
+ // How long to stay in perf mode after initial loading
+ static int32_t sInitialPerfTime;
+ // Should we switch between perf-mode and interactive-mode
+ static int32_t sEnablePerfMode;
+};
+
+#endif // _nsContentSink_h_
diff --git a/dom/base/nsContentTypeParser.h b/dom/base/nsContentTypeParser.h
new file mode 100644
index 000000000..2476ec1e3
--- /dev/null
+++ b/dom/base/nsContentTypeParser.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 nsContentTypeParser_h
+#define nsContentTypeParser_h
+
+#include "nsAString.h"
+
+class nsIMIMEHeaderParam;
+
+class nsContentTypeParser {
+public:
+ explicit nsContentTypeParser(const nsAString& aString);
+ ~nsContentTypeParser();
+
+ nsresult GetParameter(const char* aParameterName, nsAString& aResult) const;
+ nsresult GetType(nsAString& aResult) const;
+
+private:
+ NS_ConvertUTF16toUTF8 mString;
+ nsIMIMEHeaderParam* mService;
+};
+
+#endif
+
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
new file mode 100644
index 000000000..29d28f8ce
--- /dev/null
+++ b/dom/base/nsContentUtils.cpp
@@ -0,0 +1,9783 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <math.h>
+
+#include "DecoderTraits.h"
+#include "harfbuzz/hb.h"
+#include "imgICache.h"
+#include "imgIContainer.h"
+#include "imgINotificationObserver.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Value.h"
+#include "Layers.h"
+#include "MediaDecoder.h"
+// nsNPAPIPluginInstance must be included before nsIDocument.h, which is included in mozAutoDocUpdate.h.
+#include "nsNPAPIPluginInstance.h"
+#include "gfxDrawable.h"
+#include "gfxPrefs.h"
+#include "ImageOps.h"
+#include "mozAutoDocUpdate.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/AutoTimelineMarker.h"
+#include "mozilla/Base64.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/FileSystemSecurity.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/HTMLContentElement.h"
+#include "mozilla/dom/HTMLShadowElement.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/dom/TextDecoder.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/workers/ServiceWorkerManager.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/TextEvents.h"
+#include "nsArrayUtils.h"
+#include "nsAString.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "nsBindingManager.h"
+#include "nsCaret.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsCOMPtr.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentDLF.h"
+#include "nsContentList.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentSecurityManager.h"
+#include "nsCPrefetchService.h"
+#include "nsCRT.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCycleCollector.h"
+#include "nsDataHashtable.h"
+#include "nsDocShellCID.h"
+#include "nsDocument.h"
+#include "nsDOMCID.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "nsDOMJSUtils.h"
+#include "nsDOMMutationObserver.h"
+#include "nsError.h"
+#include "nsFocusManager.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGenericHTMLFrameElement.h"
+#include "nsGkAtoms.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsHtml5Module.h"
+#include "nsHtml5StringParser.h"
+#include "nsIAddonPolicyService.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsICategoryManager.h"
+#include "nsIChannelEventSink.h"
+#include "nsICharsetDetectionObserver.h"
+#include "nsIChromeRegistry.h"
+#include "nsIConsoleService.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIContentSink.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocument.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMDocumentType.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDOMXULCommandEvent.h"
+#include "nsIDragService.h"
+#include "nsIEditor.h"
+#include "nsIFormControl.h"
+#include "nsIForm.h"
+#include "nsIFragmentContentSink.h"
+#include "nsContainerFrame.h"
+#include "nsIHTMLDocument.h"
+#include "nsIIdleService.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIIOService.h"
+#include "nsILineBreaker.h"
+#include "nsILoadContext.h"
+#include "nsILoadGroup.h"
+#include "nsIMemoryReporter.h"
+#include "nsIMIMEHeaderParam.h"
+#include "nsIMIMEService.h"
+#include "nsINode.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIOfflineCacheUpdate.h"
+#include "nsIParser.h"
+#include "nsIParserService.h"
+#include "nsIPermissionManager.h"
+#include "nsIPluginHost.h"
+#include "nsIRequest.h"
+#include "nsIRunnable.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScrollable.h"
+#include "nsIStreamConverterService.h"
+#include "nsIStringBundle.h"
+#include "nsIURI.h"
+#include "nsIURIWithPrincipal.h"
+#include "nsIURL.h"
+#include "nsIWebNavigation.h"
+#include "nsIWindowMediator.h"
+#include "nsIWordBreaker.h"
+#include "nsIXPConnect.h"
+#include "nsJSUtils.h"
+#include "nsLWBrkCIID.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsNodeInfoManager.h"
+#include "nsNullPrincipal.h"
+#include "nsParserCIID.h"
+#include "nsParserConstants.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+#include "nsPrintfCString.h"
+#include "nsReferencedElement.h"
+#include "nsSandboxFlags.h"
+#include "nsScriptSecurityManager.h"
+#include "nsStreamUtils.h"
+#include "nsTextEditorState.h"
+#include "nsTextFragment.h"
+#include "nsTextNode.h"
+#include "nsThreadUtils.h"
+#include "nsUnicharUtilCIID.h"
+#include "nsUnicodeProperties.h"
+#include "nsViewManager.h"
+#include "nsViewportInfo.h"
+#include "nsWidgetsCID.h"
+#include "nsIWindowProvider.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsXULPopupManager.h"
+#include "xpcprivate.h" // nsXPConnect
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "nsContentTypeParser.h"
+#include "nsICookiePermission.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsICookieService.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/BloomFilter.h"
+#include "TabChild.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/TabGroup.h"
+
+#include "nsIBidiKeyboard.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);
+
+class imgLoader;
+
+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;
+nsIParserService *nsContentUtils::sParserService = nullptr;
+nsNameSpaceManager *nsContentUtils::sNameSpaceManager;
+nsIIOService *nsContentUtils::sIOService;
+nsIUUIDGenerator *nsContentUtils::sUUIDGenerator;
+nsIConsoleService *nsContentUtils::sConsoleService;
+nsDataHashtable<nsISupportsHashKey, EventNameMapping>* nsContentUtils::sAtomEventTable = nullptr;
+nsDataHashtable<nsStringHashKey, EventNameMapping>* nsContentUtils::sStringEventTable = nullptr;
+nsCOMArray<nsIAtom>* nsContentUtils::sUserDefinedEvents = nullptr;
+nsIStringBundleService *nsContentUtils::sStringBundleService;
+nsIStringBundle *nsContentUtils::sStringBundles[PropertiesFile_COUNT];
+nsIContentPolicy *nsContentUtils::sContentPolicyService;
+bool nsContentUtils::sTriedToGetContentPolicy = false;
+nsILineBreaker *nsContentUtils::sLineBreaker;
+nsIWordBreaker *nsContentUtils::sWordBreaker;
+nsIBidiKeyboard *nsContentUtils::sBidiKeyboard = nullptr;
+uint32_t nsContentUtils::sScriptBlockerCount = 0;
+uint32_t nsContentUtils::sDOMNodeRemovedSuppressCount = 0;
+uint32_t nsContentUtils::sMicroTaskLevel = 0;
+AutoTArray<nsCOMPtr<nsIRunnable>, 8>* nsContentUtils::sBlockedScriptRunners = nullptr;
+uint32_t nsContentUtils::sRunnersCountAtFirstBlocker = 0;
+nsIInterfaceRequestor* nsContentUtils::sSameOriginChecker = nullptr;
+
+bool nsContentUtils::sIsHandlingKeyBoardEvent = false;
+bool nsContentUtils::sAllowXULXBL_for_file = 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;
+bool nsContentUtils::sIsFullScreenApiEnabled = false;
+bool nsContentUtils::sIsUnprefixedFullscreenApiEnabled = false;
+bool nsContentUtils::sTrustedFullScreenOnly = true;
+bool nsContentUtils::sIsCutCopyAllowed = true;
+bool nsContentUtils::sIsFrameTimingPrefEnabled = false;
+bool nsContentUtils::sIsPerformanceTimingEnabled = false;
+bool nsContentUtils::sIsResourceTimingEnabled = false;
+bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
+bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
+bool nsContentUtils::sEncodeDecodeURLHash = false;
+bool nsContentUtils::sGettersDecodeURLHash = false;
+bool nsContentUtils::sPrivacyResistFingerprinting = false;
+bool nsContentUtils::sSendPerformanceTimingNotifications = false;
+bool nsContentUtils::sUseActivityCursor = false;
+
+uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
+
+uint32_t nsContentUtils::sCookiesLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
+uint32_t nsContentUtils::sCookiesBehavior = nsICookieService::BEHAVIOR_ACCEPT;
+
+nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
+nsIParser* nsContentUtils::sXMLFragmentParser = nullptr;
+nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
+bool nsContentUtils::sFragmentParsingActive = false;
+
+#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
+bool nsContentUtils::sDOMWindowDumpEnabled;
+#endif
+
+bool nsContentUtils::sDoNotTrackEnabled = false;
+
+mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump");
+
+// Subset of http://www.whatwg.org/specs/web-apps/current-work/#autofill-field-name
+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 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 NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID);
+static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
+
+static PLDHashTable* sEventListenerManagersHash;
+
+class DOMEventListenerManagersHashReporter final : public nsIMemoryReporter
+{
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ ~DOMEventListenerManagersHashReporter() {}
+
+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() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+};
+
+class CharsetDetectionObserver final : public nsICharsetDetectionObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Notify(const char *aCharset, nsDetectionConfident aConf) override
+ {
+ mCharset = aCharset;
+ return NS_OK;
+ }
+
+ const nsACString& GetResult() const
+ {
+ return mCharset;
+ }
+
+private:
+ nsCString mCharset;
+};
+
+} // namespace
+
+/* static */
+TimeDuration
+nsContentUtils::HandlingUserInputTimeout()
+{
+ return TimeDuration::FromMilliseconds(sHandlingInputTimeout);
+}
+
+// static
+nsresult
+nsContentUtils::Init()
+{
+ if (sInitialized) {
+ NS_WARNING("Init() called twice");
+
+ return NS_OK;
+ }
+
+ sNameSpaceManager = nsNameSpaceManager::GetInstance();
+ NS_ENSURE_TRUE(sNameSpaceManager, NS_ERROR_OUT_OF_MEMORY);
+
+ sXPConnect = nsXPConnect::XPConnect();
+
+ sSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
+ if(!sSecurityManager)
+ return NS_ERROR_FAILURE;
+ NS_ADDREF(sSecurityManager);
+
+ sSecurityManager->GetSystemPrincipal(&sSystemPrincipal);
+ MOZ_ASSERT(sSystemPrincipal);
+
+ // We use the constructor here because we want infallible initialization; we
+ // apparently don't care whether sNullSubjectPrincipal has a sane URI or not.
+ RefPtr<nsNullPrincipal> nullPrincipal = new nsNullPrincipal();
+ nullPrincipal->Init();
+ nullPrincipal.forget(&sNullSubjectPrincipal);
+
+ nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService);
+ if (NS_FAILED(rv)) {
+ // This makes life easier, but we can live without it.
+
+ sIOService = nullptr;
+ }
+
+ rv = CallGetService(NS_LBRK_CONTRACTID, &sLineBreaker);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CallGetService(NS_WBRK_CONTRACTID, &sWordBreaker);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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>;
+
+ Preferences::AddBoolVarCache(&sAllowXULXBL_for_file,
+ "dom.allow_XUL_XBL_for_file");
+
+ Preferences::AddBoolVarCache(&sIsFullScreenApiEnabled,
+ "full-screen-api.enabled");
+
+ Preferences::AddBoolVarCache(&sIsUnprefixedFullscreenApiEnabled,
+ "full-screen-api.unprefix.enabled");
+
+ Preferences::AddBoolVarCache(&sTrustedFullScreenOnly,
+ "full-screen-api.allow-trusted-requests-only");
+
+ Preferences::AddBoolVarCache(&sIsCutCopyAllowed,
+ "dom.allow_cut_copy", true);
+
+ Preferences::AddBoolVarCache(&sIsPerformanceTimingEnabled,
+ "dom.enable_performance", true);
+
+ Preferences::AddBoolVarCache(&sIsResourceTimingEnabled,
+ "dom.enable_resource_timing", true);
+
+ Preferences::AddBoolVarCache(&sIsUserTimingLoggingEnabled,
+ "dom.performance.enable_user_timing_logging", false);
+
+ Preferences::AddBoolVarCache(&sIsFrameTimingPrefEnabled,
+ "dom.enable_frame_timing", false);
+
+ Preferences::AddBoolVarCache(&sIsExperimentalAutocompleteEnabled,
+ "dom.forms.autocomplete.experimental", false);
+
+ Preferences::AddBoolVarCache(&sEncodeDecodeURLHash,
+ "dom.url.encode_decode_hash", false);
+
+ Preferences::AddBoolVarCache(&sGettersDecodeURLHash,
+ "dom.url.getters_decode_hash", false);
+
+ Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
+ "privacy.resistFingerprinting", false);
+
+ Preferences::AddUintVarCache(&sHandlingInputTimeout,
+ "dom.event.handling-user-input-time-limit",
+ 1000);
+
+ Preferences::AddBoolVarCache(&sSendPerformanceTimingNotifications,
+ "dom.performance.enable_notify_performance_timing", false);
+
+ Preferences::AddUintVarCache(&sCookiesLifetimePolicy,
+ "network.cookie.lifetimePolicy",
+ nsICookieService::ACCEPT_NORMALLY);
+
+ Preferences::AddUintVarCache(&sCookiesBehavior,
+ "network.cookie.cookieBehavior",
+ nsICookieService::BEHAVIOR_ACCEPT);
+
+#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
+ Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled,
+ "browser.dom.window.dump.enabled");
+#endif
+
+ Preferences::AddBoolVarCache(&sDoNotTrackEnabled,
+ "privacy.donottrackheader.enabled", false);
+
+ Preferences::AddBoolVarCache(&sUseActivityCursor,
+ "ui.use_activity_cursor", false);
+
+ Element::InitCCCallbacks();
+
+ nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ uuidGenerator.forget(&sUUIDGenerator);
+
+ sInitialized = true;
+
+ return NS_OK;
+}
+
+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::services::GetStringBundleService();
+ 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");
+ nsXPIDLString shiftModifier;
+ nsXPIDLString metaModifier;
+ nsXPIDLString osModifier;
+ nsXPIDLString altModifier;
+ nsXPIDLString controlModifier;
+ nsXPIDLString modifierSeparator;
+ if (bundle) {
+ //macs use symbols for each modifier key, so fetch each from the bundle, which also covers i18n
+ bundle->GetStringFromName(u"VK_SHIFT", getter_Copies(shiftModifier));
+ bundle->GetStringFromName(u"VK_META", getter_Copies(metaModifier));
+ bundle->GetStringFromName(u"VK_WIN", getter_Copies(osModifier));
+ bundle->GetStringFromName(u"VK_ALT", getter_Copies(altModifier));
+ bundle->GetStringFromName(u"VK_CONTROL", getter_Copies(controlModifier));
+ bundle->GetStringFromName(u"MODIFIER_SEPARATOR", getter_Copies(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);
+}
+
+// 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)
+{
+ switch(aMapping.mMessage) {
+#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
+ case message_: return nsGkAtoms::on##name_ == aMapping.mAtom;
+#include "mozilla/EventNameList.h"
+#undef MESSAGE_TO_EVENT
+ default:
+ break;
+ }
+ return false;
+}
+
+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 NON_IDL_EVENT EVENT
+#include "mozilla/EventNameList.h"
+#undef WINDOW_ONLY_EVENT
+#undef NON_IDL_EVENT
+#undef EVENT
+ { nullptr }
+ };
+
+ sAtomEventTable = new nsDataHashtable<nsISupportsHashKey, EventNameMapping>(
+ ArrayLength(eventArray));
+ sStringEventTable = new nsDataHashtable<nsStringHashKey, EventNameMapping>(
+ ArrayLength(eventArray));
+ sUserDefinedEvents = new nsCOMArray<nsIAtom>(64);
+
+ // Subtract one from the length because of the trailing null
+ for (uint32_t i = 0; i < ArrayLength(eventArray) - 1; ++i) {
+ sAtomEventTable->Put(eventArray[i].mAtom, eventArray[i]);
+ if (ShouldAddEventToStringEventTable(eventArray[i])) {
+ sStringEventTable->Put(
+ 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->Put(touchEventArray[i].mAtom, touchEventArray[i]);
+ sStringEventTable->Put(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* end = aAsciiBase64String.EndReading();
+ nsString trimmedString;
+ if (!trimmedString.SetCapacity(aAsciiBase64String.Length(), fallible)) {
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+ while (start < end) {
+ if (!nsContentUtils::IsHTMLWhitespace(*start)) {
+ trimmedString.Append(*start);
+ }
+ start++;
+ }
+ nsresult rv = Base64Decode(trimmedString, aBinaryData);
+ if (NS_FAILED(rv) && rv == NS_ERROR_INVALID_ARG) {
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+ return rv;
+}
+
+bool
+nsContentUtils::IsAutocompleteEnabled(nsIDOMHTMLInputElement* aInput)
+{
+ NS_PRECONDITION(aInput, "aInput should not be null!");
+
+ nsAutoString autocomplete;
+ aInput->GetAutocomplete(autocomplete);
+
+ if (autocomplete.IsEmpty()) {
+ nsCOMPtr<nsIDOMHTMLFormElement> form;
+ aInput->GetForm(getter_AddRefs(form));
+ 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)
+{
+ if (!aAttr ||
+ aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
+ return aCachedState;
+ }
+
+ return InternalSerializeAutocompleteAttribute(aAttr, aInfo);
+}
+
+/**
+ * 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)
+{
+ // No sandbox 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;
+
+ nsAutoString str;
+ bool result = enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false);
+ if (result) {
+ // Off/Automatic/Normal categories.
+ if (enumValue.Equals(NS_LITERAL_STRING("off"), eIgnoreCase) ||
+ enumValue.Equals(NS_LITERAL_STRING("on"), eIgnoreCase)) {
+ if (numTokens > 1) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ enumValue.ToString(str);
+ ASCIIToLower(str);
+ aInfo.mFieldName.Assign(str);
+ return eAutocompleteAttrState_Valid;
+ }
+
+ // Only allow on/off if experimental @autocomplete values aren't enabled.
+ if (!sIsExperimentalAutocompleteEnabled) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ // Normal category
+ if (numTokens > 2) {
+ return eAutocompleteAttrState_Invalid;
+ }
+ category = eAutocompleteCategory_NORMAL;
+ } else { // Check if the last token is of the contact category instead.
+ // Only allow on/off if experimental @autocomplete values aren't enabled.
+ if (!sIsExperimentalAutocompleteEnabled) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ result = enumValue.ParseEnumValue(tokenString, kAutocompleteContactFieldNameTable, false);
+ if (!result || numTokens > 3) {
+ return eAutocompleteAttrState_Invalid;
+ }
+
+ category = eAutocompleteCategory_CONTACT;
+ }
+
+ enumValue.ToString(str);
+ ASCIIToLower(str);
+ aInfo.mFieldName.Assign(str);
+
+ // 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) {
+ 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;
+ }
+
+ // Clear the fields as the autocomplete attribute is invalid.
+ aInfo.mAddressType.Truncate();
+ aInfo.mContactType.Truncate();
+ aInfo.mFieldName.Truncate();
+
+ return eAutocompleteAttrState_Invalid;
+}
+
+// Parse an integer according to HTML spec
+int32_t
+nsContentUtils::ParseHTMLInteger(const nsAString& aValue,
+ ParseHTMLIntegerResultFlags *aResult)
+{
+ int result = eParseHTMLInteger_NoFlags;
+
+ nsAString::const_iterator iter, end;
+ aValue.BeginReading(iter);
+ aValue.EndReading(end);
+
+ while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
+ result |= eParseHTMLInteger_NonStandard;
+ ++iter;
+ }
+
+ if (iter == end) {
+ result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorNoValue;
+ *aResult = (ParseHTMLIntegerResultFlags)result;
+ return 0;
+ }
+
+ int sign = 1;
+ if (*iter == char16_t('-')) {
+ sign = -1;
+ ++iter;
+ } else if (*iter == char16_t('+')) {
+ result |= eParseHTMLInteger_NonStandard;
+ ++iter;
+ }
+
+ bool foundValue = false;
+ CheckedInt32 value = 0;
+
+ // Check for leading zeros first.
+ uint64_t leadingZeros = 0;
+ while (iter != end) {
+ if (*iter != char16_t('0')) {
+ break;
+ }
+
+ ++leadingZeros;
+ foundValue = true;
+ ++iter;
+ }
+
+ while (iter != end) {
+ if (*iter >= char16_t('0') && *iter <= char16_t('9')) {
+ value = (value * 10) + (*iter - char16_t('0')) * sign;
+ ++iter;
+ if (!value.isValid()) {
+ result |= eParseHTMLInteger_Error | eParseHTMLInteger_ErrorOverflow;
+ break;
+ } else {
+ foundValue = true;
+ }
+ } else if (*iter == char16_t('%')) {
+ ++iter;
+ result |= eParseHTMLInteger_IsPercent;
+ break;
+ } 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 != end) {
+ result |= eParseHTMLInteger_DidNotConsumeAllInput;
+ }
+
+ *aResult = (ParseHTMLIntegerResultFlags)result;
+ return value.isValid() ? value.value() : 0;
+}
+
+#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, nsIAtom *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)
+{
+ return aName.LowerCaseEqualsLiteral("javascript") ||
+ aName.LowerCaseEqualsLiteral("livescript") ||
+ aName.LowerCaseEqualsLiteral("mocha") ||
+ aName.LowerCaseEqualsLiteral("javascript1.0") ||
+ aName.LowerCaseEqualsLiteral("javascript1.1") ||
+ aName.LowerCaseEqualsLiteral("javascript1.2") ||
+ aName.LowerCaseEqualsLiteral("javascript1.3") ||
+ aName.LowerCaseEqualsLiteral("javascript1.4") ||
+ aName.LowerCaseEqualsLiteral("javascript1.5");
+}
+
+JSVersion
+nsContentUtils::ParseJavascriptVersion(const nsAString& aVersionStr)
+{
+ if (aVersionStr.Length() != 3 || aVersionStr[0] != '1' ||
+ aVersionStr[1] != '.') {
+ return JSVERSION_UNKNOWN;
+ }
+
+ switch (aVersionStr[2]) {
+ case '0': /* fall through */
+ case '1': /* fall through */
+ case '2': /* fall through */
+ case '3': /* fall through */
+ case '4': /* fall through */
+ case '5': return JSVERSION_DEFAULT;
+ case '6': return JSVERSION_1_6;
+ case '7': return JSVERSION_1_7;
+ case '8': return JSVERSION_1_8;
+ default: return JSVERSION_UNKNOWN;
+ }
+}
+
+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();
+}
+
+nsresult
+nsContentUtils::IsUserIdle(uint32_t aRequestedIdleTimeInMS, bool* aUserIsIdle)
+{
+ nsresult rv;
+ nsCOMPtr<nsIIdleService> idleService =
+ do_GetService("@mozilla.org/widget/idleservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t idleTimeInMS;
+ rv = idleService->GetIdleTime(&idleTimeInMS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aUserIsIdle = idleTimeInMS >= aRequestedIdleTimeInMS;
+ return NS_OK;
+}
+
+/**
+ * Access a cached parser service. Don't addref. We need only one
+ * reference to it and this class has that one.
+ */
+/* static */
+nsIParserService*
+nsContentUtils::GetParserService()
+{
+ // XXX: This isn't accessed from several threads, is it?
+ if (!sParserService) {
+ // Lock, recheck sCachedParserService and aquire if this should be
+ // safe for multiple threads.
+ nsresult rv = CallGetService(kParserServiceCID, &sParserService);
+ if (NS_FAILED(rv)) {
+ sParserService = nullptr;
+ }
+ }
+
+ return sParserService;
+}
+
+/**
+* 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.Append(NS_LITERAL_STRING(" ")); \
+ } \
+ aString.Append(nsDependentAtomString(nsGkAtoms::atom)); \
+ }
+#include "IframeSandboxKeywordList.h"
+#undef SANDBOX_KEYWORD
+}
+
+nsIBidiKeyboard*
+nsContentUtils::GetBidiKeyboard()
+{
+ if (!sBidiKeyboard) {
+ nsresult rv = CallGetService("@mozilla.org/widget/bidikeyboard;1", &sBidiKeyboard);
+ if (NS_FAILED(rv)) {
+ sBidiKeyboard = nullptr;
+ }
+ }
+ return sBidiKeyboard;
+}
+
+template <class OutputIterator>
+struct NormalizeNewlinesCharTraits {
+ public:
+ typedef typename OutputIterator::value_type value_type;
+
+ public:
+ explicit NormalizeNewlinesCharTraits(OutputIterator& aIterator) : mIterator(aIterator) { }
+ void writechar(typename OutputIterator::value_type aChar) {
+ *mIterator++ = aChar;
+ }
+
+ private:
+ OutputIterator mIterator;
+};
+
+template <class CharT>
+struct NormalizeNewlinesCharTraits<CharT*> {
+ public:
+ typedef CharT value_type;
+
+ public:
+ explicit NormalizeNewlinesCharTraits(CharT* aCharPtr) : mCharPtr(aCharPtr) { }
+ void writechar(CharT aChar) {
+ *mCharPtr++ = aChar;
+ }
+
+ private:
+ CharT* mCharPtr;
+};
+
+template <class OutputIterator>
+class CopyNormalizeNewlines
+{
+ public:
+ typedef typename OutputIterator::value_type value_type;
+
+ public:
+ explicit CopyNormalizeNewlines(OutputIterator* aDestination,
+ bool aLastCharCR = false) :
+ mLastCharCR(aLastCharCR),
+ mDestination(aDestination),
+ mWritten(0)
+ { }
+
+ uint32_t GetCharsWritten() {
+ return mWritten;
+ }
+
+ bool IsLastCharCR() {
+ return mLastCharCR;
+ }
+
+ void write(const typename OutputIterator::value_type* aSource, uint32_t aSourceLength) {
+
+ const typename OutputIterator::value_type* done_writing = aSource + aSourceLength;
+
+ // If the last source buffer ended with a CR...
+ if (mLastCharCR) {
+ // ..and if the next one is a LF, then skip it since
+ // we've already written out a newline
+ if (aSourceLength && (*aSource == value_type('\n'))) {
+ ++aSource;
+ }
+ mLastCharCR = false;
+ }
+
+ uint32_t num_written = 0;
+ while ( aSource < done_writing ) {
+ if (*aSource == value_type('\r')) {
+ mDestination->writechar('\n');
+ ++aSource;
+ // If we've reached the end of the buffer, record
+ // that we wrote out a CR
+ if (aSource == done_writing) {
+ mLastCharCR = true;
+ }
+ // If the next character is a LF, skip it
+ else if (*aSource == value_type('\n')) {
+ ++aSource;
+ }
+ }
+ else {
+ mDestination->writechar(*aSource++);
+ }
+ ++num_written;
+ }
+
+ mWritten += num_written;
+ }
+
+ private:
+ bool mLastCharCR;
+ OutputIterator* mDestination;
+ uint32_t mWritten;
+};
+
+// static
+uint32_t
+nsContentUtils::CopyNewlineNormalizedUnicodeTo(const nsAString& aSource,
+ uint32_t aSrcOffset,
+ char16_t* aDest,
+ uint32_t aLength,
+ bool& aLastCharCR)
+{
+ typedef NormalizeNewlinesCharTraits<char16_t*> sink_traits;
+
+ sink_traits dest_traits(aDest);
+ CopyNormalizeNewlines<sink_traits> normalizer(&dest_traits,aLastCharCR);
+ nsReadingIterator<char16_t> fromBegin, fromEnd;
+ copy_string(aSource.BeginReading(fromBegin).advance( int32_t(aSrcOffset) ),
+ aSource.BeginReading(fromEnd).advance( int32_t(aSrcOffset+aLength) ),
+ normalizer);
+ aLastCharCR = normalizer.IsLastCharCR();
+ return normalizer.GetCharsWritten();
+}
+
+// static
+uint32_t
+nsContentUtils::CopyNewlineNormalizedUnicodeTo(nsReadingIterator<char16_t>& aSrcStart, const nsReadingIterator<char16_t>& aSrcEnd, nsAString& aDest)
+{
+ typedef nsWritingIterator<char16_t> WritingIterator;
+ typedef NormalizeNewlinesCharTraits<WritingIterator> sink_traits;
+
+ WritingIterator iter;
+ aDest.BeginWriting(iter);
+ sink_traits dest_traits(iter);
+ CopyNormalizeNewlines<sink_traits> normalizer(&dest_traits);
+ copy_string(aSrcStart, aSrcEnd, normalizer);
+ return normalizer.GetCharsWritten();
+}
+
+/**
+ * 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::IsFirstLetterPunctuationAt(const nsTextFragment* aFrag, uint32_t aOffset)
+{
+ char16_t h = aFrag->CharAt(aOffset);
+ if (!IS_SURROGATE(h)) {
+ return IsFirstLetterPunctuation(h);
+ }
+ if (NS_IS_HIGH_SURROGATE(h) && aOffset + 1 < aFrag->GetLength()) {
+ char16_t l = aFrag->CharAt(aOffset + 1);
+ if (NS_IS_LOW_SURROGATE(l)) {
+ return IsFirstLetterPunctuation(SURROGATE_TO_UCS4(h, l));
+ }
+ }
+ return false;
+}
+
+// static
+bool nsContentUtils::IsAlphanumeric(uint32_t aChar)
+{
+ nsIUGenCategory::nsUGenCategory cat = mozilla::unicode::GetGenCategory(aChar);
+
+ return (cat == nsIUGenCategory::kLetter || cat == nsIUGenCategory::kNumber);
+}
+
+// static
+bool nsContentUtils::IsAlphanumericAt(const nsTextFragment* aFrag, uint32_t aOffset)
+{
+ char16_t h = aFrag->CharAt(aOffset);
+ if (!IS_SURROGATE(h)) {
+ return IsAlphanumeric(h);
+ }
+ if (NS_IS_HIGH_SURROGATE(h) && aOffset + 1 < aFrag->GetLength()) {
+ char16_t l = aFrag->CharAt(aOffset + 1);
+ if (NS_IS_LOW_SURROGATE(l)) {
+ return IsAlphanumeric(SURROGATE_TO_UCS4(h, l));
+ }
+ }
+ return false;
+}
+
+/* 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::IsHTMLBlock(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::multicol, // XXX get rid of this one?
+ 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 */
+bool
+nsContentUtils::IsControlledByServiceWorker(nsIDocument* aDocument)
+{
+ if (nsContentUtils::IsInPrivateBrowsing(aDocument)) {
+ return false;
+ }
+
+ RefPtr<workers::ServiceWorkerManager> swm =
+ workers::ServiceWorkerManager::GetInstance();
+ MOZ_ASSERT(swm);
+
+ ErrorResult rv;
+ bool controlled = swm->IsControlled(aDocument, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ return controlled;
+}
+
+/* static */
+void
+nsContentUtils::GetOfflineAppManifest(nsIDocument *aDocument, nsIURI **aURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aDocument);
+ *aURI = nullptr;
+
+ if (IsControlledByServiceWorker(aDocument)) {
+ 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)
+{
+ nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
+ do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
+ if (!updateService) {
+ return false;
+ }
+
+ bool allowed;
+ nsresult rv =
+ updateService->OfflineAppAllowedForURI(aURI,
+ Preferences::GetRootBranch(),
+ &allowed);
+ return NS_SUCCEEDED(rv) && allowed;
+}
+
+/* static */
+bool
+nsContentUtils::OfflineAppAllowed(nsIPrincipal *aPrincipal)
+{
+ nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
+ do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
+ if (!updateService) {
+ return false;
+ }
+
+ bool allowed;
+ nsresult rv = updateService->OfflineAppAllowed(aPrincipal,
+ Preferences::GetRootBranch(),
+ &allowed);
+ return NS_SUCCEEDED(rv) && allowed;
+}
+
+bool
+nsContentUtils::MaybeAllowOfflineAppByDefault(nsIPrincipal *aPrincipal)
+{
+ if (!Preferences::GetRootBranch())
+ return false;
+
+ nsresult rv;
+
+ bool allowedByDefault;
+ rv = Preferences::GetRootBranch()->GetBoolPref(
+ "offline-apps.allow_by_default", &allowedByDefault);
+ if (NS_FAILED(rv))
+ return false;
+
+ if (!allowedByDefault)
+ return false;
+
+ nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
+ do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID);
+ if (!updateService) {
+ return false;
+ }
+
+ rv = updateService->AllowOfflineApp(aPrincipal);
+ return NS_SUCCEEDED(rv);
+}
+
+// static
+void
+nsContentUtils::Shutdown()
+{
+ sInitialized = false;
+
+ NS_IF_RELEASE(sContentPolicyService);
+ sTriedToGetContentPolicy = false;
+ uint32_t i;
+ for (i = 0; i < PropertiesFile_COUNT; ++i)
+ NS_IF_RELEASE(sStringBundles[i]);
+
+ NS_IF_RELEASE(sStringBundleService);
+ NS_IF_RELEASE(sConsoleService);
+ sXPConnect = nullptr;
+ NS_IF_RELEASE(sSecurityManager);
+ NS_IF_RELEASE(sSystemPrincipal);
+ NS_IF_RELEASE(sNullSubjectPrincipal);
+ NS_IF_RELEASE(sParserService);
+ NS_IF_RELEASE(sIOService);
+ NS_IF_RELEASE(sUUIDGenerator);
+ NS_IF_RELEASE(sLineBreaker);
+ NS_IF_RELEASE(sWordBreaker);
+ NS_IF_RELEASE(sBidiKeyboard);
+
+ 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;
+ }
+ }
+
+ 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;
+
+ NS_IF_RELEASE(sSameOriginChecker);
+}
+
+/**
+ * Checks whether two nodes come from the same origin. aTrustedNode is
+ * considered 'safe' in that a user can operate on it and that it isn't
+ * a js-object that implements nsIDOMNode.
+ * Never call this function with the first node provided by script, it
+ * must always be known to be a 'real' node!
+ */
+// static
+nsresult
+nsContentUtils::CheckSameOrigin(const nsINode *aTrustedNode,
+ nsIDOMNode *aUnTrustedNode)
+{
+ MOZ_ASSERT(aTrustedNode);
+
+ // Make sure it's a real node.
+ nsCOMPtr<nsINode> unTrustedNode = do_QueryInterface(aUnTrustedNode);
+ NS_ENSURE_TRUE(unTrustedNode, NS_ERROR_UNEXPECTED);
+ return CheckSameOrigin(aTrustedNode, unTrustedNode);
+}
+
+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(nsIDOMNode *aNode)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ NS_ENSURE_TRUE(node, false);
+ return CanCallerAccess(node);
+}
+
+// static
+bool
+nsContentUtils::CanCallerAccess(nsINode* aNode)
+{
+ return CanCallerAccess(SubjectPrincipal(), 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::InProlog(nsINode *aNode)
+{
+ NS_PRECONDITION(aNode, "missing node to nsContentUtils::InProlog");
+
+ nsINode* parent = aNode->GetParentNode();
+ if (!parent || !parent->IsNodeOfType(nsINode::eDOCUMENT)) {
+ return false;
+ }
+
+ nsIDocument* doc = static_cast<nsIDocument*>(parent);
+ nsIContent* root = doc->GetRootElement();
+
+ return !root || doc->IndexOf(aNode) < doc->IndexOf(root);
+}
+
+nsIDocument*
+nsContentUtils::GetDocumentFromCaller()
+{
+ AutoJSContext cx;
+
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(cx)));
+ if (!win) {
+ return nullptr;
+ }
+
+ return win->GetExtantDoc();
+}
+
+bool
+nsContentUtils::IsCallerChrome()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (SubjectPrincipal() == sSystemPrincipal) {
+ return true;
+ }
+
+ // If the check failed, look for UniversalXPConnect on the cx compartment.
+ return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
+}
+
+bool
+nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell)
+{
+ if (!aDocShell) {
+ return false;
+ }
+ bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
+ return !isChrome && sPrivacyResistFingerprinting;
+}
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+extern bool IsCurrentThreadRunningChromeWorker();
+extern JSContext* GetCurrentThreadJSContext();
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+bool
+nsContentUtils::ThreadsafeIsCallerChrome()
+{
+ return NS_IsMainThread() ?
+ IsCallerChrome() :
+ mozilla::dom::workers::IsCurrentThreadRunningChromeWorker();
+}
+
+bool
+nsContentUtils::IsCallerContentXBL()
+{
+ JSContext *cx = GetCurrentJSContext();
+ if (!cx)
+ return false;
+
+ JSCompartment *c = js::GetContextCompartment(cx);
+
+ // For remote XUL, we run XBL in the XUL scope. Given that we care about
+ // compat and not security for remote XUL, just always claim to be XBL.
+ if (!xpc::AllowContentXBLScope(c)) {
+ MOZ_ASSERT(nsContentUtils::AllowXULXBLForPrincipal(xpc::GetCompartmentPrincipal(c)));
+ return true;
+ }
+
+ return xpc::IsContentXBLScope(c);
+}
+
+// static
+bool
+nsContentUtils::LookupBindingMember(JSContext* aCx, nsIContent *aContent,
+ JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc)
+{
+ nsXBLBinding* binding = aContent->GetXBLBinding();
+ if (!binding)
+ return true;
+ return binding->LookupMember(aCx, aId, aDesc);
+}
+
+// static
+nsINode*
+nsContentUtils::GetCrossDocParentNode(nsINode* aChild)
+{
+ NS_PRECONDITION(aChild, "The child is null!");
+
+ nsINode* parent = aChild->GetParentNode();
+ if (parent && parent->IsContent() && aChild->IsContent()) {
+ parent = aChild->AsContent()->GetFlattenedTreeParent();
+ }
+
+ if (parent || !aChild->IsNodeOfType(nsINode::eDOCUMENT))
+ return parent;
+
+ nsIDocument* doc = static_cast<nsIDocument*>(aChild);
+ nsIDocument* parentDoc = doc->GetParentDocument();
+ return parentDoc ? parentDoc->FindContentForSubDocument(doc) : nullptr;
+}
+
+// static
+bool
+nsContentUtils::ContentIsDescendantOf(const nsINode* aPossibleDescendant,
+ const nsINode* aPossibleAncestor)
+{
+ NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!");
+ NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor)
+ return true;
+ aPossibleDescendant = aPossibleDescendant->GetParentNode();
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+bool
+nsContentUtils::ContentIsHostIncludingDescendantOf(
+ const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor)
+{
+ NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!");
+ NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor)
+ return true;
+ if (aPossibleDescendant->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+ aPossibleDescendant =
+ static_cast<const DocumentFragment*>(aPossibleDescendant)->GetHost();
+ } else {
+ aPossibleDescendant = aPossibleDescendant->GetParentNode();
+ }
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+// static
+bool
+nsContentUtils::ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant,
+ nsINode* aPossibleAncestor)
+{
+ NS_PRECONDITION(aPossibleDescendant, "The possible descendant is null!");
+ NS_PRECONDITION(aPossibleAncestor, "The possible ancestor is null!");
+
+ do {
+ if (aPossibleDescendant == aPossibleAncestor)
+ return true;
+
+ aPossibleDescendant = GetCrossDocParentNode(aPossibleDescendant);
+ } while (aPossibleDescendant);
+
+ return false;
+}
+
+
+// static
+nsresult
+nsContentUtils::GetAncestors(nsINode* aNode,
+ nsTArray<nsINode*>& aArray)
+{
+ while (aNode) {
+ aArray.AppendElement(aNode);
+ aNode = aNode->GetParentNode();
+ }
+ return NS_OK;
+}
+
+// static
+nsresult
+nsContentUtils::GetAncestorsAndOffsets(nsIDOMNode* aNode,
+ int32_t aOffset,
+ nsTArray<nsIContent*>* aAncestorNodes,
+ nsTArray<int32_t>* aAncestorOffsets)
+{
+ NS_ENSURE_ARG_POINTER(aNode);
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
+
+ if (!content) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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.get());
+ aAncestorOffsets->AppendElement(aOffset);
+
+ // insert all the ancestors
+ nsIContent* child = content;
+ nsIContent* parent = child->GetParent();
+ while (parent) {
+ aAncestorNodes->AppendElement(parent);
+ aAncestorOffsets->AppendElement(parent->IndexOf(child));
+ child = parent;
+ parent = parent->GetParent();
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult
+nsContentUtils::GetCommonAncestor(nsIDOMNode *aNode,
+ nsIDOMNode *aOther,
+ nsIDOMNode** aCommonAncestor)
+{
+ *aCommonAncestor = nullptr;
+
+ nsCOMPtr<nsINode> node1 = do_QueryInterface(aNode);
+ nsCOMPtr<nsINode> node2 = do_QueryInterface(aOther);
+
+ NS_ENSURE_TRUE(node1 && node2, NS_ERROR_UNEXPECTED);
+
+ nsINode* common = GetCommonAncestor(node1, node2);
+ NS_ENSURE_TRUE(common, NS_ERROR_NOT_AVAILABLE);
+
+ return CallQueryInterface(common, aCommonAncestor);
+}
+
+// static
+nsINode*
+nsContentUtils::GetCommonAncestor(nsINode* aNode1,
+ nsINode* aNode2)
+{
+ if (aNode1 == aNode2) {
+ return aNode1;
+ }
+
+ // Build the chain of parents
+ AutoTArray<nsINode*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(aNode1);
+ aNode1 = aNode1->GetParentNode();
+ } while (aNode1);
+ do {
+ parents2.AppendElement(aNode2);
+ aNode2 = aNode2->GetParentNode();
+ } while (aNode2);
+
+ // Find where the parent chain differs
+ uint32_t pos1 = parents1.Length();
+ uint32_t pos2 = parents2.Length();
+ nsINode* parent = nullptr;
+ uint32_t len;
+ for (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 */
+bool
+nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2)
+{
+ return (aNode2->CompareDocumentPosition(*aNode1) &
+ (nsIDOMNode::DOCUMENT_POSITION_PRECEDING |
+ nsIDOMNode::DOCUMENT_POSITION_DISCONNECTED)) ==
+ nsIDOMNode::DOCUMENT_POSITION_PRECEDING;
+}
+
+/* static */
+int32_t
+nsContentUtils::ComparePoints(nsINode* aParent1, int32_t aOffset1,
+ nsINode* aParent2, int32_t aOffset2,
+ bool* aDisconnected)
+{
+ if (aParent1 == aParent2) {
+ return aOffset1 < aOffset2 ? -1 :
+ aOffset1 > aOffset2 ? 1 :
+ 0;
+ }
+
+ AutoTArray<nsINode*, 32> parents1, parents2;
+ nsINode* node1 = aParent1;
+ nsINode* node2 = aParent2;
+ do {
+ parents1.AppendElement(node1);
+ node1 = node1->GetParentNode();
+ } while (node1);
+ do {
+ parents2.AppendElement(node2);
+ node2 = node2->GetParentNode();
+ } 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
+ nsINode* parent = parents1.ElementAt(pos1);
+ uint32_t len;
+ for (len = std::min(pos1, pos2); len > 0; --len) {
+ nsINode* child1 = parents1.ElementAt(--pos1);
+ nsINode* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ return parent->IndexOf(child1) < parent->IndexOf(child2) ? -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) {
+ nsINode* child2 = parents2.ElementAt(--pos2);
+ return aOffset1 <= parent->IndexOf(child2) ? -1 : 1;
+ }
+
+ nsINode* child1 = parents1.ElementAt(--pos1);
+ return parent->IndexOf(child1) < aOffset2 ? -1 : 1;
+}
+
+/* static */
+int32_t
+nsContentUtils::ComparePoints(nsIDOMNode* aParent1, int32_t aOffset1,
+ nsIDOMNode* aParent2, int32_t aOffset2,
+ bool* aDisconnected)
+{
+ nsCOMPtr<nsINode> parent1 = do_QueryInterface(aParent1);
+ nsCOMPtr<nsINode> parent2 = do_QueryInterface(aParent2);
+ NS_ENSURE_TRUE(parent1 && parent2, -1);
+ return ComparePoints(parent1, aOffset1, parent2, aOffset2);
+}
+
+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.Append(nsPrintfCString("%d", aInt));
+}
+
+static inline bool IsAutocompleteOff(const nsIContent* aElement)
+{
+ return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocomplete,
+ NS_LITERAL_STRING("off"), eIgnoreCase);
+}
+
+/*static*/ nsresult
+nsContentUtils::GenerateStateKey(nsIContent* aContent,
+ const nsIDocument* aDocument,
+ nsACString& aKey)
+{
+ aKey.Truncate();
+
+ uint32_t partID = aDocument ? aDocument->GetPartID() : 0;
+
+ // We must have content if we're not using a special state id
+ NS_ENSURE_TRUE(aContent, NS_ERROR_FAILURE);
+
+ // Don't capture state for anonymous content
+ if (aContent->IsInAnonymousSubtree()) {
+ return NS_OK;
+ }
+
+ if (IsAutocompleteOff(aContent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIHTMLDocument> htmlDocument =
+ do_QueryInterface(aContent->GetUncomposedDoc());
+
+ KeyAppendInt(partID, aKey); // first append a partID
+ bool generatedUniqueKey = false;
+
+ if (htmlDocument) {
+ // Flush our content model so it'll be up to date
+ // If this becomes unnecessary and the following line is removed,
+ // please also remove the corresponding flush operation from
+ // nsHtml5TreeBuilderCppSupplement.h. (Look for "See bug 497861." there.)
+ aContent->GetUncomposedDoc()->FlushPendingNotifications(Flush_Content);
+
+ nsContentList *htmlForms = htmlDocument->GetForms();
+ nsContentList *htmlFormControls = htmlDocument->GetFormControls();
+
+ NS_ENSURE_TRUE(htmlForms && htmlFormControls, NS_ERROR_OUT_OF_MEMORY);
+
+ // 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.
+ //
+ // If the control has a form, the format of the key is:
+ // f>type>IndOfFormInDoc>IndOfControlInForm>FormName>name
+ // else:
+ // d>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 && htmlFormControls && htmlForms) {
+
+ // Append the control type
+ KeyAppendInt(control->GetType(), aKey);
+
+ // If in a form, add form name / index of form / index in form
+ int32_t index = -1;
+ Element *formElement = control->GetFormElement();
+ if (formElement) {
+ if (IsAutocompleteOff(formElement)) {
+ aKey.Truncate();
+ return NS_OK;
+ }
+
+ KeyAppendString(NS_LITERAL_CSTRING("f"), aKey);
+
+ // Append the index of the form in the document
+ 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 = htmlDocument->GetNumFormsSynchronous() - 1;
+ }
+ if (index > -1) {
+ KeyAppendInt(index, aKey);
+
+ // Append the index of the control in the form
+ nsCOMPtr<nsIForm> form(do_QueryInterface(formElement));
+ index = form->IndexOfControl(control);
+
+ 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 {
+
+ KeyAppendString(NS_LITERAL_CSTRING("d"), aKey);
+
+ // If not in a form, add index of control in document
+ // Less desirable than indexing by form info.
+
+ // Hash by index of control in doc (we are not in a form)
+ // These are important as they are unique, and type/name may not be.
+
+ // We have to flush sink notifications at this point to make
+ // sure that htmlFormControls is up to date.
+ index = htmlFormControls->IndexOf(aContent, true);
+ if (index > -1) {
+ KeyAppendInt(index, aKey);
+ generatedUniqueKey = true;
+ }
+ }
+
+ // Append the control name
+ nsAutoString name;
+ aContent->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(NS_LITERAL_CSTRING("o"), 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->IndexOf(content), aKey);
+ content = parent;
+ parent = content->GetParentNode();
+ }
+ }
+
+ return NS_OK;
+}
+
+// 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");
+ }
+
+ JSCompartment *compartment = js::GetContextCompartment(cx);
+
+ // When an AutoJSAPI is instantiated, we are in a null compartment until the
+ // first JSAutoCompartment, 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
+ // GetSubjectPrincipal 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 compartment creation against using this
+ // principal on an actual global.
+ if (!compartment) {
+ return sNullSubjectPrincipal;
+ }
+
+ JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment);
+ return nsJSPrincipals::get(principals);
+}
+
+// static
+nsIPrincipal*
+nsContentUtils::ObjectPrincipal(JSObject* aObj)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+#ifdef DEBUG
+ JS::AssertObjectBelongsToCurrentThread(aObj);
+#endif
+
+ // This is duplicated from nsScriptSecurityManager. We don't call through there
+ // because the API unnecessarily requires a JSContext for historical reasons.
+ JSCompartment *compartment = js::GetObjectCompartment(aObj);
+ JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment);
+ return nsJSPrincipals::get(principals);
+}
+
+// static
+nsresult
+nsContentUtils::NewURIWithDocumentCharset(nsIURI** aResult,
+ const nsAString& aSpec,
+ nsIDocument* aDocument,
+ nsIURI* aBaseURI)
+{
+ return NS_NewURI(aResult, aSpec,
+ aDocument ? aDocument->GetDocumentCharacterSet().get() : nullptr,
+ aBaseURI, sIOService);
+}
+
+// static
+bool
+nsContentUtils::IsCustomElementName(nsIAtom* 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 (NS_IS_HIGH_SURROGATE(name[i]) && i + 1 < len &&
+ NS_IS_LOW_SURROGATE(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++;
+ }
+ }
+
+ 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;
+ }
+
+ // MOZ_EXPAT_EMPTY_QNAME || MOZ_EXPAT_INVALID_CHARACTER
+ if (result == (1 << 0) || result == (1 << 1)) {
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ }
+
+ return NS_ERROR_DOM_NAMESPACE_ERR;
+}
+
+//static
+nsresult
+nsContentUtils::SplitQName(const nsIContent* aNamespaceResolver,
+ const nsAFlatString& aQName,
+ int32_t *aNamespace, nsIAtom **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 = NameSpaceManager()->GetNameSpaceID(nameSpace,
+ nsContentUtils::IsChromeDoc(aNamespaceResolver->OwnerDoc()));
+ if (*aNamespace == kNameSpaceID_Unknown)
+ return NS_ERROR_FAILURE;
+
+ *aLocalName = NS_Atomize(Substring(colon + 1, end)).take();
+ }
+ else {
+ *aNamespace = kNameSpaceID_None;
+ *aLocalName = NS_Atomize(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 nsAFlatString& qName = PromiseFlatString(aQualifiedName);
+ const char16_t* colon;
+ nsresult rv = nsContentUtils::CheckQName(qName, true, &colon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t nsID;
+ sNameSpaceManager->RegisterNameSpace(aNamespaceURI, nsID);
+ if (colon) {
+ const char16_t* end;
+ qName.EndReading(end);
+
+ nsCOMPtr<nsIAtom> prefix = NS_Atomize(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, nsIAtom **aPrefix,
+ nsIAtom **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) {
+ if (sNameSpaceManager) {
+ sNameSpaceManager->RegisterNameSpace(nsDependentSubstring(aExpatName,
+ uriEnd),
+ *aNameSpaceID);
+ }
+ else {
+ *aNameSpaceID = kNameSpaceID_Unknown;
+ }
+
+ nameStart = (uriEnd + 1);
+ if (nameEnd) {
+ const char16_t *prefixStart = nameEnd + 1;
+ *aPrefix = NS_Atomize(Substring(prefixStart, pos)).take();
+ }
+ else {
+ nameEnd = pos;
+ *aPrefix = nullptr;
+ }
+ }
+ else {
+ *aNameSpaceID = kNameSpaceID_None;
+ nameStart = aExpatName;
+ nameEnd = pos;
+ *aPrefix = nullptr;
+ }
+ *aLocalName = NS_Atomize(Substring(nameStart, nameEnd)).take();
+}
+
+// static
+nsPresContext*
+nsContentUtils::GetContextForContent(const nsIContent* aContent)
+{
+ nsIDocument* doc = aContent->GetComposedDoc();
+ if (doc) {
+ nsIPresShell *presShell = doc->GetShell();
+ if (presShell) {
+ return presShell->GetPresContext();
+ }
+ }
+ return nullptr;
+}
+
+// static
+bool
+nsContentUtils::CanLoadImage(nsIURI* aURI, nsISupports* aContext,
+ nsIDocument* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal,
+ int16_t* aImageBlockingStatus,
+ uint32_t aContentType)
+{
+ NS_PRECONDITION(aURI, "Must have a URI");
+ NS_PRECONDITION(aLoadingDocument, "Must have a document");
+ NS_PRECONDITION(aLoadingPrincipal, "Must have a loading principal");
+
+ nsresult rv;
+
+ uint32_t appType = nsIDocShell::APP_TYPE_UNKNOWN;
+
+ {
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aLoadingDocument->GetDocShell();
+ if (docShellTreeItem) {
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShellTreeItem->GetRootTreeItem(getter_AddRefs(root));
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(root));
+
+ if (!docShell || NS_FAILED(docShell->GetAppType(&appType))) {
+ appType = nsIDocShell::APP_TYPE_UNKNOWN;
+ }
+ }
+ }
+
+ 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);
+ if (NS_FAILED(rv)) {
+ if (aImageBlockingStatus) {
+ // Reject the request itself, not all requests to the relevant
+ // server...
+ *aImageBlockingStatus = nsIContentPolicy::REJECT_REQUEST;
+ }
+ return false;
+ }
+ }
+
+ int16_t decision = nsIContentPolicy::ACCEPT;
+
+ rv = NS_CheckContentLoadPolicy(aContentType,
+ aURI,
+ aLoadingPrincipal,
+ aContext,
+ EmptyCString(), //mime guess
+ nullptr, //extra
+ &decision,
+ GetContentPolicy(),
+ sSecurityManager);
+
+ if (aImageBlockingStatus) {
+ *aImageBlockingStatus =
+ NS_FAILED(rv) ? nsIContentPolicy::REJECT_REQUEST : decision;
+ }
+ return NS_FAILED(rv) ? false : NS_CP_ACCEPTED(decision);
+}
+
+// static
+mozilla::PrincipalOriginAttributes
+nsContentUtils::GetOriginAttributes(nsIDocument* aDocument)
+{
+ if (!aDocument) {
+ return mozilla::PrincipalOriginAttributes();
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup();
+ if (loadGroup) {
+ return GetOriginAttributes(loadGroup);
+ }
+
+ mozilla::PrincipalOriginAttributes attrs;
+ mozilla::NeckoOriginAttributes nattrs;
+ nsCOMPtr<nsIChannel> channel = aDocument->GetChannel();
+ if (channel && NS_GetOriginAttributes(channel, nattrs)) {
+ attrs.InheritFromNecko(nattrs);
+ }
+ return attrs;
+}
+
+// static
+mozilla::PrincipalOriginAttributes
+nsContentUtils::GetOriginAttributes(nsILoadGroup* aLoadGroup)
+{
+ if (!aLoadGroup) {
+ return mozilla::PrincipalOriginAttributes();
+ }
+ mozilla::PrincipalOriginAttributes attrs;
+ mozilla::DocShellOriginAttributes dsattrs;
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+ if (loadContext && loadContext->GetOriginAttributes(dsattrs)) {
+ attrs.InheritFromDocShellToDoc(dsattrs, nullptr);
+ }
+ }
+ return attrs;
+}
+
+// static
+bool
+nsContentUtils::IsInPrivateBrowsing(nsIDocument* aDoc)
+{
+ if (!aDoc) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup = aDoc->GetDocumentLoadGroup();
+ if (loadGroup) {
+ return IsInPrivateBrowsing(loadGroup);
+ }
+
+ 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;
+}
+
+bool
+nsContentUtils::DocumentInactiveForImageLoads(nsIDocument* aDocument)
+{
+ if (aDocument && !IsChromeDoc(aDocument) && !aDocument->IsResourceDoc()) {
+ nsCOMPtr<nsPIDOMWindowInner> win =
+ do_QueryInterface(aDocument->GetScopeObject());
+ return !win || !win->GetDocShell();
+ }
+ return false;
+}
+
+imgLoader*
+nsContentUtils::GetImgLoaderForDocument(nsIDocument* 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,
+ nsIDocument* 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
+bool
+nsContentUtils::IsImageInCache(nsIURI* aURI, nsIDocument* aDocument)
+{
+ imgILoader* loader = GetImgLoaderForDocument(aDocument);
+ nsCOMPtr<imgICache> cache = do_QueryInterface(loader);
+
+ // If something unexpected happened we return false, otherwise if props
+ // is set, the image is cached and we return true
+ nsCOMPtr<nsIProperties> props;
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDocument);
+ nsresult rv = cache->FindEntryProperties(aURI, domDoc, getter_AddRefs(props));
+ return (NS_SUCCEEDED(rv) && props);
+}
+
+// static
+nsresult
+nsContentUtils::LoadImage(nsIURI* aURI, nsINode* aContext,
+ nsIDocument* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal, nsIURI* aReferrer,
+ net::ReferrerPolicy aReferrerPolicy,
+ imgINotificationObserver* aObserver, int32_t aLoadFlags,
+ const nsAString& initiatorType,
+ imgRequestProxy** aRequest,
+ uint32_t aContentPolicyType)
+{
+ NS_PRECONDITION(aURI, "Must have a URI");
+ NS_PRECONDITION(aContext, "Must have a context");
+ NS_PRECONDITION(aLoadingDocument, "Must have a document");
+ NS_PRECONDITION(aLoadingPrincipal, "Must have a principal");
+ NS_PRECONDITION(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 || IsFontTableURI(documentURI),
+ "Could not get loadgroup; onload may fire too early");
+
+ // Make the URI immutable so people won't change it under us
+ NS_TryToSetImmutable(aURI);
+
+ // 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 */
+ aReferrer, /* referrer */
+ aReferrerPolicy, /* referrer policy */
+ aLoadingPrincipal, /* loading principal */
+ loadGroup, /* loadgroup */
+ aObserver, /* imgINotificationObserver */
+ aContext, /* loading context */
+ aLoadingDocument, /* uniquification key */
+ aLoadFlags, /* load flags */
+ nullptr, /* cache key */
+ aContentPolicyType, /* content policy type */
+ initiatorType, /* the load initiator */
+ 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) {
+ imgRequest.swap(*aRequest);
+ }
+
+ return imgContainer.forget();
+}
+
+//static
+already_AddRefed<imgRequestProxy>
+nsContentUtils::GetStaticRequest(imgRequestProxy* aRequest)
+{
+ NS_ENSURE_TRUE(aRequest, nullptr);
+ RefPtr<imgRequestProxy> retval;
+ aRequest->GetStaticRequest(getter_AddRefs(retval));
+ return retval.forget();
+}
+
+// static
+bool
+nsContentUtils::ContentIsDraggable(nsIContent* aContent)
+{
+ nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aContent);
+ if (htmlElement) {
+ bool draggable = false;
+ htmlElement->GetDraggable(&draggable);
+ if (draggable)
+ return true;
+
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable,
+ nsGkAtoms::_false, eIgnoreCase))
+ return false;
+ }
+
+ // special handling for content area image and link dragging
+ return IsDraggableImage(aContent) || IsDraggableLink(aContent);
+}
+
+// static
+bool
+nsContentUtils::IsDraggableImage(nsIContent* aContent)
+{
+ NS_PRECONDITION(aContent, "Must have content node to test");
+
+ 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 != nullptr;
+}
+
+// static
+bool
+nsContentUtils::IsDraggableLink(const nsIContent* aContent) {
+ nsCOMPtr<nsIURI> absURI;
+ return aContent->IsLink(getter_AddRefs(absURI));
+}
+
+// static
+nsresult
+nsContentUtils::NameChanged(mozilla::dom::NodeInfo* aNodeInfo, nsIAtom* aName,
+ mozilla::dom::NodeInfo** aResult)
+{
+ nsNodeInfoManager *niMgr = aNodeInfo->NodeInfoManager();
+
+ *aResult = niMgr->GetNodeInfo(aName, aNodeInfo->GetPrefixAtom(),
+ aNodeInfo->NamespaceID(),
+ aNodeInfo->NodeType(),
+ aNodeInfo->GetExtraName()).take();
+ return NS_OK;
+}
+
+
+static bool
+TestSitePerm(nsIPrincipal* aPrincipal, const char* 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 = services::GetPermissionManager();
+ 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 char* aType)
+{
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION, false);
+}
+
+bool
+nsContentUtils::IsSitePermDeny(nsIPrincipal* aPrincipal, const char* aType)
+{
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION, false);
+}
+
+bool
+nsContentUtils::IsExactSitePermAllow(nsIPrincipal* aPrincipal, const char* aType)
+{
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::ALLOW_ACTION, true);
+}
+
+bool
+nsContentUtils::IsExactSitePermDeny(nsIPrincipal* aPrincipal, const char* aType)
+{
+ return TestSitePerm(aPrincipal, aType, nsIPermissionManager::DENY_ACTION, true);
+}
+
+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,
+ nsIAtom *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);
+ }
+}
+
+static const char gPropertiesFiles[nsContentUtils::PropertiesFile_COUNT][56] = {
+ // Must line up with the enum values in |PropertiesFile| enum.
+ "chrome://global/locale/css.properties",
+ "chrome://global/locale/xbl.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"
+};
+
+/* static */ nsresult
+nsContentUtils::EnsureStringBundle(PropertiesFile aFile)
+{
+ if (!sStringBundles[aFile]) {
+ if (!sStringBundleService) {
+ nsresult rv =
+ CallGetService(NS_STRINGBUNDLE_CONTRACTID, &sStringBundleService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsIStringBundle *bundle;
+ nsresult rv =
+ sStringBundleService->CreateBundle(gPropertiesFiles[aFile], &bundle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ sStringBundles[aFile] = bundle; // transfer ownership
+ }
+ return NS_OK;
+}
+
+/* static */
+nsresult nsContentUtils::GetLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ nsXPIDLString& aResult)
+{
+ nsresult rv = EnsureStringBundle(aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIStringBundle *bundle = sStringBundles[aFile];
+
+ return bundle->GetStringFromName(NS_ConvertASCIItoUTF16(aKey).get(),
+ getter_Copies(aResult));
+}
+
+/* static */
+nsresult nsContentUtils::FormatLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ const char16_t **aParams,
+ uint32_t aParamsLength,
+ nsXPIDLString& aResult)
+{
+ nsresult rv = EnsureStringBundle(aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIStringBundle *bundle = sStringBundles[aFile];
+
+ return bundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aKey).get(),
+ aParams, aParamsLength,
+ getter_Copies(aResult));
+}
+
+/* static */
+nsresult nsContentUtils::FormatLocalizedString(
+ PropertiesFile aFile,
+ const char* aKey,
+ const nsTArray<nsString>& aParamArray,
+ nsXPIDLString& aResult)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ UniquePtr<const char16_t*[]> params;
+ uint32_t paramsLength = aParamArray.Length();
+ if (paramsLength > 0) {
+ params = MakeUnique<const char16_t*[]>(paramsLength);
+ for (uint32_t i = 0; i < paramsLength; ++i) {
+ params[i] = aParamArray[i].get();
+ }
+ }
+ return FormatLocalizedString(aFile, aKey, params.get(), paramsLength,
+ aResult);
+}
+
+
+/* static */ void
+nsContentUtils::LogSimpleConsoleError(const nsAString& aErrorText,
+ const char * classification)
+{
+ 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, EmptyString(),
+ EmptyString(), 0, 0,
+ nsIScriptError::errorFlag,
+ classification))) {
+ console->LogMessage(scriptError);
+ }
+ }
+}
+
+/* static */ nsresult
+nsContentUtils::ReportToConsole(uint32_t aErrorFlags,
+ const nsACString& aCategory,
+ const nsIDocument* aDocument,
+ PropertiesFile aFile,
+ const char *aMessageName,
+ const char16_t **aParams,
+ uint32_t aParamsLength,
+ nsIURI* aURI,
+ const nsAFlatString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber)
+{
+ NS_ASSERTION((aParams && aParamsLength) || (!aParams && !aParamsLength),
+ "Supply either both parameters and their number or no"
+ "parameters and 0.");
+
+ nsresult rv;
+ nsXPIDLString errorText;
+ if (aParams) {
+ rv = FormatLocalizedString(aFile, aMessageName, aParams, aParamsLength,
+ errorText);
+ }
+ else {
+ rv = GetLocalizedString(aFile, aMessageName, errorText);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ReportToConsoleNonLocalized(errorText, aErrorFlags, aCategory,
+ aDocument, aURI, aSourceLine,
+ aLineNumber, aColumnNumber);
+}
+
+
+/* static */ nsresult
+nsContentUtils::ReportToConsoleNonLocalized(const nsAString& aErrorText,
+ uint32_t aErrorFlags,
+ const nsACString& aCategory,
+ const nsIDocument* aDocument,
+ nsIURI* aURI,
+ const nsAFlatString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ MissingErrorLocationMode aLocationMode)
+{
+ uint64_t innerWindowID = 0;
+ if (aDocument) {
+ if (!aURI) {
+ aURI = aDocument->GetDocumentURI();
+ }
+ innerWindowID = aDocument->InnerWindowID();
+ }
+
+ nsresult rv;
+ if (!sConsoleService) { // only need to bother null-checking here
+ rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoCString spec;
+ if (!aLineNumber && aLocationMode == eUSE_CALLING_LOCATION) {
+ JSContext *cx = GetCurrentJSContext();
+ if (cx) {
+ nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber, &aColumnNumber);
+ }
+ }
+ if (spec.IsEmpty() && aURI) {
+ spec = aURI->GetSpecOrDefault();
+ }
+
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = errorObject->InitWithWindowID(aErrorText,
+ NS_ConvertUTF8toUTF16(spec), // file name
+ aSourceLine,
+ aLineNumber, aColumnNumber,
+ aErrorFlags, aCategory,
+ innerWindowID);
+ 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(nsIDocument *aDocument)
+{
+ if (!aDocument) {
+ return false;
+ }
+ return aDocument->NodePrincipal() == sSystemPrincipal;
+}
+
+bool
+nsContentUtils::IsChildOfSameType(nsIDocument* aDoc)
+{
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(aDoc->GetDocShell());
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
+ if (docShellAsItem) {
+ docShellAsItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
+ }
+ return sameTypeParent != nullptr;
+}
+
+bool
+nsContentUtils::IsScriptType(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(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::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) ||
+ IsScriptType(aContentType);
+}
+
+bool
+nsContentUtils::GetWrapperSafeScriptFilename(nsIDocument* aDocument,
+ nsIURI* aURI,
+ nsACString& aScriptURI,
+ nsresult* aRv)
+{
+ MOZ_ASSERT(aRv);
+ bool scriptFileNameModified = false;
+ *aRv = NS_OK;
+
+ *aRv = aURI->GetSpec(aScriptURI);
+ NS_ENSURE_SUCCESS(*aRv, false);
+
+ if (IsChromeDoc(aDocument)) {
+ nsCOMPtr<nsIChromeRegistry> chromeReg =
+ mozilla::services::GetChromeRegistryService();
+
+ if (!chromeReg) {
+ // If we're running w/o a chrome registry we won't modify any
+ // script file names.
+
+ return scriptFileNameModified;
+ }
+
+ bool docWrappersEnabled =
+ chromeReg->WrappersEnabled(aDocument->GetDocumentURI());
+
+ bool uriWrappersEnabled = chromeReg->WrappersEnabled(aURI);
+
+ nsIURI *docURI = aDocument->GetDocumentURI();
+
+ if (docURI && docWrappersEnabled && !uriWrappersEnabled) {
+ // aURI is a script from a URL that doesn't get wrapper
+ // automation. aDocument is a chrome document that does get
+ // wrapper automation. Prepend the chrome document's URI
+ // followed by the string " -> " to the URI of the script we're
+ // loading here so that script in that URI gets the same wrapper
+ // automation that the chrome document expects.
+ nsAutoCString spec;
+ *aRv = docURI->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(*aRv))) {
+ return false;
+ }
+
+ spec.AppendLiteral(" -> ");
+ spec.Append(aScriptURI);
+
+ aScriptURI = spec;
+
+ scriptFileNameModified = true;
+ }
+ }
+
+ return scriptFileNameModified;
+}
+
+// static
+bool
+nsContentUtils::IsInChromeDocshell(nsIDocument *aDocument)
+{
+ if (!aDocument) {
+ return false;
+ }
+
+ if (aDocument->GetDisplayDocument()) {
+ return IsInChromeDocshell(aDocument->GetDisplayDocument());
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> docShell = aDocument->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+
+ return docShell->ItemType() == nsIDocShellTreeItem::typeChrome;
+}
+
+// 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(nsIAtom* 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(nsIAtom* aName)
+{
+ 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;
+}
+
+nsIAtom*
+nsContentUtils::GetEventMessageAndAtom(const nsAString& aName,
+ mozilla::EventClassID aEventClassID,
+ EventMessage* aEventMessage)
+{
+ 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->Count() > 127) {
+ while (sUserDefinedEvents->Count() > 64) {
+ nsIAtom* first = sUserDefinedEvents->ObjectAt(0);
+ sStringEventTable->Remove(Substring(nsDependentAtomString(first), 2));
+ sUserDefinedEvents->RemoveObjectAt(0);
+ }
+ }
+
+ *aEventMessage = eUnidentifiedEvent;
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(NS_LITERAL_STRING("on") + aName);
+ sUserDefinedEvents->AppendObject(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->Put(aName, mapping);
+ return mapping.mAtom;
+}
+
+// static
+EventMessage
+nsContentUtils::GetEventMessageAndAtomForListener(const nsAString& aName,
+ nsIAtom** aOnName)
+{
+ // 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;
+ nsCOMPtr<nsIAtom> 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_Atomize(NS_LITERAL_STRING("on") + 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(nsIDocument* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble, bool aCancelable,
+ bool aTrusted, nsIDOMEvent** aEvent,
+ EventTarget** aTargetOut)
+{
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDoc);
+ nsCOMPtr<EventTarget> target(do_QueryInterface(aTarget));
+ NS_ENSURE_TRUE(domDoc && target, NS_ERROR_INVALID_ARG);
+
+ nsCOMPtr<nsIDOMEvent> event;
+ nsresult rv =
+ domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ event->InitEvent(aEventName, aCanBubble, aCancelable);
+ event->SetTrusted(aTrusted);
+
+ rv = event->SetTarget(target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ event.forget(aEvent);
+ target.forget(aTargetOut);
+ return NS_OK;
+}
+
+// static
+nsresult
+nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble, bool aCancelable,
+ bool *aDefaultAction)
+{
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ true, aDefaultAction);
+}
+
+// static
+nsresult
+nsContentUtils::DispatchUntrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble, bool aCancelable,
+ bool *aDefaultAction)
+{
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ false, aDefaultAction);
+}
+
+// static
+nsresult
+nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble, bool aCancelable,
+ bool aTrusted, bool *aDefaultAction,
+ bool aOnlyChromeDispatch)
+{
+ nsCOMPtr<nsIDOMEvent> event;
+ nsCOMPtr<EventTarget> target;
+ nsresult rv = GetEventAndTarget(aDoc, aTarget, aEventName, aCanBubble,
+ aCancelable, aTrusted, getter_AddRefs(event),
+ getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = aOnlyChromeDispatch;
+
+ bool dummy;
+ return target->DispatchEvent(event, aDefaultAction ? aDefaultAction : &dummy);
+}
+
+nsresult
+nsContentUtils::DispatchChromeEvent(nsIDocument *aDoc,
+ nsISupports *aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble, bool aCancelable,
+ bool *aDefaultAction)
+{
+
+ nsCOMPtr<nsIDOMEvent> event;
+ nsCOMPtr<EventTarget> target;
+ nsresult rv = GetEventAndTarget(aDoc, aTarget, aEventName, aCanBubble,
+ aCancelable, true, 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;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ rv = piTarget->DispatchDOMEvent(nullptr, event, nullptr, &status);
+ if (aDefaultAction) {
+ *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault);
+ }
+ return rv;
+}
+
+/* static */
+nsresult
+nsContentUtils::DispatchFocusChromeEvent(nsPIDOMWindowOuter* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return DispatchChromeEvent(doc, aWindow,
+ NS_LITERAL_STRING("DOMServiceWorkerFocusClient"),
+ true, true);
+}
+
+nsresult
+nsContentUtils::DispatchEventOnlyToChrome(nsIDocument* aDoc,
+ nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble, bool aCancelable,
+ bool* aDefaultAction)
+{
+ return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
+ true, aDefaultAction, true);
+}
+
+/* static */
+Element*
+nsContentUtils::MatchElementId(nsIContent *aContent, const nsIAtom* 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)
+{
+ NS_PRECONDITION(!aId.IsEmpty(), "Will match random elements");
+
+ // ID attrs are generally stored as atoms, so just atomize this up front
+ nsCOMPtr<nsIAtom> id(NS_Atomize(aId));
+ if (!id) {
+ // OOM, so just bail
+ return nullptr;
+ }
+
+ return MatchElementId(aContent, id);
+}
+
+/* static */
+nsIDocument*
+nsContentUtils::GetSubdocumentWithOuterWindowId(nsIDocument *aDocument,
+ uint64_t aOuterWindowId)
+{
+ if (!aDocument || !aOuterWindowId) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsGlobalWindow::GetOuterWindowWithId(aOuterWindowId)->AsOuter();
+ if (!window) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> foundDoc = window->GetDoc();
+ if (nsContentUtils::ContentIsCrossDocDescendantOf(foundDoc, aDocument)) {
+ // Note that ContentIsCrossDocDescendantOf will return true if
+ // foundDoc == aDocument.
+ return foundDoc;
+ }
+
+ return nullptr;
+}
+
+// Convert the string from the given encoding to Unicode.
+/* static */
+nsresult
+nsContentUtils::ConvertStringFromEncoding(const nsACString& aEncoding,
+ const nsACString& aInput,
+ nsAString& aOutput)
+{
+ nsAutoCString encoding;
+ if (aEncoding.IsEmpty()) {
+ encoding.AssignLiteral("UTF-8");
+ } else {
+ encoding.Assign(aEncoding);
+ }
+
+ ErrorResult rv;
+ nsAutoPtr<TextDecoder> decoder(new TextDecoder());
+ decoder->InitWithEncoding(encoding, false);
+
+ decoder->Decode(aInput.BeginReading(), aInput.Length(), false,
+ aOutput, rv);
+ return rv.StealNSResult();
+}
+
+/* static */
+bool
+nsContentUtils::CheckForBOM(const unsigned char* aBuffer, uint32_t aLength,
+ nsACString& aCharset)
+{
+ bool found = true;
+ aCharset.Truncate();
+ if (aLength >= 3 &&
+ aBuffer[0] == 0xEF &&
+ aBuffer[1] == 0xBB &&
+ aBuffer[2] == 0xBF) {
+ aCharset = "UTF-8";
+ }
+ else if (aLength >= 2 &&
+ aBuffer[0] == 0xFE && aBuffer[1] == 0xFF) {
+ aCharset = "UTF-16BE";
+ }
+ else if (aLength >= 2 &&
+ aBuffer[0] == 0xFF && aBuffer[1] == 0xFE) {
+ aCharset = "UTF-16LE";
+ } else {
+ found = false;
+ }
+
+ return found;
+}
+
+/* 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,
+ nsIAtom* aName)
+{
+ static nsIContent::AttrValuesArray strings[] = {&nsGkAtoms::_empty, nullptr};
+ return aContent->FindAttrValueIn(aNameSpaceID, aName, strings, eCaseMatters)
+ == nsIContent::ATTR_VALUE_NO_MATCH;
+}
+
+/* static */
+bool
+nsContentUtils::HasMutationListeners(nsINode* aNode,
+ uint32_t aType,
+ nsINode* aTargetForSubtreeModified)
+{
+ nsIDocument* 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->IsNodeOfType(nsINode::eCONTENT) &&
+ static_cast<nsIContent*>(aNode)->ChromeOnlyAccess()) {
+ 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;
+ }
+
+ if (aNode->IsNodeOfType(nsINode::eCONTENT)) {
+ nsIContent* content = static_cast<nsIContent*>(aNode);
+ nsIContent* insertionParent = content->GetXBLInsertionParent();
+ if (insertionParent) {
+ aNode = insertionParent;
+ continue;
+ }
+ }
+ aNode = aNode->GetParentNode();
+ }
+
+ return false;
+}
+
+/* static */
+bool
+nsContentUtils::HasMutationListeners(nsIDocument* 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,
+ nsIDocument* aOwnerDoc)
+{
+ NS_PRECONDITION(aChild, "Missing child");
+ NS_PRECONDITION(aChild->GetParentNode() == aParent, "Wrong parent");
+ NS_PRECONDITION(aChild->OwnerDoc() == aOwnerDoc, "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->IsContent() && aChild->AsContent()->IsInNativeAnonymousSubtree()) &&
+ !sDOMNodeRemovedSuppressCount) {
+ NS_ERROR("Want to fire DOMNodeRemoved event, but it's not safe");
+ WarnScriptWasIgnored(aOwnerDoc);
+ }
+ return;
+ }
+
+ if (HasMutationListeners(aChild,
+ NS_EVENT_BITS_MUTATION_NODEREMOVED, aParent)) {
+ InternalMutationEvent mutation(true, eLegacyNodeRemoved);
+ mutation.mRelatedNode = do_QueryInterface(aParent);
+
+ mozAutoSubtreeModified subtree(aOwnerDoc, 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->IsInUncomposedDoc() &&
+ 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;
+}
+
+/* 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(nsIAtom *aLocalName, nsIAtom *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);
+}
+
+/* static */
+nsresult
+nsContentUtils::CreateContextualFragment(nsINode* aContextNode,
+ const nsAString& aFragment,
+ bool aPreventScriptExecution,
+ nsIDOMDocumentFragment** aReturn)
+{
+ ErrorResult rv;
+ *aReturn = CreateContextualFragment(aContextNode, aFragment,
+ aPreventScriptExecution, rv).take();
+ return rv.StealNSResult();
+}
+
+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.
+ nsCOMPtr<nsIDocument> document = aContextNode->OwnerDoc();
+ bool isHTML = document->IsHTMLDocument();
+#ifdef DEBUG
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(document);
+ NS_ASSERTION(!isHTML || htmlDoc, "Should have HTMLDocument here!");
+#endif
+
+ if (isHTML) {
+ RefPtr<DocumentFragment> frag =
+ new DocumentFragment(document->NodeInfoManager());
+
+ nsCOMPtr<nsIContent> contextAsContent = do_QueryInterface(aContextNode);
+ if (contextAsContent && !contextAsContent->IsElement()) {
+ contextAsContent = contextAsContent->GetParent();
+ if (contextAsContent && !contextAsContent->IsElement()) {
+ // can this even happen?
+ contextAsContent = nullptr;
+ }
+ }
+
+ if (contextAsContent && !contextAsContent->IsHTMLElement(nsGkAtoms::html)) {
+ aRv = ParseFragmentHTML(aFragment, frag,
+ contextAsContent->NodeInfo()->NameAtom(),
+ contextAsContent->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;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aContextNode);
+ // just in case we have a text node
+ if (content && !content->IsElement())
+ content = content->GetParent();
+
+ while (content && content->IsElement()) {
+ nsString& tagName = *tagStack.AppendElement();
+ tagName = content->NodeInfo()->QualifiedName();
+
+ // see if we need to add xmlns declarations
+ uint32_t count = content->GetAttrCount();
+ bool setDefaultNamespace = false;
+ if (count > 0) {
+ uint32_t index;
+
+ for (index = 0; index < count; index++) {
+ const BorrowedAttrInfo info = content->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("=\"");
+ tagName.Append(uriStr);
+ tagName.Append('"');
+ }
+ }
+ }
+
+ if (!setDefaultNamespace) {
+ mozilla::dom::NodeInfo* info = content->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(" xmlns=\"");
+ tagName.Append(uriStr);
+ tagName.Append('"');
+ }
+ }
+
+ content = content->GetParent();
+ }
+
+ nsCOMPtr<nsIDOMDocumentFragment> frag;
+ aRv = ParseFragmentXML(aFragment, document, tagStack,
+ aPreventScriptExecution, getter_AddRefs(frag));
+ return frag.forget().downcast<DocumentFragment>();
+}
+
+/* static */
+void
+nsContentUtils::DropFragmentParsers()
+{
+ NS_IF_RELEASE(sHTMLFragmentParser);
+ NS_IF_RELEASE(sXMLFragmentParser);
+ NS_IF_RELEASE(sXMLFragmentSink);
+}
+
+/* static */
+void
+nsContentUtils::XPCOMShutdown()
+{
+ nsContentUtils::DropFragmentParsers();
+}
+
+/* static */
+nsresult
+nsContentUtils::ParseFragmentHTML(const nsAString& aSourceBuffer,
+ nsIContent* aTargetNode,
+ nsIAtom* aContextLocalName,
+ int32_t aContextNamespace,
+ bool aQuirks,
+ bool aPreventScriptExecution)
+{
+ AutoTimelineMarker m(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML");
+
+ if (nsContentUtils::sFragmentParsingActive) {
+ NS_NOTREACHED("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->ParseFragment(aSourceBuffer,
+ aTargetNode,
+ aContextLocalName,
+ aContextNamespace,
+ aQuirks,
+ aPreventScriptExecution);
+ return rv;
+}
+
+/* static */
+nsresult
+nsContentUtils::ParseDocumentHTML(const nsAString& aSourceBuffer,
+ nsIDocument* aTargetDocument,
+ bool aScriptingEnabledForNoscriptParsing)
+{
+ AutoTimelineMarker m(aTargetDocument->GetDocShell(), "Parse HTML");
+
+ if (nsContentUtils::sFragmentParsingActive) {
+ NS_NOTREACHED("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,
+ nsIDocument* aDocument,
+ nsTArray<nsString>& aTagStack,
+ bool aPreventScriptExecution,
+ nsIDOMDocumentFragment** aReturn)
+{
+ AutoTimelineMarker m(aDocument->GetDocShell(), "Parse XML");
+
+ if (nsContentUtils::sFragmentParsingActive) {
+ NS_NOTREACHED("Re-entrant fragment parsing attempted.");
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ mozilla::AutoRestore<bool> guard(nsContentUtils::sFragmentParsingActive);
+ nsContentUtils::sFragmentParsingActive = true;
+ if (!sXMLFragmentParser) {
+ nsCOMPtr<nsIParser> parser = do_CreateInstance(kCParserCID);
+ 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);
+
+ sXMLFragmentSink->SetTargetDocument(aDocument);
+ 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();
+
+ return rv;
+}
+
+/* static */
+nsresult
+nsContentUtils::ConvertToPlainText(const nsAString& aSourceBuffer,
+ nsAString& aResultBuffer,
+ uint32_t aFlags,
+ uint32_t aWrapCol)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:blank");
+ nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::Create();
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ nsresult rv = NS_NewDOMDocument(getter_AddRefs(domDocument),
+ EmptyString(),
+ EmptyString(),
+ nullptr,
+ uri,
+ uri,
+ principal,
+ true,
+ nullptr,
+ DocumentFlavorHTML);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(domDocument);
+ rv = nsContentUtils::ParseDocumentHTML(aSourceBuffer, document,
+ !(aFlags & nsIDocumentEncoder::OutputNoScriptContent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocumentEncoder> encoder = do_CreateInstance(
+ "@mozilla.org/layout/documentEncoder;1?type=text/plain");
+
+ rv = encoder->Init(domDocument, NS_LITERAL_STRING("text/plain"), aFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ encoder->SetWrapColumn(aWrapCol);
+
+ return encoder->EncodeToString(aResultBuffer);
+}
+
+/* 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.
+ nsIDocument* 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->IsNodeOfType(nsINode::eTEXT)) {
+ skipFirst = false;
+ continue;
+ }
+ nsContentUtils::MaybeFireNodeRemoved(child, aContent, doc);
+ }
+ }
+ }
+
+ // Might as well stick a batch around this since we're performing several
+ // mutations.
+ mozAutoDocUpdate updateBatch(aContent->GetComposedDoc(),
+ UPDATE_CONTENT_MODEL, true);
+ nsAutoMutationBatch mb;
+
+ uint32_t childCount = aContent->GetChildCount();
+
+ if (aTryReuse && !aValue.IsEmpty()) {
+ uint32_t removeIndex = 0;
+
+ for (uint32_t i = 0; i < childCount; ++i) {
+ nsIContent* child = aContent->GetChildAt(removeIndex);
+ if (removeIndex == 0 && child && child->IsNodeOfType(nsINode::eTEXT)) {
+ nsresult rv = child->SetText(aValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ removeIndex = 1;
+ }
+ else {
+ aContent->RemoveChildAt(removeIndex, true);
+ }
+ }
+
+ if (removeIndex == 1) {
+ return NS_OK;
+ }
+ }
+ else {
+ mb.Init(aContent, true, false);
+ for (uint32_t i = 0; i < childCount; ++i) {
+ aContent->RemoveChildAt(0, true);
+ }
+ }
+ mb.RemovalDone();
+
+ if (aValue.IsEmpty()) {
+ return NS_OK;
+ }
+
+ RefPtr<nsTextNode> textContent =
+ new nsTextNode(aContent->NodeInfo()->NodeInfoManager());
+
+ textContent->SetText(aValue, true);
+
+ nsresult rv = aContent->AppendChildTo(textContent, true);
+ mb.NodesAdded();
+ return rv;
+}
+
+static bool
+AppendNodeTextContentsRecurse(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 (child->IsNodeOfType(nsINode::eTEXT)) {
+ bool ok = child->AppendTextTo(aResult, aFallible);
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool
+nsContentUtils::AppendNodeTextContent(nsINode* aNode, bool aDeep,
+ nsAString& aResult,
+ const fallible_t& aFallible)
+{
+ if (aNode->IsNodeOfType(nsINode::eTEXT)) {
+ return static_cast<nsIContent*>(aNode)->AppendTextTo(aResult,
+ aFallible);
+ }
+ else if (aDeep) {
+ return AppendNodeTextContentsRecurse(aNode, aResult, aFallible);
+ }
+ else {
+ for (nsIContent* child = aNode->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsNodeOfType(nsINode::eTEXT)) {
+ bool ok = child->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->IsNodeOfType(nsINode::eTEXT) &&
+ child->TextLength() > 0) {
+ return true;
+ }
+
+ if (aDiscoverMode == eRecurseIntoChildren &&
+ HasNonEmptyTextContent(child, aDiscoverMode)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */
+bool
+nsContentUtils::IsInSameAnonymousTree(const nsINode* aNode,
+ const nsIContent* aContent)
+{
+ NS_PRECONDITION(aNode,
+ "Must have a node to work with");
+ NS_PRECONDITION(aContent,
+ "Must have a content to work with");
+
+ if (!aNode->IsNodeOfType(nsINode::eCONTENT)) {
+ /**
+ * The root isn't an nsIContent, so it's a document or attribute. The only
+ * nodes in the same anonymous subtree as it will have a null
+ * bindingParent.
+ *
+ * XXXbz strictly speaking, that's not true for attribute nodes.
+ */
+ return aContent->GetBindingParent() == nullptr;
+ }
+
+ const nsIContent* nodeAsContent = static_cast<const nsIContent*>(aNode);
+
+ // For nodes in a shadow tree, it is insufficient to simply compare
+ // the binding parent because a node may host multiple ShadowRoots,
+ // thus nodes in different shadow tree may have the same binding parent.
+ if (aNode->IsInShadowTree()) {
+ return nodeAsContent->GetContainingShadow() ==
+ aContent->GetContainingShadow();
+ }
+
+ return nodeAsContent->GetBindingParent() == aContent->GetBindingParent();
+}
+
+class AnonymousContentDestroyer : public Runnable {
+public:
+ explicit AnonymousContentDestroyer(nsCOMPtr<nsIContent>* aContent) {
+ mContent.swap(*aContent);
+ mParent = mContent->GetParent();
+ mDoc = mContent->OwnerDoc();
+ }
+ explicit AnonymousContentDestroyer(nsCOMPtr<Element>* aElement) {
+ mContent = aElement->forget();
+ mParent = mContent->GetParent();
+ mDoc = mContent->OwnerDoc();
+ }
+ NS_IMETHOD Run() override {
+ mContent->UnbindFromTree();
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIContent> mContent;
+ // Hold strong refs to the parent content and document so that they
+ // don't die unexpectedly
+ nsCOMPtr<nsIDocument> mDoc;
+ nsCOMPtr<nsIContent> mParent;
+};
+
+/* static */
+void
+nsContentUtils::DestroyAnonymousContent(nsCOMPtr<nsIContent>* aContent)
+{
+ if (*aContent) {
+ AddScriptRunner(new AnonymousContentDestroyer(aContent));
+ }
+}
+
+/* static */
+void
+nsContentUtils::DestroyAnonymousContent(nsCOMPtr<Element>* aElement)
+{
+ if (*aElement) {
+ AddScriptRunner(new AnonymousContentDestroyer(aElement));
+ }
+}
+
+/* static */
+void
+nsContentUtils::NotifyInstalledMenuKeyboardListener(bool aInstalling)
+{
+ IMEStateManager::OnInstalledMenuKeyboardListener(aInstalling);
+}
+
+static bool SchemeIs(nsIURI* aURI, const char* aScheme)
+{
+ nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(baseURI, false);
+
+ bool isScheme = false;
+ return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme;
+}
+
+bool
+nsContentUtils::IsSystemPrincipal(nsIPrincipal* aPrincipal)
+{
+ MOZ_ASSERT(IsInitialized());
+ return aPrincipal == sSystemPrincipal;
+}
+
+bool
+nsContentUtils::IsExpandedPrincipal(nsIPrincipal* aPrincipal)
+{
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
+ return !!ep;
+}
+
+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, nsPresContext *aPresContext,
+ nsIURI *aLinkURI, const nsString &aTargetSpec,
+ bool aClick, bool aIsUserTriggered,
+ bool aIsTrusted)
+{
+ NS_ASSERTION(aPresContext, "Need a nsPresContext");
+ NS_PRECONDITION(aLinkURI, "No link URI");
+
+ if (aContent->IsEditable()) {
+ return;
+ }
+
+ nsILinkHandler *handler = aPresContext->GetLinkHandler();
+ if (!handler) {
+ return;
+ }
+
+ if (!aClick) {
+ handler->OnOverLink(aContent, aLinkURI, aTargetSpec.get());
+ return;
+ }
+
+ // Check that this page is allowed to load this URI.
+ nsresult proceed = NS_OK;
+
+ if (sSecurityManager) {
+ uint32_t flag =
+ aIsUserTriggered ?
+ (uint32_t)nsIScriptSecurityManager::STANDARD :
+ (uint32_t)nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT;
+ proceed =
+ sSecurityManager->CheckLoadURIWithPrincipal(aContent->NodePrincipal(),
+ aLinkURI, flag);
+ }
+
+ // 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->GetAttr(kNameSpaceID_None, nsGkAtoms::download, fileName) ||
+ NS_FAILED(aContent->NodePrincipal()->CheckMayLoad(aLinkURI, false, true))) {
+ fileName.SetIsVoid(true); // No actionable download attribute was found.
+ }
+
+ handler->OnLinkClick(aContent, aLinkURI,
+ fileName.IsVoid() ? aTargetSpec.get() : EmptyString().get(),
+ fileName, nullptr, nullptr, aIsTrusted);
+ }
+}
+
+/* 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]) {
+ nsAdoptingString tmp = Preferences::GetLocalizedString("intl.ellipsis");
+ 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_DIAGNOSTIC_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
+ 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 */
+nsIWindowProvider*
+nsContentUtils::GetWindowProviderForContentProcess()
+{
+ MOZ_ASSERT(XRE_IsContentProcess());
+ return ContentChild::GetSingleton();
+}
+
+/* static */
+already_AddRefed<nsPIDOMWindowOuter>
+nsContentUtils::GetMostRecentNonPBWindow()
+{
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+ nsCOMPtr<nsIWindowMediator_44> wm = do_QueryInterface(windowMediator);
+
+ 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(nsIDocument* aDocument)
+{
+ nsAutoString msg;
+ if (aDocument) {
+ nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI();
+ if (uri) {
+ msg.Append(NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault()));
+ msg.AppendLiteral(" : ");
+ }
+ }
+ msg.AppendLiteral("Unable to run script because scripts are blocked internally.");
+
+ LogSimpleConsoleError(msg, "DOM");
+}
+
+/* static */
+void
+nsContentUtils::AddScriptRunner(already_AddRefed<nsIRunnable> aRunnable)
+{
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ if (!runnable) {
+ return;
+ }
+
+ if (sScriptBlockerCount) {
+ sBlockedScriptRunners->AppendElement(runnable.forget());
+ return;
+ }
+
+ runnable->Run();
+}
+
+/* static */
+void
+nsContentUtils::AddScriptRunner(nsIRunnable* aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ AddScriptRunner(runnable.forget());
+}
+
+/* static */
+void
+nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
+{
+ MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
+ CycleCollectedJSContext::Get()->RunInStableState(Move(aRunnable));
+}
+
+/* static */
+void
+nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable)
+{
+ MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
+ CycleCollectedJSContext::Get()->RunInMetastableState(Move(aRunnable));
+}
+
+void
+nsContentUtils::EnterMicroTask()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ ++sMicroTaskLevel;
+}
+
+void
+nsContentUtils::LeaveMicroTask()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (--sMicroTaskLevel == 0) {
+ PerformMainThreadMicroTaskCheckpoint();
+ }
+}
+
+bool
+nsContentUtils::IsInMicroTask()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return sMicroTaskLevel != 0;
+}
+
+uint32_t
+nsContentUtils::MicroTaskLevel()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return sMicroTaskLevel;
+}
+
+void
+nsContentUtils::SetMicroTaskLevel(uint32_t aLevel)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ sMicroTaskLevel = aLevel;
+}
+
+void
+nsContentUtils::PerformMainThreadMicroTaskCheckpoint()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsDOMMutationObserver::HandleMutations();
+}
+
+/*
+ * Helper function for nsContentUtils::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(nsIDocument *aDocument,
+ 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. */
+ nsCOMPtr<nsIAtom> key_atom = NS_Atomize(key);
+ if (key_atom == nsGkAtoms::height)
+ aDocument->SetHeaderData(nsGkAtoms::viewport_height, value);
+ else if (key_atom == nsGkAtoms::width)
+ aDocument->SetHeaderData(nsGkAtoms::viewport_width, value);
+ else if (key_atom == nsGkAtoms::initial_scale)
+ aDocument->SetHeaderData(nsGkAtoms::viewport_initial_scale, value);
+ else if (key_atom == nsGkAtoms::minimum_scale)
+ aDocument->SetHeaderData(nsGkAtoms::viewport_minimum_scale, value);
+ else if (key_atom == nsGkAtoms::maximum_scale)
+ aDocument->SetHeaderData(nsGkAtoms::viewport_maximum_scale, value);
+ else if (key_atom == nsGkAtoms::user_scalable)
+ aDocument->SetHeaderData(nsGkAtoms::viewport_user_scalable, value);
+}
+
+#define IS_SEPARATOR(c) ((c == '=') || (c == ',') || (c == ';') || \
+ (c == '\t') || (c == '\n') || (c == '\r'))
+
+/* static */
+nsresult
+nsContentUtils::ProcessViewportInfo(nsIDocument *aDocument,
+ const nsAString &viewportInfo) {
+
+ /* We never fail. */
+ nsresult rv = NS_OK;
+
+ aDocument->SetHeaderData(nsGkAtoms::viewport, viewportInfo);
+
+ /* Iterators. */
+ nsAString::const_iterator tip, tail, end;
+ viewportInfo.BeginReading(tip);
+ tail = tip;
+ viewportInfo.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(aDocument, Substring(tail, tip));
+
+ /* Skip separators. */
+ while ((tip != end) && (IS_SEPARATOR(*tip) || nsCRT::IsAsciiSpace(*tip)))
+ ++tip;
+ }
+
+ return rv;
+
+}
+
+#undef IS_SEPARATOR
+
+/* static */
+void
+nsContentUtils::HidePopupsInDocument(nsIDocument* aDocument)
+{
+#ifdef MOZ_XUL
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm && aDocument) {
+ nsCOMPtr<nsIDocShellTreeItem> docShellToHide = aDocument->GetDocShell();
+ if (docShellToHide)
+ pm->HidePopupsInDocShell(docShellToHide);
+ }
+#endif
+}
+
+/* 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
+
+ nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
+ nsCOMPtr<DataTransfer> initialDataTransfer;
+ dragSession->GetDataTransfer(getter_AddRefs(dataTransfer));
+ if (dataTransfer) {
+ initialDataTransfer = do_QueryInterface(dataTransfer);
+ if (!initialDataTransfer) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // 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, effectAllowed;
+ dragSession->GetDragAction(&action);
+ aDragEvent->mDataTransfer->GetEffectAllowedInt(&effectAllowed);
+ 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.
+ uint32_t dropEffect;
+ initialDataTransfer->GetDropEffectInt(&dropEffect);
+ aDragEvent->mDataTransfer->SetDropEffectInt(dropEffect);
+ }
+
+ 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 = do_QueryInterface(aDropEvent->mOriginalTarget);
+ if (!target) {
+ return true;
+ }
+
+ nsIDocument* targetDoc = target->OwnerDoc();
+ nsPIDOMWindowOuter* targetWin = targetDoc->GetWindow();
+ if (!targetWin) {
+ return true;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> tdsti = targetWin->GetDocShell();
+ if (!tdsti) {
+ return true;
+ }
+
+ // Always allow dropping onto chrome shells.
+ if (tdsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ return false;
+ }
+
+ // If there is no source node, then this is a drag from another
+ // application, which should be allowed.
+ nsCOMPtr<nsIDOMDocument> sourceDocument;
+ aDragSession->GetSourceDocument(getter_AddRefs(sourceDocument));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(sourceDocument);
+ if (doc) {
+ // 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.
+ do {
+ doc = doc->GetParentDocument();
+ if (doc == targetDoc) {
+ // The drag is from a child frame.
+ return true;
+ }
+ } while (doc);
+ }
+
+ return false;
+}
+
+/* static */
+bool
+nsContentUtils::URIIsLocalFile(nsIURI *aURI)
+{
+ bool isFile;
+ nsCOMPtr<nsINetUtil> util = do_QueryInterface(sIOService);
+
+ // 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 */
+nsIScriptContext*
+nsContentUtils::GetContextForEventHandlers(nsINode* aNode,
+ nsresult* aRv)
+{
+ *aRv = NS_OK;
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* sgo =
+ aNode->OwnerDoc()->GetScriptHandlingObject(hasHadScriptObject);
+ // It is bad if the document doesn't have event handling context,
+ // but it used to have one.
+ if (!sgo && hasHadScriptObject) {
+ *aRv = NS_ERROR_UNEXPECTED;
+ return nullptr;
+ }
+
+ if (sgo) {
+ nsIScriptContext* scx = sgo->GetContext();
+ // Bad, no context from script global object!
+ if (!scx) {
+ *aRv = NS_ERROR_UNEXPECTED;
+ return nullptr;
+ }
+ return scx;
+ }
+
+ return nullptr;
+}
+
+/* static */
+JSContext *
+nsContentUtils::GetCurrentJSContext()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsInitialized());
+ if (!IsJSAPIActive()) {
+ return nullptr;
+ }
+ return danger::GetJSContext();
+}
+
+/* static */
+JSContext *
+nsContentUtils::GetCurrentJSContextForThread()
+{
+ MOZ_ASSERT(IsInitialized());
+ if (MOZ_LIKELY(NS_IsMainThread())) {
+ return GetCurrentJSContext();
+ } else {
+ return workers::GetCurrentThreadJSContext();
+ }
+}
+
+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(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, false);
+ if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) {
+ rv = oldPrincipal->CheckMayLoad(newOriginalURI, false, false);
+ }
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(SameOriginCheckerImpl,
+ nsIChannelEventSink,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+SameOriginCheckerImpl::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* cb)
+{
+ NS_PRECONDITION(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(nsIPrincipal* aPrincipal, nsACString& aOrigin)
+{
+ NS_PRECONDITION(aPrincipal, "missing principal");
+
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (uri) {
+ return GetASCIIOrigin(uri, aOrigin);
+ }
+
+ aOrigin.AssignLiteral("null");
+
+ return NS_OK;
+}
+
+/* static */
+nsresult
+nsContentUtils::GetASCIIOrigin(nsIURI* aURI, nsACString& aOrigin)
+{
+ NS_PRECONDITION(aURI, "missing uri");
+
+ // For Blob URI we have to return the origin of page using its principal.
+ nsCOMPtr<nsIURIWithPrincipal> uriWithPrincipal = do_QueryInterface(aURI);
+ if (uriWithPrincipal) {
+ nsCOMPtr<nsIPrincipal> principal;
+ uriWithPrincipal->GetPrincipal(getter_AddRefs(principal));
+
+ if (principal) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = principal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (uri && uri != aURI) {
+ return GetASCIIOrigin(uri, aOrigin);
+ }
+ }
+ }
+
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ nsCString host;
+ nsresult rv = uri->GetAsciiHost(host);
+
+ if (NS_SUCCEEDED(rv) && !host.IsEmpty()) {
+ nsCString scheme;
+ rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port = -1;
+ uri->GetPort(&port);
+ if (port != -1 && port == NS_GetDefaultPort(scheme.get()))
+ port = -1;
+
+ nsCString hostPort;
+ rv = NS_GenerateHostPort(host, port, hostPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOrigin = scheme + NS_LITERAL_CSTRING("://") + hostPort;
+ }
+ else {
+ aOrigin.AssignLiteral("null");
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult
+nsContentUtils::GetUTFOrigin(nsIPrincipal* aPrincipal, nsAString& aOrigin)
+{
+ NS_PRECONDITION(aPrincipal, "missing principal");
+
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (uri) {
+ return GetUTFOrigin(uri, aOrigin);
+ }
+
+ aOrigin.AssignLiteral("null");
+
+ return NS_OK;
+}
+
+/* static */
+nsresult
+nsContentUtils::GetUTFOrigin(nsIURI* aURI, nsAString& aOrigin)
+{
+ NS_PRECONDITION(aURI, "missing uri");
+
+ // For Blob URI we have to return the origin of page using its principal.
+ nsCOMPtr<nsIURIWithPrincipal> uriWithPrincipal = do_QueryInterface(aURI);
+ if (uriWithPrincipal) {
+ nsCOMPtr<nsIPrincipal> principal;
+ uriWithPrincipal->GetPrincipal(getter_AddRefs(principal));
+
+ if (principal) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = principal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (uri && uri != aURI) {
+ return GetUTFOrigin(uri, aOrigin);
+ }
+ } else {
+ // We are probably dealing with an unknown blob URL.
+ bool isBlobURL = false;
+ nsresult rv = aURI->SchemeIs(BLOBURI_SCHEME, &isBlobURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isBlobURL) {
+ nsAutoCString path;
+ rv = aURI->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), path);
+ if (NS_FAILED(rv)) {
+ aOrigin.AssignLiteral("null");
+ return NS_OK;
+ }
+
+ return GetUTFOrigin(uri, aOrigin);
+ }
+ }
+ }
+
+ aOrigin.Truncate();
+
+ nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ nsCString host;
+ nsresult rv = uri->GetHost(host);
+
+ if (NS_SUCCEEDED(rv) && !host.IsEmpty()) {
+ nsCString scheme;
+ rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port = -1;
+ uri->GetPort(&port);
+ if (port != -1 && port == NS_GetDefaultPort(scheme.get()))
+ port = -1;
+
+ nsCString hostPort;
+ rv = NS_GenerateHostPort(host, port, hostPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOrigin = NS_ConvertUTF8toUTF16(
+ scheme + NS_LITERAL_CSTRING("://") + hostPort);
+ }
+ else {
+ aOrigin.AssignLiteral("null");
+ }
+
+ 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, false, aAllowIfInheritsPrincipal));
+}
+
+nsContentTypeParser::nsContentTypeParser(const nsAString& aString)
+ : mString(aString), mService(nullptr)
+{
+ CallGetService("@mozilla.org/network/mime-hdrparam;1", &mService);
+}
+
+nsContentTypeParser::~nsContentTypeParser()
+{
+ NS_IF_RELEASE(mService);
+}
+
+nsresult
+nsContentTypeParser::GetParameter(const char* aParameterName,
+ nsAString& aResult) const
+{
+ NS_ENSURE_TRUE(mService, NS_ERROR_FAILURE);
+ return mService->GetParameterHTTP(mString, aParameterName,
+ EmptyCString(), false, nullptr,
+ aResult);
+}
+
+nsresult
+nsContentTypeParser::GetType(nsAString& aResult) const
+{
+ nsresult rv = GetParameter(nullptr, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsContentUtils::ASCIIToLower(aResult);
+ return NS_OK;
+}
+
+/* static */
+
+bool
+nsContentUtils::CanAccessNativeAnon()
+{
+ return LegacyIsCallerChromeOrNativeCode() || IsCallerContentXBL();
+}
+
+/* static */ nsresult
+nsContentUtils::DispatchXULCommand(nsIContent* aTarget,
+ bool aTrusted,
+ nsIDOMEvent* aSourceEvent,
+ nsIPresShell* aShell,
+ bool aCtrl,
+ bool aAlt,
+ bool aShift,
+ bool aMeta)
+{
+ NS_ENSURE_STATE(aTarget);
+ nsIDocument* doc = aTarget->OwnerDoc();
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
+ NS_ENSURE_STATE(domDoc);
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"),
+ getter_AddRefs(event));
+ nsCOMPtr<nsIDOMXULCommandEvent> xulCommand = do_QueryInterface(event);
+ nsresult rv = xulCommand->InitCommandEvent(NS_LITERAL_STRING("command"),
+ true, true, doc->GetInnerWindow(),
+ 0, aCtrl, aAlt, aShift, aMeta,
+ aSourceEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aShell) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsCOMPtr<nsIPresShell> kungFuDeathGrip = aShell;
+ return aShell->HandleDOMEventWithTarget(aTarget, event, &status);
+ }
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(aTarget);
+ NS_ENSURE_STATE(target);
+ bool dummy;
+ return target->DispatchEvent(event, &dummy);
+}
+
+// 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();
+ }
+
+ nsresult rv = NS_OK;
+ JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
+ 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;
+ }
+
+ int32_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 {
+ nsAttrValue::AtomArray mClasses;
+ nsCaseTreatment mCaseTreatment;
+};
+
+// static
+bool
+nsContentUtils::MatchClassNames(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData)
+{
+ // We can't match if there are no class names
+ const nsAttrValue* classAttr = aContent->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
+ ClassMatchingInfo* info = new ClassMatchingInfo;
+ if (attrValue.Type() == nsAttrValue::eAtomArray) {
+ info->mClasses.SwapElements(*(attrValue.GetAtomArrayValue()));
+ } 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->GetFocusedContent() == aContent;
+}
+
+bool
+nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent)
+{
+ //XXXsmaug Shadow DOM spec issue!
+ // We may need to change this to GetComposedDoc().
+ nsIDocument* doc = aContent->GetUncomposedDoc();
+ if (!doc) {
+ return false;
+ }
+
+ // If the subdocument lives in another process, the frame is
+ // tabbable.
+ if (EventStateManager::IsRemoteTarget(aContent)) {
+ return true;
+ }
+
+ // XXXbz should this use OwnerDoc() for GetSubDocumentFor?
+ // sXBL/XBL2 issue!
+ nsIDocument* subDoc = doc->GetSubDocumentFor(aContent);
+ if (!subDoc) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = subDoc->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ docShell->GetContentViewer(getter_AddRefs(contentViewer));
+ if (!contentViewer) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContentViewer> zombieViewer;
+ contentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer));
+
+ // If there are 2 viewers for the current docshell, that
+ // means the current document is a zombie document.
+ // Only navigate into the subdocument if it's not a zombie.
+ return !zombieViewer;
+}
+
+bool
+nsContentUtils::IsUserFocusIgnored(nsINode* aNode)
+{
+ if (!nsGenericHTMLFrameElement::BrowserFramesEnabled()) {
+ return false;
+ }
+
+ // Check if our mozbrowser iframe ancestors has ignoreuserfocus attribute.
+ while (aNode) {
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aNode);
+ if (browserFrame &&
+ aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::ignoreuserfocus) &&
+ browserFrame->GetReallyIsBrowserOrApp()) {
+ return true;
+ }
+ nsPIDOMWindowOuter* win = aNode->OwnerDoc()->GetWindow();
+ aNode = win ? win->GetFrameElementInternal() : nullptr;
+ }
+
+ return false;
+}
+
+bool
+nsContentUtils::HasScrollgrab(nsIContent* aContent)
+{
+ nsGenericHTMLElement* element = nsGenericHTMLElement::FromContentOrNull(aContent);
+ return element && element->Scrollgrab();
+}
+
+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 (nsCOMPtr<nsIDocument> doc = aWindow->GetDoc()) {
+ doc->FlushPendingNotifications(Flush_Layout);
+ }
+
+ if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
+ int32_t i = 0, i_end;
+ docShell->GetChildCount(&i_end);
+ for (; i < i_end; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ docShell->GetChildAt(i, getter_AddRefs(item));
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = item->GetWindow()) {
+ FlushLayoutForTree(win);
+ }
+ }
+ }
+}
+
+void nsContentUtils::RemoveNewlines(nsString &aString)
+{
+ // strip CR/LF and null
+ static const char badChars[] = {'\r', '\n', 0};
+ aString.StripChars(badChars);
+}
+
+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);
+}
+
+nsIPresShell*
+nsContentUtils::FindPresShellForDocument(const nsIDocument* aDoc)
+{
+ const nsIDocument* doc = aDoc;
+ nsIDocument* displayDoc = doc->GetDisplayDocument();
+ if (displayDoc) {
+ doc = displayDoc;
+ }
+
+ nsIPresShell* shell = doc->GetShell();
+ if (shell) {
+ return shell;
+ }
+
+ 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);
+ nsIPresShell* presShell = docShell->GetPresShell();
+ if (presShell) {
+ return presShell;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ docShellTreeItem->GetParent(getter_AddRefs(parent));
+ docShellTreeItem = parent;
+ }
+
+ return nullptr;
+}
+
+nsIWidget*
+nsContentUtils::WidgetForDocument(const nsIDocument* aDoc)
+{
+ nsIPresShell* shell = FindPresShellForDocument(aDoc);
+ if (shell) {
+ nsViewManager* VM = shell->GetViewManager();
+ if (VM) {
+ nsView* rootView = VM->GetRootView();
+ if (rootView) {
+ nsView* displayRoot = nsViewManager::GetDisplayRootFor(rootView);
+ if (displayRoot) {
+ return displayRoot->GetNearestWidget(nullptr);
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+static already_AddRefed<LayerManager>
+LayerManagerForDocumentInternal(const nsIDocument *aDoc, bool aRequirePersistent)
+{
+ nsIWidget *widget = nsContentUtils::WidgetForDocument(aDoc);
+ if (widget) {
+ RefPtr<LayerManager> manager =
+ widget->GetLayerManager(aRequirePersistent ? nsIWidget::LAYER_MANAGER_PERSISTENT :
+ nsIWidget::LAYER_MANAGER_CURRENT);
+ return manager.forget();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<LayerManager>
+nsContentUtils::LayerManagerForDocument(const nsIDocument *aDoc)
+{
+ return LayerManagerForDocumentInternal(aDoc, false);
+}
+
+already_AddRefed<LayerManager>
+nsContentUtils::PersistentLayerManagerForDocument(nsIDocument *aDoc)
+{
+ return LayerManagerForDocumentInternal(aDoc, true);
+}
+
+bool
+nsContentUtils::AllowXULXBLForPrincipal(nsIPrincipal* aPrincipal)
+{
+ if (IsSystemPrincipal(aPrincipal)) {
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> princURI;
+ aPrincipal->GetURI(getter_AddRefs(princURI));
+
+ return princURI &&
+ ((sAllowXULXBL_for_file && SchemeIs(princURI, "file")) ||
+ IsSitePermAllow(aPrincipal, "allowXULXBL"));
+}
+
+bool
+nsContentUtils::IsPDFJSEnabled()
+{
+ nsCOMPtr<nsIStreamConverterService> convServ =
+ do_GetService("@mozilla.org/streamConverters;1");
+ nsresult rv = NS_ERROR_FAILURE;
+ bool canConvert = false;
+ if (convServ) {
+ rv = convServ->CanConvert("application/pdf", "text/html", &canConvert);
+ }
+ return NS_SUCCEEDED(rv) && canConvert;
+}
+
+bool
+nsContentUtils::IsSWFPlayerEnabled()
+{
+ nsCOMPtr<nsIStreamConverterService> convServ =
+ do_GetService("@mozilla.org/streamConverters;1");
+ nsresult rv = NS_ERROR_FAILURE;
+ bool canConvert = false;
+ if (convServ) {
+ rv = convServ->CanConvert("application/x-shockwave-flash",
+ "text/html", &canConvert);
+ }
+ return NS_SUCCEEDED(rv) && canConvert;
+}
+
+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;
+
+ nsXPIDLCString contractID;
+ nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers",
+ PromiseFlatCString(aType).get(),
+ getter_Copies(contractID));
+ if (NS_SUCCEEDED(rv)) {
+ docFactory = do_GetService(contractID);
+ if (docFactory && aLoaderType) {
+ if (contractID.EqualsLiteral(CONTENT_DLF_CONTRACTID))
+ *aLoaderType = TYPE_CONTENT;
+ else if (contractID.EqualsLiteral(PLUGIN_DLF_CONTRACTID))
+ *aLoaderType = TYPE_PLUGIN;
+ 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, nsIDocument* aDocument,
+ JSContext* cx)
+{
+ MOZ_ASSERT(JS_IsExceptionPending(cx));
+
+ JS::RootedValue exn(cx);
+ if (!JS_GetPendingException(cx, &exn)) {
+ return;
+ }
+ if (!exn.isObject()) {
+ // If pending exception is not an object, it should be OOM.
+ return;
+ }
+
+ JS::AutoSaveExceptionState savedExc(cx);
+ JS::RootedObject exnObj(cx, &exn.toObject());
+ JS::RootedValue messageVal(cx);
+ if (!JS_GetProperty(cx, exnObj, "message", &messageVal)) {
+ return;
+ }
+ MOZ_ASSERT(messageVal.isString());
+
+ JS::RootedString messageStr(cx, messageVal.toString());
+ MOZ_ASSERT(messageStr);
+
+ nsAutoString wideMessage;
+ if (!AssignJSString(cx, wideMessage, messageStr)) {
+ return;
+ }
+
+ const nsString& pattern = PromiseFlatString(aPattern);
+ const char16_t *strings[] = { pattern.get(), wideMessage.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ aDocument,
+ nsContentUtils::eDOM_PROPERTIES,
+ "PatternAttributeCompileFailure",
+ strings, ArrayLength(strings));
+ savedExc.drop();
+}
+
+// static
+bool
+nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern,
+ nsIDocument* aDocument)
+{
+ NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ // We can use the junk scope here, because we're just using it for
+ // regexp evaluation, not actual script execution.
+ JSAutoCompartment ac(cx, xpc::UnprivilegedJunkScope());
+
+ // The pattern has to match the entire value.
+ aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0);
+ aPattern.AppendLiteral(")$");
+
+ JS::Rooted<JSObject*> re(cx,
+ JS_NewUCRegExpObject(cx,
+ static_cast<char16_t*>(aPattern.BeginWriting()),
+ aPattern.Length(), JSREG_UNICODE));
+ if (!re) {
+ // Remove extra patterns added above to report with the original pattern.
+ aPattern.Cut(0, 4);
+ aPattern.Cut(aPattern.Length() - 2, 2);
+ ReportPatternCompileFailure(aPattern, aDocument, cx);
+ return true;
+ }
+
+ JS::Rooted<JS::Value> rval(cx, JS::NullValue());
+ size_t idx = 0;
+ if (!JS_ExecuteRegExpNoStatics(cx, re,
+ static_cast<char16_t*>(aValue.BeginWriting()),
+ aValue.Length(), &idx, true, &rval)) {
+ return true;
+ }
+
+ return !rval.isNull();
+}
+
+// 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, false)) &&
+ // One more check here. CheckMayLoad will always return true for the
+ // system principal, but we do NOT want to inherit in that case.
+ !IsSystemPrincipal(aLoadingPrincipal));
+ }
+ return inherit;
+}
+
+/* static */
+bool
+nsContentUtils::IsFullScreenApiEnabled()
+{
+ return sIsFullScreenApiEnabled;
+}
+
+/* static */
+bool
+nsContentUtils::IsRequestFullScreenAllowed()
+{
+ return !sTrustedFullScreenOnly ||
+ EventStateManager::IsHandlingUserInput() ||
+ IsCallerChrome();
+}
+
+/* static */
+bool
+nsContentUtils::IsCutCopyAllowed()
+{
+ if ((!IsCutCopyRestricted() && EventStateManager::IsHandlingUserInput()) ||
+ IsCallerChrome()) {
+ return true;
+ }
+
+ return BasePrincipal::Cast(SubjectPrincipal())->AddonHasPermission(NS_LITERAL_STRING("clipboardWrite"));
+}
+
+/* static */
+bool
+nsContentUtils::IsFrameTimingEnabled()
+{
+ return sIsFrameTimingPrefEnabled;
+}
+
+/* static */
+bool
+nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2)
+{
+ if (!aDoc1 || !aDoc2) {
+ return false;
+ }
+ bool principalsEqual = false;
+ aDoc1->NodePrincipal()->Equals(aDoc2->NodePrincipal(), &principalsEqual);
+ return principalsEqual;
+}
+
+/* static */
+bool
+nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
+{
+#ifdef XP_MACOSX
+ // We control dispatch to all mac plugins.
+ return false;
+#else
+ if (!aContent || !aContent->IsInUncomposedDoc()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(aContent);
+ if (!olc) {
+ return false;
+ }
+
+ RefPtr<nsNPAPIPluginInstance> plugin;
+ olc->GetPluginInstance(getter_AddRefs(plugin));
+ if (!plugin) {
+ return false;
+ }
+
+ bool isWindowless = false;
+ nsresult res = plugin->IsWindowless(&isWindowless);
+ if (NS_FAILED(res)) {
+ return false;
+ }
+
+ return !isWindowless;
+#endif
+}
+
+/* static */
+void
+nsContentUtils::FireMutationEventsForDirectParsing(nsIDocument* 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 */
+nsIDocument*
+nsContentUtils::GetRootDocument(nsIDocument* aDoc)
+{
+ if (!aDoc) {
+ return nullptr;
+ }
+ nsIDocument* doc = aDoc;
+ while (doc->GetParentDocument()) {
+ doc = doc->GetParentDocument();
+ }
+ return doc;
+}
+
+/* static */
+bool
+nsContentUtils::IsInPointerLockContext(nsPIDOMWindowOuter* aWin)
+{
+ if (!aWin) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocument> pointerLockedDoc =
+ do_QueryReferent(EventStateManager::sPointerLockedDoc);
+ if (!pointerLockedDoc || !pointerLockedDoc->GetWindow()) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> lockTop =
+ pointerLockedDoc->GetWindow()->GetScriptableTop();
+ nsCOMPtr<nsPIDOMWindowOuter> top = aWin->GetScriptableTop();
+
+ return top == lockTop;
+}
+
+// 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,
+ int32_t& aOutStartOffset,
+ int32_t& aOutEndOffset)
+{
+ MOZ_ASSERT(aSelection && aRoot);
+
+ if (!aSelection->RangeCount()) {
+ // Nothing selected
+ aOutStartOffset = aOutEndOffset = 0;
+ return;
+ }
+
+ nsCOMPtr<nsINode> anchorNode = aSelection->GetAnchorNode();
+ uint32_t anchorOffset = aSelection->AnchorOffset();
+ nsCOMPtr<nsINode> focusNode = aSelection->GetFocusNode();
+ uint32_t focusOffset = aSelection->FocusOffset();
+
+ // We have at most two children, consisting of an optional text node followed
+ // by an optional <br>.
+ NS_ASSERTION(aRoot->GetChildCount() <= 2, "Unexpected children");
+ nsCOMPtr<nsIContent> firstChild = aRoot->GetFirstChild();
+#ifdef DEBUG
+ nsCOMPtr<nsIContent> lastChild = aRoot->GetLastChild();
+ NS_ASSERTION(anchorNode == aRoot || anchorNode == firstChild ||
+ anchorNode == lastChild, "Unexpected anchorNode");
+ NS_ASSERTION(focusNode == aRoot || focusNode == firstChild ||
+ focusNode == lastChild, "Unexpected focusNode");
+#endif
+ if (!firstChild || !firstChild->IsNodeOfType(nsINode::eTEXT)) {
+ // No text node, so everything is 0
+ anchorOffset = focusOffset = 0;
+ } else {
+ // First child is text. If the anchor/focus 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 ((anchorNode == aRoot && anchorOffset != 0) ||
+ (anchorNode != aRoot && anchorNode != firstChild)) {
+ anchorOffset = firstChild->Length();
+ }
+ if ((focusNode == aRoot && focusOffset != 0) ||
+ (focusNode != aRoot && focusNode != firstChild)) {
+ focusOffset = firstChild->Length();
+ }
+ }
+
+ // Make sure aOutStartOffset <= aOutEndOffset.
+ aOutStartOffset = std::min(anchorOffset, focusOffset);
+ aOutEndOffset = std::max(anchorOffset, focusOffset);
+}
+
+
+nsIEditor*
+nsContentUtils::GetHTMLEditor(nsPresContext* aPresContext)
+{
+ nsCOMPtr<nsIDocShell> docShell(aPresContext->GetDocShell());
+ bool isEditable;
+ if (!docShell ||
+ NS_FAILED(docShell->GetEditable(&isEditable)) || !isEditable)
+ return nullptr;
+
+ nsCOMPtr<nsIEditor> editor;
+ docShell->GetEditor(getter_AddRefs(editor));
+ return editor;
+}
+
+bool
+nsContentUtils::IsContentInsertionPoint(nsIContent* aContent)
+{
+ // Check if the content is a XBL insertion point.
+ if (aContent->IsActiveChildrenElement()) {
+ return true;
+ }
+
+ // Check if the content is a web components content insertion point.
+ HTMLContentElement* contentElement =
+ HTMLContentElement::FromContent(aContent);
+ return contentElement && contentElement->IsInsertionPoint();
+}
+
+// static
+bool
+nsContentUtils::HasDistributedChildren(nsIContent* aContent)
+{
+ if (!aContent) {
+ return false;
+ }
+
+ if (aContent->GetShadowRoot()) {
+ // Children of a shadow root host are distributed
+ // to content insertion points in the shadow root.
+ return true;
+ }
+
+ ShadowRoot* shadow = ShadowRoot::FromNode(aContent);
+ if (shadow) {
+ // Children of a shadow root are distributed to
+ // the shadow insertion point of the younger shadow root.
+ return shadow->GetYoungerShadowRoot();
+ }
+
+ HTMLShadowElement* shadowEl = HTMLShadowElement::FromContent(aContent);
+ if (shadowEl && shadowEl->IsInsertionPoint()) {
+ // Children of a shadow insertion points are distributed
+ // to the insertion points in the older shadow root.
+ return shadowEl->GetOlderShadowRoot();
+ }
+
+ HTMLContentElement* contentEl = HTMLContentElement::FromContent(aContent);
+ if (contentEl && contentEl->IsInsertionPoint()) {
+ // Children of a content insertion point are distributed to the
+ // content insertion point if the content insertion point does
+ // not match any nodes (fallback content).
+ return contentEl->MatchedNodes().IsEmpty();
+ }
+
+ return false;
+}
+
+// static
+bool
+nsContentUtils::IsForbiddenRequestHeader(const nsACString& aHeader)
+{
+ if (IsForbiddenSystemRequestHeader(aHeader)) {
+ return true;
+ }
+
+ return StringBeginsWith(aHeader, NS_LITERAL_CSTRING("proxy-"),
+ nsCaseInsensitiveCStringComparator()) ||
+ StringBeginsWith(aHeader, NS_LITERAL_CSTRING("sec-"),
+ nsCaseInsensitiveCStringComparator());
+}
+
+// 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", "te", "trailer", "transfer-encoding", "upgrade", "via"
+ };
+ for (uint32_t i = 0; i < ArrayLength(kInvalidHeaders); ++i) {
+ if (aHeader.LowerCaseEqualsASCII(kInvalidHeaders[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// static
+bool
+nsContentUtils::IsForbiddenResponseHeader(const nsACString& aHeader)
+{
+ return (aHeader.LowerCaseEqualsASCII("set-cookie") ||
+ aHeader.LowerCaseEqualsASCII("set-cookie2"));
+}
+
+// static
+bool
+nsContentUtils::IsAllowedNonCorsContentType(const nsACString& aHeaderValue)
+{
+ nsAutoCString contentType;
+ nsAutoCString unused;
+
+ 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");
+}
+
+bool
+nsContentUtils::DOMWindowDumpEnabled()
+{
+#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
+ // In optimized builds we check a pref that controls if we should
+ // enable output from dump() or not, in debug builds it's always
+ // enabled.
+ return nsContentUtils::sDOMWindowDumpEnabled;
+#else
+ return true;
+#endif
+}
+
+bool
+nsContentUtils::DoNotTrackEnabled()
+{
+ return nsContentUtils::sDoNotTrackEnabled;
+}
+
+mozilla::LogModule*
+nsContentUtils::DOMDumpLog()
+{
+ return sDOMDumpLog;
+}
+
+bool
+nsContentUtils::GetNodeTextContent(nsINode* aNode, bool aDeep, nsAString& aResult,
+ const fallible_t& aFallible)
+{
+ aResult.Truncate();
+ return AppendNodeTextContent(aNode, aDeep, aResult, aFallible);
+}
+
+void
+nsContentUtils::GetNodeTextContent(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;
+}
+
+nsresult
+nsContentUtils::GenerateUUIDInPlace(nsID& aUUID)
+{
+ MOZ_ASSERT(sUUIDGenerator);
+
+ nsresult rv = sUUIDGenerator->GenerateUUIDInPlace(&aUUID);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsContentUtils::PrefetchEnabled(nsIDocShell* aDocShell)
+{
+ //
+ // SECURITY CHECK: disable prefetching 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 {
+ uint32_t appType = 0;
+ nsresult rv = docshell->GetAppType(&appType);
+ if (NS_FAILED(rv) || appType == nsIDocShell::APP_TYPE_MAIL) {
+ return false; // do not prefetch, preconnect from mailnews
+ }
+
+ docshell->GetParent(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;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = loadGroup->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;
+}
+
+nsresult
+nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI, nsCString& aHost)
+{
+ aHost.Truncate();
+ nsresult rv = aURI->GetHost(aHost);
+ if (NS_FAILED(rv)) { // Some URIs do not have a host
+ return rv;
+ }
+
+ if (aHost.FindChar(':') != -1) { // Escape IPv6 address
+ MOZ_ASSERT(!aHost.Length() ||
+ (aHost[0] !='[' && aHost[aHost.Length() - 1] != ']'));
+ aHost.Insert('[', 0);
+ aHost.Append(']');
+ }
+
+ 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;
+}
+
+bool
+nsContentUtils::CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
+ CallOnRemoteChildFunction aCallback,
+ void* aArg)
+{
+ uint32_t tabChildCount = 0;
+ aManager->GetChildCount(&tabChildCount);
+ for (uint32_t j = 0; j < tabChildCount; ++j) {
+ nsCOMPtr<nsIMessageListenerManager> childMM;
+ aManager->GetChildAt(j, getter_AddRefs(childMM));
+ if (!childMM) {
+ continue;
+ }
+
+ nsCOMPtr<nsIMessageBroadcaster> nonLeafMM = do_QueryInterface(childMM);
+ if (nonLeafMM) {
+ if (CallOnAllRemoteChildren(nonLeafMM, aCallback, aArg)) {
+ return true;
+ }
+ continue;
+ }
+
+ nsCOMPtr<nsIMessageSender> tabMM = do_QueryInterface(childMM);
+
+ mozilla::dom::ipc::MessageManagerCallback* cb =
+ static_cast<nsFrameMessageManager*>(tabMM.get())->GetCallback();
+ if (cb) {
+ nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
+ TabParent* remote = TabParent::GetFrom(fl);
+ if (remote && aCallback) {
+ if (aCallback(remote, aArg)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+nsContentUtils::CallOnAllRemoteChildren(nsPIDOMWindowOuter* aWindow,
+ CallOnRemoteChildFunction aCallback,
+ void* aArg)
+{
+ nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(aWindow));
+ if (chromeWindow) {
+ nsCOMPtr<nsIMessageBroadcaster> windowMM;
+ chromeWindow->GetMessageManager(getter_AddRefs(windowMM));
+ if (windowMM) {
+ CallOnAllRemoteChildren(windowMM, aCallback, aArg);
+ }
+ }
+}
+
+struct UIStateChangeInfo {
+ UIStateChangeType mShowAccelerators;
+ UIStateChangeType mShowFocusRings;
+
+ UIStateChangeInfo(UIStateChangeType aShowAccelerators,
+ UIStateChangeType aShowFocusRings)
+ : mShowAccelerators(aShowAccelerators),
+ mShowFocusRings(aShowFocusRings)
+ {}
+};
+
+bool
+SetKeyboardIndicatorsChild(TabParent* aParent, void* aArg)
+{
+ UIStateChangeInfo* stateInfo = static_cast<UIStateChangeInfo*>(aArg);
+ Unused << aParent->SendSetKeyboardIndicators(stateInfo->mShowAccelerators,
+ stateInfo->mShowFocusRings);
+ return false;
+}
+
+void
+nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(nsPIDOMWindowOuter* aWindow,
+ UIStateChangeType aShowAccelerators,
+ UIStateChangeType aShowFocusRings)
+{
+ UIStateChangeInfo stateInfo(aShowAccelerators, aShowFocusRings);
+ CallOnAllRemoteChildren(aWindow, SetKeyboardIndicatorsChild,
+ (void *)&stateInfo);
+}
+
+nsresult
+nsContentUtils::IPCTransferableToTransferable(const IPCDataTransfer& aDataTransfer,
+ const bool& aIsPrivateData,
+ nsIPrincipal* aRequestingPrincipal,
+ nsITransferable* aTransferable,
+ mozilla::dom::nsIContentParent* aContentParent,
+ mozilla::dom::TabChild* aTabChild)
+{
+ nsresult rv;
+
+ const nsTArray<IPCDataTransferItem>& items = aDataTransfer.items();
+ for (const auto& item : items) {
+ aTransferable->AddDataFlavor(item.flavor().get());
+
+ if (item.data().type() == IPCDataTransferData::TnsString) {
+ nsCOMPtr<nsISupportsString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const nsString& text = item.data().get_nsString();
+ rv = dataWrapper->SetData(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper,
+ text.Length() * sizeof(char16_t));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (item.data().type() == IPCDataTransferData::TShmem) {
+ if (nsContentUtils::IsFlavorImage(item.flavor())) {
+ nsCOMPtr<imgIContainer> imageContainer;
+ rv = nsContentUtils::DataTransferItemToImage(item,
+ getter_AddRefs(imageContainer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsInterfacePointer> imgPtr =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID);
+ NS_ENSURE_TRUE(imgPtr, NS_ERROR_FAILURE);
+
+ rv = imgPtr->SetData(imageContainer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aTransferable->SetTransferData(item.flavor().get(), imgPtr, sizeof(nsISupports*));
+ } else {
+ nsCOMPtr<nsISupportsCString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The buffer contains the terminating null.
+ Shmem itemData = item.data().get_Shmem();
+ const nsDependentCString text(itemData.get<char>(),
+ itemData.Size<char>());
+ rv = dataWrapper->SetData(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aTransferable->SetTransferData(item.flavor().get(), dataWrapper, text.Length());
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aContentParent) {
+ Unused << aContentParent->DeallocShmem(item.data().get_Shmem());
+ } else if (aTabChild) {
+ Unused << aTabChild->DeallocShmem(item.data().get_Shmem());
+ }
+ }
+ }
+
+ aTransferable->SetIsPrivateData(aIsPrivateData);
+ aTransferable->SetRequestingPrincipal(aRequestingPrincipal);
+ return NS_OK;
+}
+
+void
+nsContentUtils::TransferablesToIPCTransferables(nsIArray* aTransferables,
+ nsTArray<IPCDataTransfer>& aIPC,
+ bool aInSyncMessage,
+ mozilla::dom::nsIContentChild* aChild,
+ mozilla::dom::nsIContentParent* aParent)
+{
+ aIPC.Clear();
+ if (aTransferables) {
+ uint32_t transferableCount = 0;
+ aTransferables->GetLength(&transferableCount);
+ for (uint32_t i = 0; i < transferableCount; ++i) {
+ IPCDataTransfer* dt = aIPC.AppendElement();
+ nsCOMPtr<nsITransferable> transferable = do_QueryElementAt(aTransferables, i);
+ TransferableToIPCTransferable(transferable, dt, aInSyncMessage, aChild, aParent);
+ }
+ }
+}
+
+nsresult
+nsContentUtils::SlurpFileToString(nsIFile* aFile, nsACString& aString)
+{
+ aString.Truncate();
+
+ nsCOMPtr<nsIURI> fileURI;
+ nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), aFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ fileURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open2(getter_AddRefs(stream));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_ConsumeStream(stream, UINT32_MAX, aString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Close();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsContentUtils::IsFileImage(nsIFile* aFile, nsACString& aType)
+{
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
+ if (!mime) {
+ return false;
+ }
+
+ nsresult rv = mime->GetTypeFromFile(aFile, aType);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return StringBeginsWith(aType, NS_LITERAL_CSTRING("image/"));
+}
+
+nsresult
+nsContentUtils::DataTransferItemToImage(const IPCDataTransferItem& aItem,
+ imgIContainer** aContainer)
+{
+ MOZ_ASSERT(aItem.data().type() == IPCDataTransferData::TShmem);
+ MOZ_ASSERT(IsFlavorImage(aItem.flavor()));
+
+ const IPCDataTransferImage& imageDetails = aItem.imageDetails();
+ const IntSize size(imageDetails.width(), imageDetails.height());
+ if (!size.width || !size.height) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Shmem data = aItem.data().get_Shmem();
+
+ RefPtr<DataSourceSurface> image =
+ CreateDataSourceSurfaceFromData(size,
+ static_cast<SurfaceFormat>(imageDetails.format()),
+ data.get<uint8_t>(),
+ imageDetails.stride());
+
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, 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);
+}
+
+static Shmem
+ConvertToShmem(mozilla::dom::nsIContentChild* aChild,
+ mozilla::dom::nsIContentParent* aParent,
+ const nsACString& aInput)
+{
+ MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
+
+ IShmemAllocator* allocator =
+ aChild ? static_cast<IShmemAllocator*>(aChild)
+ : static_cast<IShmemAllocator*>(aParent);
+
+ Shmem result;
+ if (!allocator->AllocShmem(aInput.Length() + 1,
+ SharedMemory::TYPE_BASIC,
+ &result)) {
+ return result;
+ }
+
+ memcpy(result.get<char>(),
+ aInput.BeginReading(),
+ aInput.Length() + 1);
+
+ return result;
+}
+
+void
+nsContentUtils::TransferableToIPCTransferable(nsITransferable* aTransferable,
+ IPCDataTransfer* aIPCDataTransfer,
+ bool aInSyncMessage,
+ mozilla::dom::nsIContentChild* aChild,
+ mozilla::dom::nsIContentParent* aParent)
+{
+ MOZ_ASSERT((aChild && !aParent) || (!aChild && aParent));
+
+ if (aTransferable) {
+ nsCOMPtr<nsIArray> flavorList;
+ aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t flavorCount = 0;
+ flavorList->GetLength(&flavorCount);
+ for (uint32_t j = 0; j < flavorCount; ++j) {
+ nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavorList, j);
+ if (!flavor) {
+ continue;
+ }
+
+ nsAutoCString flavorStr;
+ flavor->GetData(flavorStr);
+ if (!flavorStr.Length()) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> data;
+ uint32_t dataLen = 0;
+ aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(data), &dataLen);
+
+ nsCOMPtr<nsISupportsString> text = do_QueryInterface(data);
+ nsCOMPtr<nsISupportsCString> ctext = do_QueryInterface(data);
+ if (text) {
+ nsAutoString dataAsString;
+ text->GetData(dataAsString);
+ IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = dataAsString;
+ } else if (ctext) {
+ nsAutoCString dataAsString;
+ ctext->GetData(dataAsString);
+ IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+ item->flavor() = flavorStr;
+
+ Shmem dataAsShmem = ConvertToShmem(aChild, aParent, dataAsString);
+ if (!dataAsShmem.IsReadable() || !dataAsShmem.Size<char>()) {
+ continue;
+ }
+
+ item->data() = dataAsShmem;
+ } else {
+ nsCOMPtr<nsISupportsInterfacePointer> sip =
+ do_QueryInterface(data);
+ if (sip) {
+ sip->GetData(getter_AddRefs(data));
+ }
+
+ // Images to be pasted on the clipboard are nsIInputStreams
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(data));
+ if (stream) {
+ IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+ item->flavor() = flavorStr;
+
+ nsCString imageData;
+ NS_ConsumeStream(stream, UINT32_MAX, imageData);
+
+ Shmem imageDataShmem = ConvertToShmem(aChild, aParent, imageData);
+ if (!imageDataShmem.IsReadable() || !imageDataShmem.Size<char>()) {
+ continue;
+ }
+
+ item->data() = imageDataShmem;
+ continue;
+ }
+
+ // Images to be placed on the clipboard are imgIContainers.
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(data));
+ if (image) {
+ RefPtr<mozilla::gfx::SourceSurface> surface =
+ image->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ continue;
+ }
+ RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+ surface->GetDataSurface();
+ if (!dataSurface) {
+ continue;
+ }
+ size_t length;
+ int32_t stride;
+ Shmem surfaceData;
+ IShmemAllocator* allocator = aChild ? static_cast<IShmemAllocator*>(aChild)
+ : static_cast<IShmemAllocator*>(aParent);
+ GetSurfaceData(dataSurface, &length, &stride,
+ allocator,
+ &surfaceData);
+
+ IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+ item->flavor() = flavorStr;
+ // Turn item->data() into an nsCString prior to accessing it.
+ item->data() = surfaceData;
+
+ IPCDataTransferImage& imageDetails = item->imageDetails();
+ mozilla::gfx::IntSize size = dataSurface->GetSize();
+ imageDetails.width() = size.width;
+ imageDetails.height() = size.height;
+ imageDetails.stride() = stride;
+ imageDetails.format() = static_cast<uint8_t>(dataSurface->GetFormat());
+
+ continue;
+ }
+
+ // Otherwise, handle this as a file.
+ nsCOMPtr<BlobImpl> blobImpl;
+ nsCOMPtr<nsIFile> file = do_QueryInterface(data);
+ if (file) {
+ // If we can send this over as a blob, do so. Otherwise, we're
+ // responding to a sync message and the child can't process the blob
+ // constructor before processing our response, which would crash. In
+ // that case, hope that the caller is nsClipboardProxy::GetData,
+ // called from editor and send over images as raw data.
+ if (aInSyncMessage) {
+ nsAutoCString type;
+ if (IsFileImage(file, type)) {
+ IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+ item->flavor() = type;
+ nsAutoCString data;
+ SlurpFileToString(file, data);
+
+ Shmem dataAsShmem = ConvertToShmem(aChild, aParent, data);
+ item->data() = dataAsShmem;
+ }
+
+ continue;
+ }
+
+ 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 BlobImplFile(file, false);
+ ErrorResult rv;
+ // Ensure that file data is cached no that the content process
+ // has this data available to it when passed over:
+ blobImpl->GetSize(rv);
+ blobImpl->GetLastModified(rv);
+ } else {
+ if (aInSyncMessage) {
+ // Can't do anything.
+ continue;
+ }
+ blobImpl = do_QueryInterface(data);
+ }
+ if (blobImpl) {
+ IPCDataTransferData data;
+
+ // 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.
+ if (aChild) {
+ auto* child = mozilla::dom::BlobChild::GetOrCreate(aChild,
+ static_cast<BlobImpl*>(blobImpl.get()));
+ if (!child) {
+ continue;
+ }
+
+ data = child;
+ } else if (aParent) {
+ auto* parent = mozilla::dom::BlobParent::GetOrCreate(aParent,
+ static_cast<BlobImpl*>(blobImpl.get()));
+ if (!parent) {
+ continue;
+ }
+
+ data = parent;
+ }
+
+ IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = data;
+ } else {
+ // 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)) {
+ IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = NS_ConvertUTF8toUTF16(flavorStr);
+ } else if (!data) {
+ // Empty element, transfer only the flavor
+ IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+ item->flavor() = flavorStr;
+ item->data() = nsString();
+ continue;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+namespace {
+// The default type used for calling GetSurfaceData(). Gets surface data as
+// raw buffer.
+struct GetSurfaceDataRawBuffer
+{
+ using ReturnType = mozilla::UniquePtr<char[]>;
+ using BufferType = char*;
+
+ ReturnType Allocate(size_t aSize)
+ {
+ return ReturnType(new char[aSize]);
+ }
+
+ static BufferType
+ GetBuffer(const ReturnType& aReturnValue)
+ {
+ return aReturnValue.get();
+ }
+
+ static ReturnType
+ NullValue()
+ {
+ return ReturnType();
+ }
+};
+
+// The type used for calling GetSurfaceData() that allocates and writes to
+// a shared memory buffer.
+struct GetSurfaceDataShmem
+{
+ using ReturnType = Shmem;
+ using BufferType = char*;
+
+ explicit GetSurfaceDataShmem(IShmemAllocator* aAllocator)
+ : mAllocator(aAllocator)
+ { }
+
+ ReturnType Allocate(size_t aSize)
+ {
+ Shmem returnValue;
+ mAllocator->AllocShmem(aSize,
+ SharedMemory::TYPE_BASIC,
+ &returnValue);
+ return returnValue;
+ }
+
+ static BufferType
+ GetBuffer(ReturnType aReturnValue)
+ {
+ return aReturnValue.get<char>();
+ }
+
+ static ReturnType
+ NullValue()
+ {
+ return ReturnType();
+ }
+private:
+ IShmemAllocator* mAllocator;
+};
+
+/*
+ * Get the pixel data from the given source surface and return it as a buffer.
+ * The length and stride will be assigned from the surface.
+ */
+template <typename GetSurfaceDataContext = GetSurfaceDataRawBuffer>
+typename GetSurfaceDataContext::ReturnType
+GetSurfaceDataImpl(mozilla::gfx::DataSourceSurface* aSurface,
+ size_t* aLength, int32_t* aStride,
+ GetSurfaceDataContext aContext = GetSurfaceDataContext())
+{
+ mozilla::gfx::DataSourceSurface::MappedSurface map;
+ if (!aSurface->Map(mozilla::gfx::DataSourceSurface::MapType::READ, &map)) {
+ return GetSurfaceDataContext::NullValue();
+ }
+
+ mozilla::gfx::IntSize size = aSurface->GetSize();
+ mozilla::CheckedInt32 requiredBytes =
+ mozilla::CheckedInt32(map.mStride) * mozilla::CheckedInt32(size.height);
+ if (!requiredBytes.isValid()) {
+ return GetSurfaceDataContext::NullValue();
+ }
+
+ size_t maxBufLen = requiredBytes.value();
+ mozilla::gfx::SurfaceFormat format = aSurface->GetFormat();
+
+ // Surface data handling is totally nuts. This is the magic one needs to
+ // know to access the data.
+ size_t bufLen = maxBufLen - map.mStride + (size.width * BytesPerPixel(format));
+
+ // nsDependentCString wants null-terminated string.
+ typename GetSurfaceDataContext::ReturnType surfaceData = aContext.Allocate(maxBufLen + 1);
+ if (GetSurfaceDataContext::GetBuffer(surfaceData)) {
+ memcpy(GetSurfaceDataContext::GetBuffer(surfaceData),
+ reinterpret_cast<char*>(map.mData),
+ bufLen);
+ memset(GetSurfaceDataContext::GetBuffer(surfaceData) + bufLen,
+ 0,
+ maxBufLen - bufLen + 1);
+ }
+
+ *aLength = maxBufLen;
+ *aStride = map.mStride;
+
+ aSurface->Unmap();
+ return surfaceData;
+}
+} // Anonymous namespace.
+
+mozilla::UniquePtr<char[]>
+nsContentUtils::GetSurfaceData(
+ NotNull<mozilla::gfx::DataSourceSurface*> aSurface,
+ size_t* aLength, int32_t* aStride)
+{
+ return GetSurfaceDataImpl(aSurface, aLength, aStride);
+}
+
+void
+nsContentUtils::GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
+ size_t* aLength, int32_t* aStride,
+ IShmemAllocator* aAllocator,
+ Shmem *aOutShmem)
+{
+ *aOutShmem = GetSurfaceDataImpl(aSurface, aLength, aStride,
+ GetSurfaceDataShmem(aAllocator));
+}
+
+mozilla::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(nsIPresShell* aPresShell, nsPoint* aOffset) {
+ if (aPresShell) {
+ nsIFrame* frame = aPresShell->GetRootFrame();
+ if (frame)
+ return frame->GetView()->GetNearestWidget(aOffset);
+ }
+ return nullptr;
+}
+
+int16_t
+nsContentUtils::GetButtonsFlagForButton(int32_t aButton)
+{
+ switch (aButton) {
+ case -1:
+ return WidgetMouseEvent::eNoButtonFlag;
+ case WidgetMouseEvent::eLeftButton:
+ return WidgetMouseEvent::eLeftButtonFlag;
+ case WidgetMouseEvent::eMiddleButton:
+ return WidgetMouseEvent::eMiddleButtonFlag;
+ case WidgetMouseEvent::eRightButton:
+ return WidgetMouseEvent::eRightButtonFlag;
+ case 4:
+ return WidgetMouseEvent::e4thButtonFlag;
+ case 5:
+ return WidgetMouseEvent::e5thButtonFlag;
+ default:
+ NS_ERROR("Button not known.");
+ return 0;
+ }
+}
+
+LayoutDeviceIntPoint
+nsContentUtils::ToWidgetPoint(const CSSPoint& aPoint,
+ const nsPoint& aOffset,
+ nsPresContext* aPresContext)
+{
+ return LayoutDeviceIntPoint::FromAppUnitsRounded(
+ (CSSPoint::ToAppUnits(aPoint) +
+ aOffset).ApplyResolution(nsLayoutUtils::GetCurrentAPZResolutionScale(aPresContext->PresShell())),
+ aPresContext->AppUnitsPerDevPixel());
+}
+
+nsView*
+nsContentUtils::GetViewToDispatchEvent(nsPresContext* presContext,
+ nsIPresShell** presShell)
+{
+ if (presContext && presShell) {
+ *presShell = presContext->PresShell();
+ if (*presShell) {
+ NS_ADDREF(*presShell);
+ if (nsViewManager* viewManager = (*presShell)->GetViewManager()) {
+ if (nsView* view = viewManager->GetRootView()) {
+ return view;
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsresult
+nsContentUtils::SendKeyEvent(nsIWidget* aWidget,
+ const nsAString& aType,
+ int32_t aKeyCode,
+ int32_t aCharCode,
+ int32_t aModifiers,
+ uint32_t aAdditionalFlags,
+ bool* aDefaultActionTaken)
+{
+ // get the widget to send the event to
+ if (!aWidget)
+ return NS_ERROR_FAILURE;
+
+ EventMessage msg;
+ if (aType.EqualsLiteral("keydown"))
+ msg = eKeyDown;
+ else if (aType.EqualsLiteral("keyup"))
+ msg = eKeyUp;
+ else if (aType.EqualsLiteral("keypress"))
+ msg = eKeyPress;
+ else
+ return NS_ERROR_FAILURE;
+
+ WidgetKeyboardEvent event(true, msg, aWidget);
+ event.mModifiers = GetWidgetModifiers(aModifiers);
+
+ if (msg == eKeyPress) {
+ event.mKeyCode = aCharCode ? 0 : aKeyCode;
+ event.mCharCode = aCharCode;
+ } else {
+ event.mKeyCode = aKeyCode;
+ event.mCharCode = 0;
+ }
+
+ uint32_t locationFlag = (aAdditionalFlags &
+ (nsIDOMWindowUtils::KEY_FLAG_LOCATION_STANDARD | nsIDOMWindowUtils::KEY_FLAG_LOCATION_LEFT |
+ nsIDOMWindowUtils::KEY_FLAG_LOCATION_RIGHT | nsIDOMWindowUtils::KEY_FLAG_LOCATION_NUMPAD));
+ switch (locationFlag) {
+ case nsIDOMWindowUtils::KEY_FLAG_LOCATION_STANDARD:
+ event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ break;
+ case nsIDOMWindowUtils::KEY_FLAG_LOCATION_LEFT:
+ event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+ break;
+ case nsIDOMWindowUtils::KEY_FLAG_LOCATION_RIGHT:
+ event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+ break;
+ case nsIDOMWindowUtils::KEY_FLAG_LOCATION_NUMPAD:
+ event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+ break;
+ default:
+ if (locationFlag != 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ // If location flag isn't set, choose the location from keycode.
+ switch (aKeyCode) {
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD0:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD1:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD2:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD3:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD4:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD5:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD6:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD7:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD8:
+ case nsIDOMKeyEvent::DOM_VK_NUMPAD9:
+ case nsIDOMKeyEvent::DOM_VK_MULTIPLY:
+ case nsIDOMKeyEvent::DOM_VK_ADD:
+ case nsIDOMKeyEvent::DOM_VK_SEPARATOR:
+ case nsIDOMKeyEvent::DOM_VK_SUBTRACT:
+ case nsIDOMKeyEvent::DOM_VK_DECIMAL:
+ case nsIDOMKeyEvent::DOM_VK_DIVIDE:
+ event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_SHIFT:
+ case nsIDOMKeyEvent::DOM_VK_CONTROL:
+ case nsIDOMKeyEvent::DOM_VK_ALT:
+ case nsIDOMKeyEvent::DOM_VK_META:
+ event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+ break;
+ default:
+ event.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ break;
+ }
+ break;
+ }
+
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ event.mTime = PR_IntervalNow();
+ if (!(aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_NOT_SYNTHESIZED_FOR_TESTS)) {
+ event.mFlags.mIsSynthesizedForTests = true;
+ }
+
+ if (aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_PREVENT_DEFAULT) {
+ event.PreventDefaultBeforeDispatch();
+ }
+
+ nsEventStatus status;
+ nsresult rv = aWidget->DispatchEvent(&event, status);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aDefaultActionTaken = (status != nsEventStatus_eConsumeNoDefault);
+
+ return NS_OK;
+}
+
+nsresult
+nsContentUtils::SendMouseEvent(nsCOMPtr<nsIPresShell> 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,
+ bool aToWindow,
+ bool *aPreventDefault,
+ bool aIsDOMEventSynthesized,
+ bool aIsWidgetEventSynthesized)
+{
+ nsPoint offset;
+ nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
+ if (!widget)
+ return NS_ERROR_FAILURE;
+
+ EventMessage msg;
+ WidgetMouseEvent::ExitFrom exitFrom = WidgetMouseEvent::eChild;
+ 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;
+ } else if (aType.EqualsLiteral("mousecancel")) {
+ msg = eMouseExitFromWidget;
+ exitFrom = WidgetMouseEvent::eTopLevel;
+ } 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 {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
+ aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
+ }
+
+ WidgetMouseEvent event(true, msg, widget,
+ aIsWidgetEventSynthesized ?
+ WidgetMouseEvent::eSynthesized :
+ WidgetMouseEvent::eReal,
+ contextMenuKey ? WidgetMouseEvent::eContextMenuKey :
+ WidgetMouseEvent::eNormal);
+ event.mModifiers = GetWidgetModifiers(aModifiers);
+ event.button = aButton;
+ event.buttons = aButtons != nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED ?
+ aButtons :
+ msg == eMouseUp ? 0 : GetButtonsFlagForButton(aButton);
+ event.pressure = aPressure;
+ event.inputSource = aInputSourceArg;
+ event.mClickCount = aClickCount;
+ event.mTime = PR_IntervalNow();
+ 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) {
+ nsCOMPtr<nsIPresShell> presShell;
+ nsView* view = GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
+ if (!presShell || !view) {
+ return NS_ERROR_FAILURE;
+ }
+ return presShell->HandleEvent(view->GetFrame(), &event, false, &status);
+ }
+ if (gfxPrefs::TestEventsAsyncEnabled()) {
+ status = widget->DispatchInputEvent(&event);
+ } else {
+ nsresult rv = widget->DispatchEvent(&event, status);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (aPreventDefault) {
+ *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void
+nsContentUtils::FirePageHideEvent(nsIDocShellTreeItem* aItem,
+ EventTarget* aChromeEventHandler)
+{
+ nsCOMPtr<nsIDocument> doc = aItem->GetDocument();
+ NS_ASSERTION(doc, "What happened here?");
+ doc->OnPageHide(true, aChromeEventHandler);
+
+ int32_t childCount = 0;
+ aItem->GetChildCount(&childCount);
+ AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids;
+ kids.AppendElements(childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ aItem->GetChildAt(i, getter_AddRefs(kids[i]));
+ }
+
+ for (uint32_t i = 0; i < kids.Length(); ++i) {
+ if (kids[i]) {
+ FirePageHideEvent(kids[i], aChromeEventHandler);
+ }
+ }
+}
+
+// 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::FirePageShowEvent(nsIDocShellTreeItem* aItem,
+ EventTarget* aChromeEventHandler,
+ bool aFireIfShowing)
+{
+ int32_t childCount = 0;
+ aItem->GetChildCount(&childCount);
+ AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids;
+ kids.AppendElements(childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ aItem->GetChildAt(i, getter_AddRefs(kids[i]));
+ }
+
+ for (uint32_t i = 0; i < kids.Length(); ++i) {
+ if (kids[i]) {
+ FirePageShowEvent(kids[i], aChromeEventHandler, aFireIfShowing);
+ }
+ }
+
+ nsCOMPtr<nsIDocument> doc = aItem->GetDocument();
+ NS_ASSERTION(doc, "What happened here?");
+ if (doc->IsShowing() == aFireIfShowing) {
+ doc->OnPageShow(true, aChromeEventHandler);
+ }
+}
+
+/* static */
+already_AddRefed<nsPIWindowRoot>
+nsContentUtils::GetWindowRoot(nsIDocument* aDoc)
+{
+ if (aDoc) {
+ if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
+ return win->GetTopWindowRoot();
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+nsContentPolicyType
+nsContentUtils::InternalContentPolicyTypeToExternal(nsContentPolicyType aType)
+{
+ switch (aType) {
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+ return nsIContentPolicy::TYPE_SCRIPT;
+
+ case nsIContentPolicy::TYPE_INTERNAL_EMBED:
+ case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
+ return nsIContentPolicy::TYPE_OBJECT;
+
+ case nsIContentPolicy::TYPE_INTERNAL_FRAME:
+ case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
+ return nsIContentPolicy::TYPE_SUBDOCUMENT;
+
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
+ case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
+ case nsIContentPolicy::TYPE_INTERNAL_TRACK:
+ return nsIContentPolicy::TYPE_MEDIA;
+
+ case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
+ case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
+ return nsIContentPolicy::TYPE_XMLHTTPREQUEST;
+
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
+ return nsIContentPolicy::TYPE_IMAGE;
+
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
+ case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
+ return nsIContentPolicy::TYPE_STYLESHEET;
+
+ default:
+ return aType;
+ }
+}
+
+/* static */
+nsContentPolicyType
+nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(nsContentPolicyType aType)
+{
+ switch (aType) {
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+ return aType;
+
+ default:
+ return InternalContentPolicyTypeToExternal(aType);
+ }
+}
+
+/* static */
+bool
+nsContentUtils::IsPreloadType(nsContentPolicyType aType)
+{
+ if (aType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD ||
+ aType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD) {
+ return true;
+ }
+ return false;
+}
+
+nsresult
+nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
+ nsIDocument* aDoc,
+ nsIHttpChannel* aChannel,
+ mozilla::net::ReferrerPolicy aReferrerPolicy)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aChannel);
+
+ nsCOMPtr<nsIURI> principalURI;
+
+ if (IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ aPrincipal->GetURI(getter_AddRefs(principalURI));
+
+ if (!aDoc) {
+ return aChannel->SetReferrerWithPolicy(principalURI, aReferrerPolicy);
+ }
+
+ // If it weren't for history.push/replaceState, we could just use the
+ // principal's URI here. But since we want changes to the URI effected
+ // by push/replaceState to be reflected in the XHR referrer, we have to
+ // be more clever.
+ //
+ // If the document's original URI (before any push/replaceStates) matches
+ // our principal, then we use the document's current URI (after
+ // push/replaceStates). Otherwise (if the document is, say, a data:
+ // URI), we just use the principal's URI.
+ nsCOMPtr<nsIURI> docCurURI = aDoc->GetDocumentURI();
+ nsCOMPtr<nsIURI> docOrigURI = aDoc->GetOriginalURI();
+
+ nsCOMPtr<nsIURI> referrerURI;
+
+ if (principalURI && docCurURI && docOrigURI) {
+ bool equal = false;
+ principalURI->Equals(docOrigURI, &equal);
+ if (equal) {
+ referrerURI = docCurURI;
+ }
+ }
+
+ if (!referrerURI) {
+ referrerURI = principalURI;
+ }
+
+ net::ReferrerPolicy referrerPolicy = aReferrerPolicy;
+ if (referrerPolicy == net::RP_Default) {
+ referrerPolicy = aDoc->GetReferrerPolicy();
+ }
+ return aChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
+}
+
+// static
+net::ReferrerPolicy
+nsContentUtils::GetReferrerPolicyFromHeader(const nsAString& aHeader)
+{
+ // Multiple headers could be concatenated into one comma-separated
+ // list of policies. Need to tokenize the multiple headers.
+ nsCharSeparatedTokenizer tokenizer(aHeader, ',');
+ nsAutoString token;
+ net::ReferrerPolicy referrerPolicy = mozilla::net::RP_Unset;
+ while (tokenizer.hasMoreTokens()) {
+ token = tokenizer.nextToken();
+ net::ReferrerPolicy policy = net::ReferrerPolicyFromString(token);
+ if (policy != net::RP_Unset) {
+ referrerPolicy = policy;
+ }
+ }
+ return referrerPolicy;
+}
+
+// static
+bool
+nsContentUtils::PushEnabled(JSContext* aCx, JSObject* aObj)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.push.enabled", false);
+ }
+
+ using namespace workers;
+
+ // Otherwise, check the pref via the WorkerPrivate
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ if (!workerPrivate) {
+ return false;
+ }
+
+ return workerPrivate->PushEnabled();
+}
+
+// static
+bool
+nsContentUtils::IsNonSubresourceRequest(nsIChannel* aChannel)
+{
+ nsLoadFlags loadFlags = 0;
+ aChannel->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (!loadInfo) {
+ return false;
+ }
+ nsContentPolicyType type = loadInfo->InternalContentPolicyType();
+ return type == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ type == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER;
+}
+
+// static, public
+nsContentUtils::StorageAccess
+nsContentUtils::StorageAllowedForWindow(nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+
+ if (nsIDocument* document = aWindow->GetExtantDoc()) {
+ nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal();
+ return InternalStorageAllowedForPrincipal(principal, aWindow);
+ }
+
+ return StorageAccess::eDeny;
+}
+
+// static, public
+nsContentUtils::StorageAccess
+nsContentUtils::StorageAllowedForPrincipal(nsIPrincipal* aPrincipal)
+{
+ return InternalStorageAllowedForPrincipal(aPrincipal, nullptr);
+}
+
+// static, private
+nsContentUtils::StorageAccess
+nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
+ nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(!aWindow || aWindow->IsInnerWindow());
+
+ StorageAccess access = StorageAccess::eAllow;
+
+ // We don't allow storage on the null principal, in general. Even if the
+ // calling context is chrome.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return StorageAccess::eDeny;
+ }
+
+ if (aWindow) {
+ // If the document is sandboxed, then it is not permitted to use storage
+ nsIDocument* document = aWindow->GetExtantDoc();
+ if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
+ return StorageAccess::eDeny;
+ }
+
+ // Check if we are in private browsing, and record that fact
+ if (IsInPrivateBrowsing(document)) {
+ access = StorageAccess::ePrivateBrowsing;
+ }
+ }
+
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ services::GetPermissionManager();
+ if (!permissionManager) {
+ return StorageAccess::eDeny;
+ }
+
+ // check the permission manager for any allow or deny permissions
+ // for cookies for the window.
+ uint32_t perm;
+ permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm);
+ if (perm == nsIPermissionManager::DENY_ACTION) {
+ return StorageAccess::eDeny;
+ } else if (perm == nsICookiePermission::ACCESS_SESSION) {
+ return std::min(access, StorageAccess::eSessionScoped);
+ } else if (perm == nsIPermissionManager::ALLOW_ACTION) {
+ return access;
+ }
+
+ // Check if we should only allow storage for the session, and record that fact
+ if (sCookiesLifetimePolicy == nsICookieService::ACCEPT_SESSION) {
+ // Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow
+ // so perform a std::min comparison to make sure we preserve ePrivateBrowsing
+ // if it has been set.
+ access = std::min(StorageAccess::eSessionScoped, access);
+ }
+
+ // About URIs are allowed to access storage, even if they don't have chrome
+ // privileges. If this is not desired, than the consumer will have to
+ // implement their own restriction functionality.
+ //
+ // This is due to backwards-compatibility and the state of storage access before
+ // the introducton of nsContentUtils::InternalStorageAllowedForPrincipal:
+ //
+ // BEFORE:
+ // localStorage, caches: allowed in 3rd-party iframes always
+ // IndexedDB: allowed in 3rd-party iframes only if 3rd party URI is an about:
+ // URI within a specific whitelist
+ //
+ // AFTER:
+ // localStorage, caches: allowed in 3rd-party iframes by default. Preference
+ // can be set to disable in 3rd-party, which will not disallow in about: URIs.
+ // IndexedDB: allowed in 3rd-party iframes by default. Preference can be set to
+ // disable in 3rd-party, which will disallow in about: URIs, unless they are
+ // within a specific whitelist.
+ //
+ // This means that behavior for storage with internal about: URIs should not be
+ // affected, which is desireable due to the lack of automated testing for about:
+ // URIs with these preferences set, and the importance of the correct functioning
+ // of these URIs even with custom preferences.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv) && uri) {
+ bool isAbout = false;
+ MOZ_ALWAYS_SUCCEEDS(uri->SchemeIs("about", &isAbout));
+ if (isAbout) {
+ return access;
+ }
+ }
+
+ // We don't want to prompt for every attempt to access permissions.
+ if (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT) {
+ return StorageAccess::eDeny;
+ }
+
+ // In the absense of a window, we assume that we are first-party.
+ if (aWindow && (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
+ sCookiesBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN)) {
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID);
+ MOZ_ASSERT(thirdPartyUtil);
+
+ bool thirdPartyWindow = false;
+ if (NS_SUCCEEDED(thirdPartyUtil->IsThirdPartyWindow(
+ aWindow->GetOuterWindow(), nullptr, &thirdPartyWindow)) && thirdPartyWindow) {
+ // XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by
+ // simply rejecting the request to use the storage. In the future, if we
+ // change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense
+ // for non-cookie storage types, this may change.
+
+ return StorageAccess::eDeny;
+ }
+ }
+
+ return access;
+}
+
+namespace {
+
+// We put StringBuilder in the anonymous namespace to prevent anything outside
+// this file from accidentally being linked against it.
+
+class StringBuilder
+{
+private:
+ // Try to keep the size of StringBuilder close to a jemalloc bucket size.
+ static const uint32_t STRING_BUFFER_UNITS = 1020;
+ class Unit
+ {
+ public:
+ Unit() : mAtom(nullptr), mType(eUnknown), mLength(0)
+ {
+ MOZ_COUNT_CTOR(StringBuilder::Unit);
+ }
+ ~Unit()
+ {
+ if (mType == eString || mType == eStringWithEncode) {
+ delete mString;
+ }
+ MOZ_COUNT_DTOR(StringBuilder::Unit);
+ }
+
+ enum Type
+ {
+ eUnknown,
+ eAtom,
+ eString,
+ eStringWithEncode,
+ eLiteral,
+ eTextFragment,
+ eTextFragmentWithEncode,
+ };
+
+ union
+ {
+ nsIAtom* mAtom;
+ const char* mLiteral;
+ nsAutoString* mString;
+ const nsTextFragment* mTextFragment;
+ };
+ Type mType;
+ uint32_t mLength;
+ };
+public:
+ StringBuilder() : mLast(this), mLength(0)
+ {
+ MOZ_COUNT_CTOR(StringBuilder);
+ }
+
+ ~StringBuilder()
+ {
+ MOZ_COUNT_DTOR(StringBuilder);
+ }
+
+ void Append(nsIAtom* aAtom)
+ {
+ Unit* u = AddUnit();
+ u->mAtom = aAtom;
+ u->mType = Unit::eAtom;
+ uint32_t len = aAtom->GetLength();
+ u->mLength = len;
+ mLength += len;
+ }
+
+ template<int N>
+ void Append(const char (&aLiteral)[N])
+ {
+ Unit* u = AddUnit();
+ u->mLiteral = aLiteral;
+ u->mType = Unit::eLiteral;
+ uint32_t len = N - 1;
+ u->mLength = len;
+ mLength += len;
+ }
+
+ template<int N>
+ void Append(char (&aLiteral)[N])
+ {
+ Unit* u = AddUnit();
+ u->mLiteral = aLiteral;
+ u->mType = Unit::eLiteral;
+ uint32_t len = N - 1;
+ u->mLength = len;
+ mLength += len;
+ }
+
+ void Append(const nsAString& aString)
+ {
+ Unit* u = AddUnit();
+ u->mString = new nsAutoString(aString);
+ u->mType = Unit::eString;
+ uint32_t len = aString.Length();
+ u->mLength = len;
+ mLength += len;
+ }
+
+ void Append(nsAutoString* aString)
+ {
+ Unit* u = AddUnit();
+ u->mString = aString;
+ u->mType = Unit::eString;
+ uint32_t len = aString->Length();
+ u->mLength = len;
+ mLength += len;
+ }
+
+ void AppendWithAttrEncode(nsAutoString* aString, uint32_t aLen)
+ {
+ Unit* u = AddUnit();
+ u->mString = aString;
+ u->mType = Unit::eStringWithEncode;
+ u->mLength = aLen;
+ mLength += aLen;
+ }
+
+ void Append(const nsTextFragment* aTextFragment)
+ {
+ Unit* u = AddUnit();
+ u->mTextFragment = aTextFragment;
+ u->mType = Unit::eTextFragment;
+ 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::eTextFragmentWithEncode;
+ u->mLength = aLen;
+ mLength += aLen;
+ }
+
+ bool ToString(nsAString& aOut)
+ {
+ if (!aOut.SetCapacity(mLength, fallible)) {
+ return false;
+ }
+
+ for (StringBuilder* current = this; current; current = current->mNext) {
+ uint32_t len = current->mUnits.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ Unit& u = current->mUnits[i];
+ switch (u.mType) {
+ case Unit::eAtom:
+ aOut.Append(nsDependentAtomString(u.mAtom));
+ break;
+ case Unit::eString:
+ aOut.Append(*(u.mString));
+ break;
+ case Unit::eStringWithEncode:
+ EncodeAttrString(*(u.mString), aOut);
+ break;
+ case Unit::eLiteral:
+ aOut.AppendASCII(u.mLiteral, u.mLength);
+ break;
+ case Unit::eTextFragment:
+ u.mTextFragment->AppendTo(aOut);
+ break;
+ case Unit::eTextFragmentWithEncode:
+ EncodeTextFragment(u.mTextFragment, aOut);
+ break;
+ default:
+ MOZ_CRASH("Unknown unit type?");
+ }
+ }
+ }
+ 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 = this;
+ aFirst->mLast = this;
+ }
+
+ void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut)
+ {
+ const char16_t* c = aValue.BeginReading();
+ const char16_t* end = aValue.EndReading();
+ while (c < end) {
+ switch (*c) {
+ case '"':
+ aOut.AppendLiteral("&quot;");
+ break;
+ case '&':
+ aOut.AppendLiteral("&amp;");
+ break;
+ case 0x00A0:
+ aOut.AppendLiteral("&nbsp;");
+ break;
+ default:
+ aOut.Append(*c);
+ break;
+ }
+ ++c;
+ }
+ }
+
+ void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut)
+ {
+ uint32_t len = aValue->GetLength();
+ if (aValue->Is2b()) {
+ const char16_t* data = aValue->Get2b();
+ for (uint32_t i = 0; i < len; ++i) {
+ const char16_t c = data[i];
+ switch (c) {
+ case '<':
+ aOut.AppendLiteral("&lt;");
+ break;
+ case '>':
+ aOut.AppendLiteral("&gt;");
+ break;
+ case '&':
+ aOut.AppendLiteral("&amp;");
+ break;
+ case 0x00A0:
+ aOut.AppendLiteral("&nbsp;");
+ break;
+ default:
+ aOut.Append(c);
+ break;
+ }
+ }
+ } else {
+ const char* data = aValue->Get1b();
+ for (uint32_t i = 0; i < len; ++i) {
+ const unsigned char c = data[i];
+ switch (c) {
+ case '<':
+ aOut.AppendLiteral("&lt;");
+ break;
+ case '>':
+ aOut.AppendLiteral("&gt;");
+ break;
+ case '&':
+ aOut.AppendLiteral("&amp;");
+ break;
+ case 0x00A0:
+ aOut.AppendLiteral("&nbsp;");
+ break;
+ default:
+ aOut.Append(c);
+ break;
+ }
+ }
+ }
+ }
+
+ AutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
+ nsAutoPtr<StringBuilder> mNext;
+ StringBuilder* mLast;
+ // mLength is used only in the first StringBuilder object in the linked list.
+ uint32_t mLength;
+};
+
+} // 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 void
+AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder)
+{
+ 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;
+ }
+
+ if (extraSpaceNeeded) {
+ aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded);
+ } else {
+ aBuilder.Append(aValue);
+ }
+}
+
+static void
+StartElement(Element* aContent, StringBuilder& aBuilder)
+{
+ nsIAtom* localName = aContent->NodeInfo()->NameAtom();
+ int32_t tagNS = aContent->GetNameSpaceID();
+
+ aBuilder.Append("<");
+ if (aContent->IsHTMLElement() || aContent->IsSVGElement() ||
+ aContent->IsMathMLElement()) {
+ aBuilder.Append(localName);
+ } else {
+ aBuilder.Append(aContent->NodeName());
+ }
+
+ int32_t count = aContent->GetAttrCount();
+ for (int32_t i = 0; i < count; i++) {
+ const nsAttrName* name = aContent->GetAttrNameAt(i);
+ int32_t attNs = name->NamespaceID();
+ nsIAtom* attName = name->LocalName();
+
+ // Filter out any attribute starting with [-|_]moz
+ nsDependentAtomString attrNameStr(attName);
+ if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
+ StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
+ continue;
+ }
+
+ nsAutoString* attValue = new nsAutoString();
+ aContent->GetAttr(attNs, attName, *attValue);
+
+ // Filter out special case of <br type="_moz*"> used by the editor.
+ // Bug 16988. Yuck.
+ if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML &&
+ attName == nsGkAtoms::type && attNs == kNameSpaceID_None &&
+ StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) {
+ delete attValue;
+ continue;
+ }
+
+ aBuilder.Append(" ");
+
+ if (MOZ_LIKELY(attNs == kNameSpaceID_None) ||
+ (attNs == kNameSpaceID_XMLNS &&
+ attName == nsGkAtoms::xmlns)) {
+ // Nothing else required
+ } else if (attNs == kNameSpaceID_XML) {
+ aBuilder.Append("xml:");
+ } else if (attNs == kNameSpaceID_XMLNS) {
+ aBuilder.Append("xmlns:");
+ } else if (attNs == kNameSpaceID_XLink) {
+ aBuilder.Append("xlink:");
+ } else {
+ nsIAtom* prefix = name->GetPrefix();
+ if (prefix) {
+ aBuilder.Append(prefix);
+ aBuilder.Append(":");
+ }
+ }
+
+ aBuilder.Append(attName);
+ aBuilder.Append("=\"");
+ AppendEncodedAttributeValue(attValue, aBuilder);
+ aBuilder.Append("\"");
+ }
+
+ aBuilder.Append(">");
+
+ /*
+ // 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() == nsIDOMNode::TEXT_NODE ||
+ fc->NodeType() == nsIDOMNode::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 nsIAtom* nonEscapingElements[] = {
+ nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
+ nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
+ nsGkAtoms::plaintext,
+ // Per the current spec noscript should be escaped in case
+ // scripts are disabled or if document doesn't have
+ // browsing context. However the latter seems to be a spec bug
+ // and Gecko hasn't traditionally done the former.
+ nsGkAtoms::noscript
+ };
+ static mozilla::BloomFilter<12, nsIAtom> sFilter;
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
+ sFilter.add(nonEscapingElements[i]);
+ }
+ }
+
+ nsIAtom* tag = aParent->NodeInfo()->NameAtom();
+ if (sFilter.mightContain(tag)) {
+ for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
+ if (tag == nonEscapingElements[i]) {
+ 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 aDescendentsOnly,
+ nsAString& aOut)
+{
+ // If you pass in a DOCUMENT_NODE, you must pass aDescendentsOnly as true
+ MOZ_ASSERT(aDescendentsOnly ||
+ aRoot->NodeType() != nsIDOMNode::DOCUMENT_NODE);
+
+ nsINode* current = aDescendentsOnly ?
+ nsNodeUtils::GetFirstChildOfTemplateOrNode(aRoot) : aRoot;
+
+ if (!current) {
+ return true;
+ }
+
+ StringBuilder builder;
+ nsIContent* next;
+ while (true) {
+ bool isVoid = false;
+ switch (current->NodeType()) {
+ case nsIDOMNode::ELEMENT_NODE: {
+ Element* elem = current->AsElement();
+ StartElement(elem, builder);
+ isVoid = IsVoidTag(elem);
+ if (!isVoid &&
+ (next = nsNodeUtils::GetFirstChildOfTemplateOrNode(current))) {
+ current = next;
+ continue;
+ }
+ break;
+ }
+
+ case nsIDOMNode::TEXT_NODE:
+ case nsIDOMNode::CDATA_SECTION_NODE: {
+ const nsTextFragment* text = static_cast<nsIContent*>(current)->GetText();
+ nsIContent* parent = current->GetParent();
+ if (ShouldEscape(parent)) {
+ AppendEncodedCharacters(text, builder);
+ } else {
+ builder.Append(text);
+ }
+ break;
+ }
+
+ case nsIDOMNode::COMMENT_NODE: {
+ builder.Append("<!--");
+ builder.Append(static_cast<nsIContent*>(current)->GetText());
+ builder.Append("-->");
+ break;
+ }
+
+ case nsIDOMNode::DOCUMENT_TYPE_NODE: {
+ builder.Append("<!DOCTYPE ");
+ builder.Append(current->NodeName());
+ builder.Append(">");
+ break;
+ }
+
+ case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: {
+ builder.Append("<?");
+ builder.Append(current->NodeName());
+ builder.Append(" ");
+ builder.Append(static_cast<nsIContent*>(current)->GetText());
+ builder.Append(">");
+ break;
+ }
+ }
+
+ while (true) {
+ if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) {
+ builder.Append("</");
+ nsIContent* elem = static_cast<nsIContent*>(current);
+ if (elem->IsHTMLElement() || elem->IsSVGElement() ||
+ elem->IsMathMLElement()) {
+ builder.Append(elem->NodeInfo()->NameAtom());
+ } else {
+ builder.Append(current->NodeName());
+ }
+ builder.Append(">");
+ }
+ 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() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+ DocumentFragment* frag = static_cast<DocumentFragment*>(current);
+ nsIContent* fragHost = frag->GetHost();
+ if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) {
+ current = fragHost;
+ }
+ }
+
+ if (aDescendentsOnly && 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
+ nsGlobalWindow* win = xpc::WindowGlobalOrNull(aGlobal);
+ if (!win) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = win->GetPrincipal();
+ NS_ENSURE_TRUE(principal, false);
+ nsCOMPtr<nsIURI> uri;
+ principal->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ return false;
+ }
+
+ // First check the scheme to avoid getting long specs in the common case.
+ bool isAbout = false;
+ uri->SchemeIs("about", &isAbout);
+ if (!isAbout) {
+ return false;
+ }
+
+ // Now check the spec itself
+ nsAutoCString spec;
+ uri->GetSpecIgnoringRef(spec);
+ return spec.EqualsASCII(aUri);
+}
+
+/* static */ void
+nsContentUtils::SetScrollbarsVisibility(nsIDocShell* aDocShell, bool aVisible)
+{
+ nsCOMPtr<nsIScrollable> scroller = do_QueryInterface(aDocShell);
+
+ if (scroller) {
+ int32_t prefValue;
+
+ if (aVisible) {
+ prefValue = nsIScrollable::Scrollbar_Auto;
+ } else {
+ prefValue = nsIScrollable::Scrollbar_Never;
+ }
+
+ scroller->SetDefaultScrollbarPreferences(
+ nsIScrollable::ScrollOrientation_Y, prefValue);
+ scroller->SetDefaultScrollbarPreferences(
+ nsIScrollable::ScrollOrientation_X, prefValue);
+ }
+}
+
+/* static */ void
+nsContentUtils::GetPresentationURL(nsIDocShell* aDocShell, nsAString& aPresentationUrl)
+{
+ MOZ_ASSERT(aDocShell);
+
+ // Simulate receiver context for web platform test
+ if (Preferences::GetBool("dom.presentation.testing.simulate-receiver")) {
+ nsCOMPtr<nsIDocument> doc;
+
+ nsCOMPtr<nsPIDOMWindowOuter> docShellWin =
+ do_QueryInterface(aDocShell->GetScriptGlobalObject());
+ if (docShellWin) {
+ doc = docShellWin->GetExtantDoc();
+ }
+
+ if (NS_WARN_IF(!doc)) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = doc->GetDocumentURI();
+ if (NS_WARN_IF(!uri)) {
+ return;
+ }
+
+ nsAutoCString uriStr;
+ uri->GetSpec(uriStr);
+ aPresentationUrl = NS_ConvertUTF8toUTF16(uriStr);
+ return;
+ }
+
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
+ aDocShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ aDocShell->GetRootTreeItem(getter_AddRefs(root));
+ if (sameTypeRoot.get() == root.get()) {
+ // presentation URL is stored in TabChild for the top most
+ // <iframe mozbrowser> in content process.
+ TabChild* tabChild = TabChild::GetFrom(aDocShell);
+ if (tabChild) {
+ aPresentationUrl = tabChild->PresentationURL();
+ }
+ return;
+ }
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(aDocShell));
+ nsCOMPtr<nsIDOMElement> topFrameElement;
+ loadContext->GetTopFrameElement(getter_AddRefs(topFrameElement));
+ if (!topFrameElement) {
+ return;
+ }
+
+ topFrameElement->GetAttribute(NS_LITERAL_STRING("mozpresentation"), aPresentationUrl);
+}
+
+/* static */ nsIDocShell*
+nsContentUtils::GetDocShellForEventTarget(EventTarget* aTarget)
+{
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow;
+
+ if (nsCOMPtr<nsINode> node = do_QueryInterface(aTarget)) {
+ bool ignore;
+ innerWindow =
+ do_QueryInterface(node->OwnerDoc()->GetScriptHandlingObject(ignore));
+ } else if ((innerWindow = do_QueryInterface(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(nsIDocument* aDocument)
+{
+ if (!aDocument) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
+
+ if (principal->GetIsSystemPrincipal()) {
+ 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->GetIsSystemPrincipal()) {
+ // 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->GetIsCodebasePrincipal());
+
+ nsCOMPtr<nsIContentSecurityManager> csm =
+ do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
+ NS_WARNING_ASSERTION(csm, "csm is null");
+ if (csm) {
+ bool isTrustworthyOrigin = false;
+ csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
+ if (isTrustworthyOrigin) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */ CustomElementDefinition*
+nsContentUtils::LookupCustomElementDefinition(nsIDocument* aDoc,
+ const nsAString& aLocalName,
+ uint32_t aNameSpaceID,
+ const nsAString* aIs)
+{
+ MOZ_ASSERT(aDoc);
+
+ // To support imported document.
+ nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
+
+ if (aNameSpaceID != kNameSpaceID_XHTML ||
+ !doc->GetDocShell()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
+ if (!window) {
+ return nullptr;
+ }
+
+ RefPtr<CustomElementRegistry> registry(window->CustomElements());
+ if (!registry) {
+ return nullptr;
+ }
+
+ return registry->LookupCustomElementDefinition(aLocalName, aIs);
+}
+
+/* static */ void
+nsContentUtils::SetupCustomElement(Element* aElement,
+ const nsAString* aTypeExtension)
+{
+ MOZ_ASSERT(aElement);
+
+ nsCOMPtr<nsIDocument> doc = aElement->OwnerDoc();
+
+ if (!doc) {
+ return;
+ }
+
+ // To support imported document.
+ doc = doc->MasterDocument();
+
+ if (aElement->GetNameSpaceID() != kNameSpaceID_XHTML ||
+ !doc->GetDocShell()) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
+ if (!window) {
+ return;
+ }
+
+ RefPtr<CustomElementRegistry> registry(window->CustomElements());
+ if (!registry) {
+ return;
+ }
+
+ return registry->SetupCustomElement(aElement, aTypeExtension);
+}
+
+/* static */ void
+nsContentUtils::EnqueueLifecycleCallback(nsIDocument* aDoc,
+ nsIDocument::ElementCallbackType aType,
+ Element* aCustomElement,
+ LifecycleCallbackArgs* aArgs,
+ CustomElementDefinition* aDefinition)
+{
+ MOZ_ASSERT(aDoc);
+
+ // To support imported document.
+ nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
+
+ if (!doc->GetDocShell()) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
+ if (!window) {
+ return;
+ }
+
+ RefPtr<CustomElementRegistry> registry(window->CustomElements());
+ if (!registry) {
+ return;
+ }
+
+ registry->EnqueueLifecycleCallback(aType, aCustomElement, aArgs, aDefinition);
+}
+
+/* static */ void
+nsContentUtils::GetCustomPrototype(nsIDocument* aDoc,
+ int32_t aNamespaceID,
+ nsIAtom* aAtom,
+ JS::MutableHandle<JSObject*> aPrototype)
+{
+ MOZ_ASSERT(aDoc);
+
+ // To support imported document.
+ nsCOMPtr<nsIDocument> doc = aDoc->MasterDocument();
+
+ if (aNamespaceID != kNameSpaceID_XHTML ||
+ !doc->GetDocShell()) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window(doc->GetInnerWindow());
+ if (!window) {
+ return;
+ }
+
+ RefPtr<CustomElementRegistry> registry(window->CustomElements());
+ if (!registry) {
+ return;
+ }
+
+ return registry->GetCustomPrototype(aAtom, aPrototype);
+}
+
+/* static */ bool
+nsContentUtils::AttemptLargeAllocationLoad(nsIHttpChannel* aChannel)
+{
+ MOZ_ASSERT(aChannel);
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_WARN_IF(NS_FAILED(rv) || !loadGroup)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_WARN_IF(NS_FAILED(rv) || !callbacks)) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+ if (NS_WARN_IF(!loadContext)) {
+ return false;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ rv = loadContext->GetAssociatedWindow(getter_AddRefs(window));
+ if (NS_WARN_IF(NS_FAILED(rv) || !window)) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* outer = nsPIDOMWindowOuter::From(window);
+ if (NS_WARN_IF(!outer)) {
+ return false;
+ }
+
+ nsIDocShell* docShell = outer->GetDocShell();
+ nsIDocument* doc = outer->GetExtantDoc();
+
+ // If the docshell is not allowed to change process, report an error based on
+ // the reason
+ const char* errorName = nullptr;
+ switch (docShell->GetProcessLockReason()) {
+ case nsIDocShell::PROCESS_LOCK_NON_CONTENT:
+ errorName = "LargeAllocationNonE10S";
+ break;
+ case nsIDocShell::PROCESS_LOCK_IFRAME:
+ errorName = "LargeAllocationIFrame";
+ break;
+ case nsIDocShell::PROCESS_LOCK_RELATED_CONTEXTS:
+ errorName = "LargeAllocationRelatedBrowsingContexts";
+ break;
+ case nsIDocShell::PROCESS_LOCK_NONE:
+ // Don't print a warning, we're allowed to change processes!
+ break;
+ default:
+ MOZ_ASSERT(false, "Should be unreachable!");
+ return false;
+ }
+
+ if (errorName) {
+ if (doc) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ doc,
+ nsContentUtils::eDOM_PROPERTIES,
+ errorName);
+ }
+
+ return false;
+ }
+
+ // Get the request method, and check if it is a GET request. If it is not GET,
+ // then we cannot perform a large allocation load.
+ nsAutoCString requestMethod;
+ rv = aChannel->GetRequestMethod(requestMethod);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (NS_WARN_IF(!requestMethod.LowerCaseEqualsLiteral("get"))) {
+ if (doc) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ doc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "LargeAllocationNonGetRequest");
+ }
+ return false;
+ }
+
+ TabChild* tabChild = TabChild::GetFrom(outer);
+ NS_ENSURE_TRUE(tabChild, false);
+
+ if (tabChild->TakeIsFreshProcess()) {
+ NS_WARNING("Already in a fresh process, ignoring Large-Allocation header!");
+ if (doc) {
+ nsContentUtils::ReportToConsole(nsIScriptError::infoFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ doc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "LargeAllocationSuccess");
+ }
+ return false;
+ }
+
+ // At this point the fress process load should succeed! We just need to get
+ // ourselves a nsIWebBrowserChrome3 to ask to perform the reload. We should
+ // have one, as we have already confirmed that we are running in a content
+ // process.
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ docShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ NS_ENSURE_TRUE(treeOwner, false);
+
+ nsCOMPtr<nsIWebBrowserChrome3> wbc3 = do_GetInterface(treeOwner);
+ NS_ENSURE_TRUE(wbc3, false);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, false);
+ NS_ENSURE_TRUE(uri, false);
+
+ nsCOMPtr<nsIURI> referrer;
+ rv = aChannel->GetReferrer(getter_AddRefs(referrer));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Actually perform the cross process load
+ bool reloadSucceeded = false;
+ rv = wbc3->ReloadInFreshProcess(docShell, uri, referrer, &reloadSucceeded);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return reloadSucceeded;
+}
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
new file mode 100644
index 000000000..278fbd008
--- /dev/null
+++ b/dom/base/nsContentUtils.h
@@ -0,0 +1,2963 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "js/TypeDecls.h"
+#include "js/Value.h"
+#include "js/RootingAPI.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/GuardObjects.h"
+#include "mozilla/TimeStamp.h"
+#include "nsContentListDeclarations.h"
+#include "nsMathUtils.h"
+#include "nsTArrayForwardDeclare.h"
+#include "Units.h"
+#include "mozilla/dom/AutocompleteInfoBinding.h"
+#include "mozilla/dom/BindingDeclarations.h" // For CallerType
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/Logging.h"
+#include "mozilla/NotNull.h"
+#include "nsIContentPolicy.h"
+#include "nsIDocument.h"
+#include "nsPIDOMWindow.h"
+
+#if defined(XP_WIN)
+// Undefine LoadImage to prevent naming conflict with Windows.
+#undef LoadImage
+#endif
+
+class imgICache;
+class imgIContainer;
+class imgINotificationObserver;
+class imgIRequest;
+class imgLoader;
+class imgRequestProxy;
+class nsAutoScriptBlockerSuppressNodeRemoved;
+class nsHtml5StringParser;
+class nsIChannel;
+class nsIConsoleService;
+class nsIContent;
+class nsIContentPolicy;
+class nsIContentSecurityPolicy;
+class nsIDocShellTreeItem;
+class nsIDocumentLoaderFactory;
+class nsIDOMDocument;
+class nsIDOMDocumentFragment;
+class nsIDOMEvent;
+class nsIDOMHTMLInputElement;
+class nsIDOMKeyEvent;
+class nsIDOMNode;
+class nsIDragSession;
+class nsIEditor;
+class nsIFragmentContentSink;
+class nsIFrame;
+class nsIImageLoadingContent;
+class nsIInterfaceRequestor;
+class nsIIOService;
+class nsILineBreaker;
+class nsILoadGroup;
+class nsIMessageBroadcaster;
+class nsNameSpaceManager;
+class nsIObserver;
+class nsIParser;
+class nsIParserService;
+class nsIPresShell;
+class nsIPrincipal;
+class nsIRequest;
+class nsIRunnable;
+class nsIScriptContext;
+class nsIScriptSecurityManager;
+class nsIStringBundle;
+class nsIStringBundleService;
+class nsISupportsHashKey;
+class nsIURI;
+class nsIUUIDGenerator;
+class nsIWidget;
+class nsIWordBreaker;
+class nsIXPConnect;
+class nsNodeInfoManager;
+class nsPIDOMWindowInner;
+class nsPIDOMWindowOuter;
+class nsPresContext;
+class nsStringBuffer;
+class nsStringHashKey;
+class nsTextFragment;
+class nsView;
+class nsViewportInfo;
+class nsWrapperCache;
+class nsAttrValue;
+class nsITransferable;
+class nsPIWindowRoot;
+class nsIWindowProvider;
+
+struct JSRuntime;
+
+template<class E> class nsCOMArray;
+template<class K, class V> class nsDataHashtable;
+template<class K, class V> class nsRefPtrHashtable;
+template<class T> class nsReadingIterator;
+
+namespace mozilla {
+class ErrorResult;
+class EventListenerManager;
+
+namespace dom {
+struct CustomElementDefinition;
+class DocumentFragment;
+class Element;
+class EventTarget;
+class IPCDataTransfer;
+class IPCDataTransferItem;
+struct LifecycleCallbackArgs;
+class NodeInfo;
+class nsIContentChild;
+class nsIContentParent;
+class TabChild;
+class Selection;
+class TabParent;
+} // namespace dom
+
+namespace ipc {
+class Shmem;
+class IShmemAllocator;
+}
+
+namespace gfx {
+class DataSourceSurface;
+} // namespace gfx
+
+namespace layers {
+class LayerManager;
+} // namespace layers
+
+} // namespace mozilla
+
+class nsIBidiKeyboard;
+
+extern const char kLoadAsData[];
+
+// Stolen from nsReadableUtils, but that's OK, since we can declare the same
+// name multiple times.
+const nsAFlatString& EmptyString();
+const nsAFlatCString& 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_HTMLXUL = 0x0003,
+ EventNameType_All = 0xFFFF
+};
+
+struct EventNameMapping
+{
+ // This holds pointers to nsGkAtoms members, and is therefore safe as a
+ // non-owning reference.
+ nsIAtom* 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;
+};
+
+typedef bool (*CallOnRemoteChildFunction) (mozilla::dom::TabParent* aTabParent,
+ void* aArg);
+
+class nsContentUtils
+{
+ friend class nsAutoScriptBlockerSuppressNodeRemoved;
+ typedef mozilla::dom::Element Element;
+ typedef mozilla::TimeDuration TimeDuration;
+
+public:
+ static nsresult Init();
+
+ static bool IsCallerChrome();
+ static bool ThreadsafeIsCallerChrome();
+ static bool IsCallerContentXBL();
+
+ // 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.
+ static bool ShouldResistFingerprinting(nsIDocShell* aDocShell);
+
+ /**
+ * Returns the parent node of aChild crossing document boundaries.
+ * Uses the parent node in the composed document.
+ */
+ static nsINode* GetCrossDocParentNode(nsINode* aChild);
+
+ /**
+ * Do not ever pass null pointers to this method. If one of your
+ * nsIContents is null, you have to decide for yourself what
+ * "IsDescendantOf" really means.
+ *
+ * @param aPossibleDescendant node to test for being a descendant of
+ * aPossibleAncestor
+ * @param aPossibleAncestor node to test for being an ancestor of
+ * aPossibleDescendant
+ * @return true if aPossibleDescendant is a descendant of
+ * aPossibleAncestor (or is aPossibleAncestor). false
+ * otherwise.
+ */
+ static bool ContentIsDescendantOf(const nsINode* aPossibleDescendant,
+ const nsINode* aPossibleAncestor);
+
+ /**
+ * Similar to ContentIsDescendantOf, 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 ContentIsDescendantOf 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);
+
+ /*
+ * This method fills the |aArray| with all ancestor nodes of |aNode|
+ * including |aNode| at the zero index.
+ */
+ static nsresult GetAncestors(nsINode* aNode,
+ nsTArray<nsINode*>& aArray);
+
+ /*
+ * 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 IndexOf the child in relation to its parent.
+ *
+ * This method just sucks.
+ */
+ static nsresult GetAncestorsAndOffsets(nsIDOMNode* aNode,
+ int32_t aOffset,
+ nsTArray<nsIContent*>* aAncestorNodes,
+ nsTArray<int32_t>* aAncestorOffsets);
+
+ /*
+ * The out parameter, |aCommonAncestor| will be the closest node, if any,
+ * to both |aNode| and |aOther| which is also an ancestor of each.
+ * Returns an error if the two nodes are disconnected and don't have
+ * a common ancestor.
+ */
+ static nsresult GetCommonAncestor(nsIDOMNode *aNode,
+ nsIDOMNode *aOther,
+ nsIDOMNode** aCommonAncestor);
+
+ /**
+ * Returns the common ancestor, if any, for two nodes. Returns null if the
+ * nodes are disconnected.
+ */
+ static nsINode* GetCommonAncestor(nsINode* aNode1,
+ nsINode* aNode2);
+
+ /**
+ * Returns true if aNode1 is before aNode2 in the same connected
+ * tree.
+ */
+ static bool PositionIsBefore(nsINode* aNode1, nsINode* aNode2);
+
+ /**
+ * 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.
+ */
+ static int32_t ComparePoints(nsINode* aParent1, int32_t aOffset1,
+ nsINode* aParent2, int32_t aOffset2,
+ bool* aDisconnected = nullptr);
+ static int32_t ComparePoints(nsIDOMNode* aParent1, int32_t aOffset1,
+ nsIDOMNode* aParent2, int32_t aOffset2,
+ bool* aDisconnected = nullptr);
+
+ /**
+ * 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 nsIAtom* aId);
+
+ /**
+ * Reverses the document position flags passed in.
+ *
+ * @param aDocumentPosition The document position flags to be reversed.
+ *
+ * @return The reversed document position flags.
+ *
+ * @see nsIDOMNode
+ */
+ static uint16_t ReverseDocumentPosition(uint16_t aDocumentPosition);
+
+ /**
+ * Returns a subdocument for aDocument with a particular outer window ID.
+ *
+ * @param aDocument
+ * The document whose subdocuments will be searched.
+ * @param aOuterWindowID
+ * The outer window ID for the subdocument to be found. This must
+ * be a value greater than 0.
+ * @return nsIDocument*
+ * A pointer to the found nsIDocument. nullptr if the subdocument
+ * cannot be found, or if either aDocument or aOuterWindowId were
+ * invalid. If the outer window ID belongs to aDocument itself, this
+ * will return a pointer to aDocument.
+ */
+ static nsIDocument* GetSubdocumentWithOuterWindowId(nsIDocument *aDocument,
+ uint64_t aOuterWindowId);
+
+ static uint32_t CopyNewlineNormalizedUnicodeTo(const nsAString& aSource,
+ uint32_t aSrcOffset,
+ char16_t* aDest,
+ uint32_t aLength,
+ bool& aLastCharCR);
+
+ static uint32_t CopyNewlineNormalizedUnicodeTo(nsReadingIterator<char16_t>& aSrcStart, const nsReadingIterator<char16_t>& aSrcEnd, nsAString& aDest);
+
+ 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);
+ static bool IsFirstLetterPunctuationAt(const nsTextFragment* aFrag, uint32_t aOffset);
+
+ /**
+ * Returns true if aChar is of class Lu, Ll, Lt, Lm, Lo, Nd, Nl or No
+ */
+ static bool IsAlphanumeric(uint32_t aChar);
+ static bool IsAlphanumericAt(const nsTextFragment* aFrag, uint32_t aOffset);
+
+ /*
+ * 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);
+
+ /**
+ * Is the HTML local name a block element?
+ */
+ static bool IsHTMLBlock(nsIContent* aContent);
+
+ enum ParseHTMLIntegerResultFlags {
+ eParseHTMLInteger_NoFlags = 0,
+ eParseHTMLInteger_IsPercent = 1 << 0,
+ // eParseHTMLInteger_NonStandard is set if the string representation of the
+ // integer was not the canonical one (e.g. had extra leading '+' or '0').
+ eParseHTMLInteger_NonStandard = 1 << 1,
+ eParseHTMLInteger_DidNotConsumeAllInput = 1 << 2,
+ // Set if one or more error flags were set.
+ eParseHTMLInteger_Error = 1 << 3,
+ eParseHTMLInteger_ErrorNoValue = 1 << 4,
+ eParseHTMLInteger_ErrorOverflow = 1 << 5
+ };
+ static int32_t ParseHTMLInteger(const nsAString& aValue,
+ ParseHTMLIntegerResultFlags *aResult);
+
+ /**
+ * 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,
+ nsIDOMNode* aUnTrustedNode);
+ static nsresult CheckSameOrigin(const nsINode* aTrustedNode,
+ const nsINode* unTrustedNode);
+
+ // Check if the (JS) caller can access aNode.
+ static bool CanCallerAccess(nsIDOMNode *aNode);
+ static bool CanCallerAccess(nsINode* aNode);
+
+ // Check if the (JS) caller can access aWindow.
+ // aWindow can be either outer or inner window.
+ static bool CanCallerAccess(nsPIDOMWindowInner* aWindow);
+
+ /**
+ * GetDocumentFromCaller gets its document by looking at the last called
+ * function and finding the document that the function itself relates to.
+ * For example, consider two windows A and B in the same origin. B has a
+ * function which does something that ends up needing the current document.
+ * If a script in window A were to call B's function, GetDocumentFromCaller
+ * would find that function (in B) and return B's document.
+ *
+ * @return The document or null if no JS Context.
+ */
+ static nsIDocument* GetDocumentFromCaller();
+
+ // Check if a node is in the document prolog, i.e. before the document
+ // element.
+ static bool InProlog(nsINode *aNode);
+
+ static nsIParserService* GetParserService();
+
+ static nsNameSpaceManager* NameSpaceManager()
+ {
+ return sNameSpaceManager;
+ }
+
+ static nsIIOService* GetIOService()
+ {
+ return sIOService;
+ }
+
+ 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. 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.
+ static nsIPrincipal* ObjectPrincipal(JSObject* aObj);
+
+ static nsresult GenerateStateKey(nsIContent* aContent,
+ const nsIDocument* 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,
+ nsIDocument* aDocument,
+ nsIURI* aBaseURI);
+
+ /**
+ * Convert aInput (in encoding aEncoding) to UTF16 in aOutput.
+ *
+ * @param aEncoding the Gecko-canonical name of the encoding or the empty
+ * string (meaning UTF-8)
+ */
+ static nsresult ConvertStringFromEncoding(const nsACString& aEncoding,
+ const nsACString& aInput,
+ nsAString& aOutput);
+
+ /**
+ * Determine whether a buffer begins with a BOM for UTF-8, UTF-16LE,
+ * UTF-16BE
+ *
+ * @param aBuffer the buffer to check
+ * @param aLength the length of the buffer
+ * @param aCharset empty if not found
+ * @return boolean indicating whether a BOM was detected.
+ */
+ static bool CheckForBOM(const unsigned char* aBuffer, uint32_t aLength,
+ nsACString& aCharset);
+
+ /**
+ * Returns true if |aName| is a valid name to be registered via
+ * document.registerElement.
+ */
+ static bool IsCustomElementName(nsIAtom* aName);
+
+ static nsresult CheckQName(const nsAString& aQualifiedName,
+ bool aNamespaceAware = true,
+ const char16_t** aColon = nullptr);
+
+ static nsresult SplitQName(const nsIContent* aNamespaceResolver,
+ const nsAFlatString& aQName,
+ int32_t *aNamespace, nsIAtom **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, nsIAtom **aPrefix,
+ nsIAtom **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 char* 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 char* 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 char* 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 char* aType);
+
+ // Returns true if aDoc1 and aDoc2 have equal NodePrincipal()s.
+ static bool HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2);
+
+ static nsILineBreaker* LineBreaker()
+ {
+ return sLineBreaker;
+ }
+
+ static nsIWordBreaker* WordBreaker()
+ {
+ return sWordBreaker;
+ }
+
+ /**
+ * 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,
+ nsIAtom* 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 GetCurrentDoc returns nullptr)
+ */
+ static nsPresContext* GetContextForContent(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 aContext 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,
+ nsISupports* aContext,
+ nsIDocument* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal,
+ int16_t* aImageBlockingStatus = nullptr,
+ uint32_t aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ /**
+ * Returns true if objects in aDocument shouldn't initiate image loads.
+ */
+ static bool DocumentInactiveForImageLoads(nsIDocument* aDocument);
+
+ /**
+ * 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 aReferrer the referrer URI
+ * @param aReferrerPolicy the referrer-sending policy to 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
+ * @return the imgIRequest for the image load
+ */
+ static nsresult LoadImage(nsIURI* aURI,
+ nsINode* aContext,
+ nsIDocument* aLoadingDocument,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIURI* aReferrer,
+ mozilla::net::ReferrerPolicy aReferrerPolicy,
+ imgINotificationObserver* aObserver,
+ int32_t aLoadFlags,
+ const nsAString& initiatorType,
+ imgRequestProxy** aRequest,
+ uint32_t aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ /**
+ * 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(nsIDocument* aDoc);
+ static imgLoader* GetImgLoaderForChannel(nsIChannel* aChannel,
+ nsIDocument* aContext);
+
+ /**
+ * Returns whether the given URI is in the image cache.
+ */
+ static bool IsImageInCache(nsIURI* aURI, nsIDocument* aDocument);
+
+ /**
+ * 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);
+
+ /**
+ * Helper method to call imgIRequest::GetStaticRequest.
+ */
+ static already_AddRefed<imgRequestProxy> GetStaticRequest(imgRequestProxy* aRequest);
+
+ /**
+ * 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 name
+ * from aNodeInfo.
+ */
+ static nsresult NameChanged(mozilla::dom::NodeInfo* aNodeInfo, nsIAtom* 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, nsIAtom *aEventName,
+ bool aIsForWindow,
+ uint32_t *aArgCount, const char*** aArgNames);
+
+ /**
+ * Returns origin attributes of the document.
+ **/
+ static mozilla::PrincipalOriginAttributes
+ GetOriginAttributes(nsIDocument* aDoc);
+
+ /**
+ * Returns origin attributes of the load group.
+ **/
+ static mozilla::PrincipalOriginAttributes
+ GetOriginAttributes(nsILoadGroup* aLoadGroup);
+
+ /**
+ * Returns true if this document is in a Private Browsing window.
+ */
+ static bool IsInPrivateBrowsing(nsIDocument* aDoc);
+
+ /**
+ * Returns true if this loadGroup uses Private Browsing.
+ */
+ static bool IsInPrivateBrowsing(nsILoadGroup* aLoadGroup);
+
+ /**
+ * If aNode is not an element, return true exactly when aContent's binding
+ * parent is null.
+ *
+ * If aNode is an element, return true exactly when aContent's binding parent
+ * is the same as aNode's.
+ *
+ * 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 nsIContent* aContent);
+
+ /**
+ * Return the nsIXPConnect service.
+ */
+ static nsIXPConnect *XPConnect()
+ {
+ return sXPConnect;
+ }
+
+ /**
+ * Report simple error message to the browser console
+ * @param aErrorText the error message
+ * @param classification Name of the module reporting error
+ */
+ static void LogSimpleConsoleError(const nsAString& aErrorText,
+ const char * classification);
+
+ /**
+ * 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=EmptyString()] (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 nsIDocument* aDocument,
+ nsIURI* aURI = nullptr,
+ const nsAFlatString& aSourceLine
+ = EmptyString(),
+ 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=nullptr] (Optional) Parameters to be substituted into
+ localized message.
+ * @param [aParamsLength=0] (Optional) Length of aParams.
+ * @param [aURI=nullptr] (Optional) URI of resource containing error.
+ * @param [aSourceLine=EmptyString()] (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,
+ eXBL_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,
+ PropertiesFile_COUNT
+ };
+ static nsresult ReportToConsole(uint32_t aErrorFlags,
+ const nsACString& aCategory,
+ const nsIDocument* aDocument,
+ PropertiesFile aFile,
+ const char *aMessageName,
+ const char16_t **aParams = nullptr,
+ uint32_t aParamsLength = 0,
+ nsIURI* aURI = nullptr,
+ const nsAFlatString& aSourceLine
+ = EmptyString(),
+ uint32_t aLineNumber = 0,
+ uint32_t aColumnNumber = 0);
+
+ static void LogMessageToConsole(const char* aMsg);
+
+ /**
+ * Get the localized string named |aKey| in properties file |aFile|.
+ */
+ static nsresult GetLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ nsXPIDLString& 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);
+
+ /**
+ * Helper function that generates a UUID.
+ */
+ static nsresult GenerateUUIDInPlace(nsID& aUUID);
+
+ static bool PrefetchEnabled(nsIDocShell* aDocShell);
+
+ /**
+ * Fill (with the parameters given) the localized string named |aKey| in
+ * properties file |aFile|.
+ */
+private:
+ static nsresult FormatLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ const char16_t** aParams,
+ uint32_t aParamsLength,
+ nsXPIDLString& aResult);
+
+public:
+ template<uint32_t N>
+ static nsresult FormatLocalizedString(PropertiesFile aFile,
+ const char* aKey,
+ const char16_t* (&aParams)[N],
+ nsXPIDLString& aResult)
+ {
+ return FormatLocalizedString(aFile, aKey, aParams, N, 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,
+ nsXPIDLString& aResult);
+
+ /**
+ * Returns true if aDocument is a chrome document
+ */
+ static bool IsChromeDoc(nsIDocument *aDocument);
+
+ /**
+ * Returns true if aDocument is in a docshell whose parent is the same type
+ */
+ static bool IsChildOfSameType(nsIDocument* aDoc);
+
+ /**
+ '* Returns true if the content-type is any of the supported script types.
+ */
+ static bool IsScriptType(const nsACString& aContentType);
+
+ /**
+ '* Returns true if the content-type will be rendered as plain-text.
+ */
+ static bool IsPlainTextType(const nsACString& aContentType);
+
+ /**
+ * Get the script file name to use when compiling the script
+ * referenced by aURI. In cases where there's no need for any extra
+ * security wrapper automation the script file name that's returned
+ * will be the spec in aURI, else it will be the spec in aDocument's
+ * URI followed by aURI's spec, separated by " -> ". Returns true
+ * if the script file name was modified, false if it's aURI's
+ * spec.
+ */
+ static bool GetWrapperSafeScriptFilename(nsIDocument *aDocument,
+ nsIURI *aURI,
+ nsACString& aScriptURI,
+ nsresult* aRv);
+
+
+ /**
+ * 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(nsIDocument *aDocument);
+
+ /**
+ * Return the content policy service
+ */
+ static nsIContentPolicy *GetContentPolicy();
+
+ /**
+ * Map internal content policy types to external ones.
+ */
+ static nsContentPolicyType InternalContentPolicyTypeToExternal(nsContentPolicyType aType);
+
+ /**
+ * Map internal content policy types to external ones or preload types:
+ * * TYPE_INTERNAL_SCRIPT_PRELOAD
+ * * TYPE_INTERNAL_IMAGE_PRELOAD
+ * * TYPE_INTERNAL_STYLESHEET_PRELOAD
+ *
+ * Note: DO NOT call this function unless you know what you're doing!
+ */
+ static nsContentPolicyType InternalContentPolicyTypeToExternalOrPreload(nsContentPolicyType aType);
+
+ /**
+ * Map internal content policy types to external ones, worker, or preload types:
+ * * TYPE_INTERNAL_WORKER
+ * * TYPE_INTERNAL_SHARED_WORKER
+ * * TYPE_INTERNAL_SERVICE_WORKER
+ *
+ * Note: DO NOT call this function unless you know what you're doing!
+ */
+ static nsContentPolicyType InternalContentPolicyTypeToExternalOrWorker(nsContentPolicyType aType);
+
+ /**
+ * 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(nsIDocument* 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.
+ * @param aOwnerDoc The ownerDocument of aChild.
+ */
+ static void MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent,
+ nsIDocument* aOwnerDoc);
+
+ /**
+ * This method creates and dispatches a trusted event.
+ * Works only with events which can be created by calling
+ * nsIDOMDocument::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
+ * nsIDOMEventTarget.
+ * @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 nsIDOMEventTarget::DispatchEvent.
+ */
+ static nsresult DispatchTrustedEvent(nsIDocument* aDoc,
+ nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble,
+ bool aCancelable,
+ bool *aDefaultAction = nullptr);
+
+ /**
+ * This method creates and dispatches a untrusted event.
+ * Works only with events which can be created by calling
+ * nsIDOMDocument::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
+ * nsIDOMEventTarget.
+ * @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 nsIDOMEventTarget::DispatchEvent.
+ */
+ static nsresult DispatchUntrustedEvent(nsIDocument* aDoc,
+ nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble,
+ bool aCancelable,
+ bool *aDefaultAction = nullptr);
+
+ /**
+ * 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
+ * nsIDOMDocument::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 nsIDOMEventTarget::DispatchEvent.
+ */
+ static nsresult DispatchChromeEvent(nsIDocument* aDoc,
+ nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble,
+ bool aCancelable,
+ bool *aDefaultAction = nullptr);
+
+ /**
+ * Helper function for dispatching a "DOMServiceWorkerFocusClient" event to
+ * the chrome event handler of the given DOM Window. This has the effect
+ * of focusing the corresponding tab and bringing the browser window
+ * to the foreground.
+ */
+ static nsresult DispatchFocusChromeEvent(nsPIDOMWindowOuter* aWindow);
+
+ /**
+ * 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
+ * nsIDOMDocument::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
+ * nsIDOMEventTarget.
+ * @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 nsIDOMEventTarget::DispatchEvent.
+ */
+ static nsresult DispatchEventOnlyToChrome(nsIDocument* aDoc,
+ nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble,
+ bool aCancelable,
+ bool *aDefaultAction = nullptr);
+
+ /**
+ * 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(nsIAtom* 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 mozilla::EventMessage GetEventMessage(nsIAtom* aName);
+
+ /**
+ * Returns the EventMessage and nsIAtom to be used for event listener
+ * registration.
+ */
+ static mozilla::EventMessage
+ GetEventMessageAndAtomForListener(const nsAString& aName, nsIAtom** 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 nsIAtom* GetEventMessageAndAtom(const nsAString& aName,
+ mozilla::EventClassID aEventClassID,
+ mozilla::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 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(nsIAtom *aLocalName, nsIAtom *aPrefix,
+ int32_t aNamespaceID);
+
+ /**
+ * Creates a DocumentFragment from text using a context node to resolve
+ * namespaces.
+ *
+ * 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 nsresult CreateContextualFragment(nsINode* aContextNode,
+ const nsAString& aFragment,
+ bool aPreventScriptExecution,
+ nsIDOMDocumentFragment** aReturn);
+ 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.
+ *
+ * @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.
+ */
+ static nsresult ParseFragmentHTML(const nsAString& aSourceBuffer,
+ nsIContent* aTargetNode,
+ nsIAtom* aContextLocalName,
+ int32_t aContextNamespace,
+ bool aQuirks,
+ bool aPreventScriptExecution);
+
+ /**
+ * Invoke the fragment parsing algorithm (innerHTML) using the XML parser.
+ *
+ * @param aSourceBuffer the string being set as innerHTML
+ * @param aTargetNode the target container
+ * @param aTagStack the namespace mapping context
+ * @param aPreventExecution whether to mark scripts as already started
+ * @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,
+ nsIDocument* aDocument,
+ nsTArray<nsString>& aTagStack,
+ bool aPreventScriptExecution,
+ nsIDOMDocumentFragment** 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,
+ nsIDocument* 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);
+
+ /**
+ * 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.
+ *
+ * @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.
+ */
+ 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.
+ */
+ MOZ_MUST_USE
+ static bool GetNodeTextContent(nsINode* aNode, bool aDeep,
+ nsAString& aResult, const mozilla::fallible_t&);
+
+ static void GetNodeTextContent(nsINode* aNode, bool aDeep,
+ nsAString& aResult);
+
+ /**
+ * Same as GetNodeTextContents but appends the result rather than sets it.
+ */
+ static bool AppendNodeTextContent(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);
+
+ /**
+ * Unbinds the content from the tree and nulls it out if it's not null.
+ */
+ static void DestroyAnonymousContent(nsCOMPtr<nsIContent>* aContent);
+ static void DestroyAnonymousContent(nsCOMPtr<Element>* aElement);
+
+ /*
+ * 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.
+ */
+ static void NotifyInstalledMenuKeyboardListener(bool aInstalling);
+
+ /**
+ * Returns true if aPrincipal is the system principal.
+ */
+ static bool IsSystemPrincipal(nsIPrincipal* aPrincipal);
+
+ /**
+ * Returns true if aPrincipal is an nsExpandedPrincipal.
+ */
+ static bool IsExpandedPrincipal(nsIPrincipal* aPrincipal);
+
+ /**
+ * Returns true if aPrincipal is the system or an nsExpandedPrincipal.
+ */
+ static bool IsSystemOrExpandedPrincipal(nsIPrincipal* aPrincipal)
+ {
+ return IsSystemPrincipal(aPrincipal) || IsExpandedPrincipal(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 aPresContext the pres context, must be non-null.
+ * @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 aIsUserTriggered whether the user triggered the link. This would be
+ * false for loads from auto XLinks or from the
+ * click() method if we ever implement it.
+ * @param aIsTrusted If false, JS Context will be pushed to stack
+ * when the link is triggered.
+ */
+ static void TriggerLink(nsIContent *aContent, nsPresContext *aPresContext,
+ nsIURI *aLinkURI, const nsString& aTargetSpec,
+ bool aClick, bool aIsUserTriggered,
+ 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.
+ */
+ static void HidePopupsInDocument(nsIDocument* 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(nsIDocument *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);
+
+ /**
+ * If offline-apps.allow_by_default is true, we set offline-app permission
+ * for the principal and return true. Otherwise false.
+ */
+ static bool MaybeAllowOfflineAppByDefault(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() {
+ return sScriptBlockerCount == 0;
+ }
+
+ // XXXcatalinb: workaround for weird include error when trying to reference
+ // ipdl types in WindowWatcher.
+ static nsIWindowProvider*
+ GetWindowProviderForContentProcess();
+
+ // 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(nsIDocument* 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 "synchronous section", in the form of an nsIRunnable run once the
+ * event loop has reached a "metastable state". |aRunnable| must not cause any
+ * queued events to be processed (i.e. must not spin the event loop).
+ * We've reached a metastable state when the currently executing task or
+ * microtask has finished. This is not specced at this time.
+ * In practice this runs aRunnable once the currently executing task or
+ * microtask finishes. If called multiple times per microtask, all the
+ * runnables will be executed, in the order in which RunInMetastableState()
+ * was called
+ */
+ static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable);
+
+ // Call EnterMicroTask when you're entering JS execution.
+ // Usually the best way to do this is to use nsAutoMicroTask.
+ static void EnterMicroTask();
+ static void LeaveMicroTask();
+
+ static bool IsInMicroTask();
+ static uint32_t MicroTaskLevel();
+ static void SetMicroTaskLevel(uint32_t aLevel);
+
+ static void PerformMainThreadMicroTaskCheckpoint();
+
+ /* 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
+ */
+ static nsresult ProcessViewportInfo(nsIDocument *aDocument,
+ const nsAString &viewportInfo);
+
+ static nsIScriptContext* GetContextForEventHandlers(nsINode* aNode,
+ nsresult* aRv);
+
+ static JSContext *GetCurrentJSContext();
+ static JSContext *GetCurrentJSContextForThread();
+
+ /**
+ * 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.
+ *
+ * @pre aPrincipal/aOrigin must not be null.
+ *
+ * @note this should be used for HTML5 origin determination.
+ */
+ static nsresult GetASCIIOrigin(nsIPrincipal* aPrincipal,
+ nsACString& aOrigin);
+ 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
+ * nsIDOMXULCommandEvent.
+ * If aShell is not null, dispatching goes via
+ * nsIPresShell::HandleDOMEventWithTarget.
+ */
+ static nsresult DispatchXULCommand(nsIContent* aTarget,
+ bool aTrusted,
+ nsIDOMEvent* aSourceEvent = nullptr,
+ nsIPresShell* aShell = nullptr,
+ bool aCtrl = false,
+ bool aAlt = false,
+ bool aShift = false,
+ bool aMeta = false);
+
+ 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();
+
+ MOZ_MUST_USE
+ 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.
+ MOZ_MUST_USE
+ static nsresult WrapNative(JSContext *cx, nsISupports *native,
+ JS::MutableHandle<JS::Value> vp,
+ bool aAllowWrapping = true)
+ {
+ return WrapNative(cx, native, nullptr, nullptr, vp, aAllowWrapping);
+ }
+
+ MOZ_MUST_USE
+ 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);
+ MOZ_MUST_USE
+ 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)
+ {
+ NS_PRECONDITION(aRootNode, "Must have root node");
+
+ return NS_GetFuncStringHTMLCollection(aRootNode, MatchClassNames,
+ DestroyClassNameArray,
+ AllocClassMatchingInfo,
+ 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 nsIPresShell* FindPresShellForDocument(const nsIDocument* aDoc);
+
+ /**
+ * 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.
+ */
+ static nsIWidget* WidgetForDocument(const nsIDocument* aDoc);
+
+ /**
+ * Returns a layer manager 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
+ * layer manager.
+ *
+ * @param aDoc the document for which to return a layer manager.
+ * @param aAllowRetaining an outparam that states whether the returned
+ * layer manager should be used for retained layers
+ */
+ static already_AddRefed<mozilla::layers::LayerManager>
+ LayerManagerForDocument(const nsIDocument *aDoc);
+
+ /**
+ * Returns a layer manager 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
+ * layer manager. In addition to the normal layer manager lookup this will
+ * specifically request a persistent layer manager. This means that the layer
+ * manager is expected to remain the layer manager for the document in the
+ * forseeable future. This function should be used carefully as it may change
+ * the document's layer manager.
+ *
+ * @param aDoc the document for which to return a layer manager.
+ * @param aAllowRetaining an outparam that states whether the returned
+ * layer manager should be used for retained layers
+ */
+ static already_AddRefed<mozilla::layers::LayerManager>
+ PersistentLayerManagerForDocument(nsIDocument *aDoc);
+
+ /**
+ * 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 the DOM full-screen API is enabled.
+ */
+ static bool IsFullScreenApiEnabled();
+
+ /**
+ * Returns true if the unprefixed fullscreen API is enabled.
+ */
+ static bool IsUnprefixedFullscreenApiEnabled()
+ { return sIsUnprefixedFullscreenApiEnabled; }
+
+ /**
+ * Returns true if requests for full-screen 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.
+ */
+ static bool IsRequestFullScreenAllowed();
+
+ /**
+ * Returns true if calling execCommand with 'cut' or 'copy' arguments
+ * is restricted to chrome code.
+ */
+ static bool IsCutCopyRestricted()
+ {
+ return !sIsCutCopyAllowed;
+ }
+
+ /**
+ * Returns true if calling execCommand with 'cut' or 'copy' arguments is
+ * allowed in the current context. These are only allowed if the user initiated
+ * them (like with a mouse-click or key press).
+ */
+ static bool IsCutCopyAllowed();
+
+ /*
+ * Returns true if the performance timing APIs are enabled.
+ */
+ static bool IsPerformanceTimingEnabled()
+ {
+ return sIsPerformanceTimingEnabled;
+ }
+
+ /*
+ * Returns true if user timing API should print to console.
+ */
+ static bool IsUserTimingLoggingEnabled()
+ {
+ return sIsUserTimingLoggingEnabled;
+ }
+
+ /*
+ * Returns true if the performance timing APIs are enabled.
+ */
+ static bool IsResourceTimingEnabled()
+ {
+ return sIsResourceTimingEnabled;
+ }
+
+ /*
+ * Returns true if notification should be sent for peformance timing events.
+ */
+ static bool SendPerformanceTimingNotifications()
+ {
+ return sSendPerformanceTimingNotifications;
+ }
+
+ /*
+ * Returns true if the frame timing APIs are enabled.
+ */
+ static bool IsFrameTimingEnabled();
+
+ /*
+ * Returns true if URL setters should percent encode the Hash/Ref segment
+ * and getters should return the percent decoded value of the segment
+ */
+ static bool EncodeDecodeURLHash()
+ {
+ return sEncodeDecodeURLHash;
+ }
+
+ /*
+ * Returns true if URL getters should percent decode the value of the segment
+ */
+ static bool GettersDecodeURLHash()
+ {
+ return sGettersDecodeURLHash && sEncodeDecodeURLHash;
+ }
+
+ /*
+ * Returns true if the browser should attempt to prevent content scripts
+ * from collecting distinctive information about the browser that could
+ * be used to "fingerprint" and track the user across websites.
+ */
+ static bool ResistFingerprinting()
+ {
+ return sPrivacyResistFingerprinting;
+ }
+
+ /*
+ * Returns true if the browser should attempt to prevent the given caller type
+ * from collecting distinctive information about the browser that could
+ * be used to "fingerprint" and track the user across websites.
+ */
+ static bool ResistFingerprinting(mozilla::dom::CallerType aCallerType)
+ {
+ return aCallerType != mozilla::dom::CallerType::System &&
+ ResistFingerprinting();
+ }
+
+ /**
+ * Returns true if the browser should show busy cursor when loading page.
+ */
+ static bool UseActivityCursor()
+ {
+ return sUseActivityCursor;
+ }
+
+ /**
+ * Return true if this doc is controlled by a ServiceWorker.
+ */
+ static bool IsControlledByServiceWorker(nsIDocument* aDocument);
+
+ /**
+ * 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(nsIDocument* 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 root document in a document hierarchy. Normally this
+ * will be the chrome document.
+ */
+ static nsIDocument* GetRootDocument(nsIDocument* aDoc);
+
+ /**
+ * Returns true if aWin and the current pointer lock document
+ * have common scriptable top window.
+ */
+ static bool IsInPointerLockContext(nsPIDOMWindowOuter* aWin);
+
+ /**
+ * Returns the time limit on handling user input before
+ * EventStateManager::IsHandlingUserInput() stops returning true.
+ * This enables us to detect long running user-generated event handlers.
+ */
+ static TimeDuration HandlingUserInputTimeout();
+
+ 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 a tabbable subdocument.
+ * A sub document isn't tabbable when it's a zombie document.
+ *
+ * @param aElement element to test.
+ *
+ * @return Whether the subdocument is tabbable.
+ */
+ static bool IsSubDocumentTabbable(nsIContent* aContent);
+
+ /**
+ * Returns if aNode ignores user focus.
+ *
+ * @param aNode node to test
+ *
+ * @return Whether the node ignores user focus.
+ */
+ static bool IsUserFocusIgnored(nsINode* aNode);
+
+ /**
+ * 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 if internal SWF player is enabled.
+ */
+ static bool IsSWFPlayerEnabled();
+
+ enum ContentViewerType
+ {
+ TYPE_UNSUPPORTED,
+ TYPE_CONTENT,
+ TYPE_PLUGIN,
+ 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.
+ * @result whether the given string is matches the pattern.
+ */
+ static bool IsPatternMatching(nsAString& aValue, nsAString& aPattern,
+ nsIDocument* aDocument);
+
+ /**
+ * 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(nsIDOMHTMLInputElement* 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);
+
+ /**
+ * 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, nsIAtom *aName,
+ nsAString& aValue);
+
+ /**
+ * Returns true if the language name is a version of JavaScript and
+ * false otherwise
+ */
+ static bool IsJavaScriptLanguage(const nsString& aName);
+
+ /**
+ * Returns the JSVersion for a string of the form '1.n', n = 0, ..., 8, and
+ * JSVERSION_UNKNOWN for other strings.
+ */
+ static JSVersion ParseJavascriptVersion(const nsAString& aVersionStr);
+
+ static bool IsJavascriptMIMEType(const nsAString& aMIMEType);
+
+ static void SplitMimeType(const nsAString& aValue, nsString& aType,
+ nsString& aParams);
+
+ /**
+ * Function checks if the user is idle.
+ *
+ * @param aRequestedIdleTimeInMS The idle observer's requested idle time.
+ * @param aUserIsIdle boolean indicating if the user
+ * is currently idle or not. *
+ * @return NS_OK NS_OK returned if the requested idle service and
+ * the current idle time were successfully obtained.
+ * NS_ERROR_FAILURE returned if the the requested
+ * idle service or the current idle were not obtained.
+ */
+ static nsresult IsUserIdle(uint32_t aRequestedIdleTimeInMS, bool* aUserIsIdle);
+
+ /**
+ * 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,
+ int32_t& aOutStartOffset,
+ int32_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);
+
+ static nsIEditor* GetHTMLEditor(nsPresContext* aPresContext);
+
+ /**
+ * Returns true if the browser.dom.window.dump.enabled pref is set.
+ */
+ static bool DOMWindowDumpEnabled();
+
+ /**
+ * Returns true if the privacy.donottrackheader.enabled pref is set.
+ */
+ static bool DoNotTrackEnabled();
+
+ /**
+ * 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 content is an insertion point for XBL
+ * bindings or web components ShadowRoot. In web components,
+ * this corresponds to a <content> element that participates
+ * in node distribution. In XBL this corresponds to an
+ * <xbl:children> element in anonymous content.
+ *
+ * @param aContent The content to test for being an insertion point.
+ */
+ static bool IsContentInsertionPoint(nsIContent* aContent);
+
+
+ /**
+ * Returns whether the children of the provided content are
+ * nodes that are distributed to Shadow DOM insertion points.
+ */
+ static bool HasDistributedChildren(nsIContent* aContent);
+
+ /**
+ * Returns whether a given header is forbidden for an XHR or fetch
+ * request.
+ */
+ static bool IsForbiddenRequestHeader(const nsACString& aHeader);
+
+ /**
+ * Returns whether a given header is forbidden for a system XHR
+ * request.
+ */
+ static bool IsForbiddenSystemRequestHeader(const nsACString& aHeader);
+
+ /**
+ * 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 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);
+
+ /**
+ * 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, nsCString& aHost);
+
+ /*
+ * Call the given callback on all remote children of the given top-level
+ * window. Return true from the callback to stop calling further children.
+ */
+ static void CallOnAllRemoteChildren(nsPIDOMWindowOuter* aWindow,
+ CallOnRemoteChildFunction aCallback,
+ void* aArg);
+
+ /*
+ * Call nsPIDOMWindow::SetKeyboardIndicators all all remote children. This is
+ * in here rather than nsGlobalWindow because TabParent indirectly includes
+ * Windows headers which aren't allowed there.
+ */
+ static void SetKeyboardIndicatorsOnRemoteChildren(nsPIDOMWindowOuter* aWindow,
+ UIStateChangeType aShowAccelerators,
+ UIStateChangeType aShowFocusRings);
+
+ /**
+ * Given an nsIFile, attempts to read it into aString.
+ *
+ * Note: Use sparingly! This causes main-thread I/O, which causes jank and all
+ * other bad things.
+ */
+ static nsresult SlurpFileToString(nsIFile* aFile, nsACString& aString);
+
+ /**
+ * Returns true if the mime service thinks this file contains an image.
+ *
+ * The content type is returned in aType.
+ */
+ static bool IsFileImage(nsIFile* aFile, nsACString& aType);
+
+ /**
+ * Given an IPCDataTransferItem that has a flavor for which IsFlavorImage
+ * returns true and whose IPCDataTransferData is of type nsCString (raw image
+ * data), construct an imgIContainer for the image encoded by the transfer
+ * item.
+ */
+ static nsresult DataTransferItemToImage(const mozilla::dom::IPCDataTransferItem& aItem,
+ 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 nsresult IPCTransferableToTransferable(const mozilla::dom::IPCDataTransfer& aDataTransfer,
+ const bool& aIsPrivateData,
+ nsIPrincipal* aRequestingPrincipal,
+ nsITransferable* aTransferable,
+ mozilla::dom::nsIContentParent* aContentParent,
+ mozilla::dom::TabChild* aTabChild);
+
+ static void TransferablesToIPCTransferables(nsIArray* aTransferables,
+ nsTArray<mozilla::dom::IPCDataTransfer>& aIPC,
+ bool aInSyncMessage,
+ mozilla::dom::nsIContentChild* aChild,
+ mozilla::dom::nsIContentParent* aParent);
+
+ static void TransferableToIPCTransferable(nsITransferable* aTransferable,
+ mozilla::dom::IPCDataTransfer* aIPCDataTransfer,
+ bool aInSyncMessage,
+ mozilla::dom::nsIContentChild* aChild,
+ mozilla::dom::nsIContentParent* aParent);
+
+ /*
+ * Get the pixel data from the given source surface and return it as a buffer.
+ * The length and stride will be assigned from the surface.
+ */
+ static mozilla::UniquePtr<char[]> GetSurfaceData(
+ mozilla::NotNull<mozilla::gfx::DataSourceSurface*> aSurface,
+ size_t* aLength, int32_t* aStride);
+
+ /*
+ * Get the pixel data from the given source surface and fill it in Shmem.
+ * The length and stride will be assigned from the surface.
+ */
+ static void GetSurfaceData(mozilla::gfx::DataSourceSurface* aSurface,
+ size_t* aLength, int32_t* aStride,
+ mozilla::ipc::IShmemAllocator* aAlloc,
+ mozilla::ipc::Shmem *aOutShmem);
+
+ // Helpers shared by the implementations of nsContentUtils methods and
+ // nsIDOMWindowUtils methods.
+ static mozilla::Modifiers GetWidgetModifiers(int32_t aModifiers);
+ static nsIWidget* GetWidget(nsIPresShell* 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,
+ nsIPresShell** aPresShell);
+
+ /**
+ * Synthesize a key event to the given widget
+ * (see nsIDOMWindowUtils.sendKeyEvent).
+ */
+ static nsresult SendKeyEvent(nsIWidget* aWidget,
+ const nsAString& aType,
+ int32_t aKeyCode,
+ int32_t aCharCode,
+ int32_t aModifiers,
+ uint32_t aAdditionalFlags,
+ bool* aDefaultActionTaken);
+
+ /**
+ * Synthesize a mouse event to the given widget
+ * (see nsIDOMWindowUtils.sendMouseEvent).
+ */
+ static nsresult SendMouseEvent(nsCOMPtr<nsIPresShell> 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,
+ bool aToWindow,
+ bool *aPreventDefault,
+ bool aIsDOMEventSynthesized,
+ bool aIsWidgetEventSynthesized);
+
+ static void FirePageShowEvent(nsIDocShellTreeItem* aItem,
+ mozilla::dom::EventTarget* aChromeEventHandler,
+ bool aFireIfShowing);
+
+ static void FirePageHideEvent(nsIDocShellTreeItem* aItem,
+ mozilla::dom::EventTarget* aChromeEventHandler);
+
+ static already_AddRefed<nsPIWindowRoot> GetWindowRoot(nsIDocument* aDoc);
+
+ /*
+ * Implements step 3.1 and 3.3 of the Determine request's Referrer algorithm
+ * from the Referrer Policy specification.
+ *
+ * The referrer policy of the document is applied by Necko when using
+ * channels.
+ *
+ * For documents representing an iframe srcdoc attribute, the document sets
+ * its own URI correctly, so this method simply uses the document's original
+ * or current URI as appropriate.
+ *
+ * aDoc may be null.
+ *
+ * https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrer
+ */
+ static nsresult SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
+ nsIDocument* aDoc,
+ nsIHttpChannel* aChannel,
+ mozilla::net::ReferrerPolicy aReferrerPolicy);
+
+ /*
+ * Parse a referrer policy from a Referrer-Policy header
+ * https://www.w3.org/TR/referrer-policy/#parse-referrer-policy-from-header
+ *
+ * @param aHeader the response's Referrer-Policy header to parse
+ * @return referrer policy from the response header.
+ */
+ static mozilla::net::ReferrerPolicy GetReferrerPolicyFromHeader(const nsAString& aHeader);
+
+ static bool PushEnabled(JSContext* aCx, JSObject* aObj);
+
+ static bool IsNonSubresourceRequest(nsIChannel* aChannel);
+
+ static uint32_t CookiesBehavior()
+ {
+ return sCookiesBehavior;
+ }
+
+ // The order of these entries matters, as we use std::min for total ordering
+ // of permissions. Private Browsing is considered to be more limiting
+ // then session scoping
+ enum class StorageAccess {
+ // Don't allow access to the storage
+ eDeny = 0,
+ // Allow access to the storage, but only if it is secure to do so in a
+ // private browsing context.
+ ePrivateBrowsing = 1,
+ // Allow access to the storage, but only persist it for the current session
+ eSessionScoped = 2,
+ // Allow access to the storage
+ eAllow = 3,
+ };
+
+ /*
+ * Checks if storage for the given window is permitted by a combination of
+ * the user's preferences, and whether the window is a third-party iframe.
+ *
+ * This logic is intended to be shared between the different forms of
+ * persistent storage which are available to web pages. Cookies don't use
+ * this logic, and security logic related to them must be updated separately.
+ */
+ static StorageAccess StorageAllowedForWindow(nsPIDOMWindowInner* aWindow);
+
+ /*
+ * Checks if storage for the given principal is permitted by the user's
+ * preferences. The caller is assumed to not be a third-party iframe.
+ * (if that is possible, the caller should use StorageAllowedForWindow)
+ */
+ static StorageAccess StorageAllowedForPrincipal(nsIPrincipal* aPrincipal);
+
+ /*
+ * 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);
+
+ /*
+ * Return the associated presentation URL of the presented content.
+ * Will return empty string if the docshell is not in a presented content.
+ */
+ static void GetPresentationURL(nsIDocShell* aDocShell, nsAString& aPresentationUrl);
+
+ /*
+ * 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(nsIDocument* aDocument);
+
+ /**
+ * Looking up a custom element definition.
+ * https://html.spec.whatwg.org/#look-up-a-custom-element-definition
+ */
+ static mozilla::dom::CustomElementDefinition*
+ LookupCustomElementDefinition(nsIDocument* aDoc,
+ const nsAString& aLocalName,
+ uint32_t aNameSpaceID,
+ const nsAString* aIs = nullptr);
+
+ static void SetupCustomElement(Element* aElement,
+ const nsAString* aTypeExtension = nullptr);
+
+ static void EnqueueLifecycleCallback(nsIDocument* aDoc,
+ nsIDocument::ElementCallbackType aType,
+ Element* aCustomElement,
+ mozilla::dom::LifecycleCallbackArgs* aArgs = nullptr,
+ mozilla::dom::CustomElementDefinition* aDefinition = nullptr);
+
+ static void GetCustomPrototype(nsIDocument* aDoc,
+ int32_t aNamespaceID,
+ nsIAtom* aAtom,
+ JS::MutableHandle<JSObject*> prototype);
+
+ static bool AttemptLargeAllocationLoad(nsIHttpChannel* aChannel);
+
+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);
+
+ static nsresult DispatchEvent(nsIDocument* aDoc,
+ nsISupports* aTarget,
+ const nsAString& aEventName,
+ bool aCanBubble,
+ bool aCancelable,
+ bool aTrusted,
+ bool *aDefaultAction = nullptr,
+ bool aOnlyChromeDispatch = false);
+
+ static void InitializeModifierStrings();
+
+ static void DropFragmentParsers();
+
+ static bool MatchClassNames(nsIContent* aContent, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData);
+ static void DestroyClassNameArray(void* aData);
+ static void* AllocClassMatchingInfo(nsINode* aRootNode,
+ const nsString* aClasses);
+
+ // Fills in aInfo with the tokens from the supplied autocomplete attribute.
+ static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
+ mozilla::dom::AutocompleteInfo& aInfo);
+
+ static bool CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
+ CallOnRemoteChildFunction aCallback,
+ void* aArg);
+
+ /*
+ * Checks if storage for a given principal is permitted by the user's
+ * preferences. If aWindow is non-null, its principal must be passed as
+ * aPrincipal, and the third-party iframe and sandboxing status of the window
+ * are also checked.
+ *
+ * Used in the implementation of StorageAllowedForWindow and
+ * StorageAllowedForPrincipal.
+ */
+ static StorageAccess InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
+ nsPIDOMWindowInner* aWindow);
+
+ static nsIXPConnect *sXPConnect;
+
+ static nsIScriptSecurityManager *sSecurityManager;
+ static nsIPrincipal *sSystemPrincipal;
+ static nsIPrincipal *sNullSubjectPrincipal;
+
+ static nsIParserService *sParserService;
+
+ static nsNameSpaceManager *sNameSpaceManager;
+
+ static nsIIOService *sIOService;
+ static nsIUUIDGenerator *sUUIDGenerator;
+
+ static nsIConsoleService* sConsoleService;
+
+ static nsDataHashtable<nsISupportsHashKey, EventNameMapping>* sAtomEventTable;
+ static nsDataHashtable<nsStringHashKey, EventNameMapping>* sStringEventTable;
+ static nsCOMArray<nsIAtom>* sUserDefinedEvents;
+
+ static nsIStringBundleService* sStringBundleService;
+ static nsIStringBundle* sStringBundles[PropertiesFile_COUNT];
+
+ static nsIContentPolicy* sContentPolicyService;
+ static bool sTriedToGetContentPolicy;
+
+ static nsILineBreaker* sLineBreaker;
+ static nsIWordBreaker* sWordBreaker;
+
+ static nsIBidiKeyboard* sBidiKeyboard;
+
+ static bool sInitialized;
+ static uint32_t sScriptBlockerCount;
+ static uint32_t sDOMNodeRemovedSuppressCount;
+ static uint32_t sMicroTaskLevel;
+ // 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;
+ static bool sAllowXULXBL_for_file;
+ static bool sIsFullScreenApiEnabled;
+ static bool sIsUnprefixedFullscreenApiEnabled;
+ static bool sTrustedFullScreenOnly;
+ static bool sIsCutCopyAllowed;
+ static uint32_t sHandlingInputTimeout;
+ static bool sIsPerformanceTimingEnabled;
+ static bool sIsResourceTimingEnabled;
+ static bool sIsUserTimingLoggingEnabled;
+ static bool sIsFrameTimingPrefEnabled;
+ static bool sIsExperimentalAutocompleteEnabled;
+ static bool sEncodeDecodeURLHash;
+ static bool sGettersDecodeURLHash;
+ static bool sPrivacyResistFingerprinting;
+ static bool sSendPerformanceTimingNotifications;
+ static bool sUseActivityCursor;
+ static uint32_t sCookiesLifetimePolicy;
+ static uint32_t sCookiesBehavior;
+
+ static nsHtml5StringParser* sHTMLFragmentParser;
+ static nsIParser* 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;
+
+#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
+ static bool sDOMWindowDumpEnabled;
+#endif
+ static bool sDoNotTrackEnabled;
+ static mozilla::LazyLogModule sDOMDumpLog;
+};
+
+class MOZ_RAII nsAutoScriptBlocker {
+public:
+ explicit nsAutoScriptBlocker(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ nsContentUtils::AddScriptBlocker();
+ }
+ ~nsAutoScriptBlocker() {
+ nsContentUtils::RemoveScriptBlocker();
+ }
+private:
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+class MOZ_STACK_CLASS nsAutoScriptBlockerSuppressNodeRemoved :
+ public nsAutoScriptBlocker {
+public:
+ nsAutoScriptBlockerSuppressNodeRemoved() {
+ ++nsContentUtils::sDOMNodeRemovedSuppressCount;
+ }
+ ~nsAutoScriptBlockerSuppressNodeRemoved() {
+ --nsContentUtils::sDOMNodeRemovedSuppressCount;
+ }
+};
+
+class MOZ_STACK_CLASS nsAutoMicroTask
+{
+public:
+ nsAutoMicroTask()
+ {
+ nsContentUtils::EnterMicroTask();
+ }
+ ~nsAutoMicroTask()
+ {
+ nsContentUtils::LeaveMicroTask();
+ }
+};
+
+namespace mozilla {
+namespace 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 dom
+} // namespace mozilla
+
+#define NS_INTERFACE_MAP_ENTRY_TEAROFF(_interface, _allocator) \
+ if (aIID.Equals(NS_GET_IID(_interface))) { \
+ foundInterface = static_cast<_interface *>(_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 (!mozilla::IsFinite(f)) { \
+ return (rv); \
+ }
+
+#define NS_ENSURE_FINITE2(f1, f2, rv) \
+ if (!mozilla::IsFinite((f1)+(f2))) { \
+ return (rv); \
+ }
+
+#define NS_ENSURE_FINITE4(f1, f2, f3, f4, rv) \
+ if (!mozilla::IsFinite((f1)+(f2)+(f3)+(f4))) { \
+ return (rv); \
+ }
+
+#define NS_ENSURE_FINITE5(f1, f2, f3, f4, f5, rv) \
+ if (!mozilla::IsFinite((f1)+(f2)+(f3)+(f4)+(f5))) { \
+ return (rv); \
+ }
+
+#define NS_ENSURE_FINITE6(f1, f2, f3, f4, f5, f6, rv) \
+ if (!mozilla::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 000000000..dfaf77a9e
--- /dev/null
+++ b/dom/base/nsCopySupport.cpp
@@ -0,0 +1,927 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIClipboard.h"
+#include "nsIFormControl.h"
+#include "nsISelection.h"
+#include "nsWidgetsCID.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIDOMRange.h"
+#include "nsRange.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "nsIPresShell.h"
+#include "nsFocusManager.h"
+#include "mozilla/dom/DataTransfer.h"
+
+#include "nsIDocShell.h"
+#include "nsIContentViewerEdit.h"
+#include "nsIClipboardDragDropHooks.h"
+#include "nsIClipboardDragDropHookList.h"
+#include "nsIClipboardHelper.h"
+#include "nsISelectionController.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMDocument.h"
+#include "nsIHTMLDocument.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+#include "nsIURI.h"
+#include "nsISimpleEnumerator.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 "nsIURL.h"
+#include "nsReadableUtils.h"
+#include "nsXULAppAPI.h"
+#endif
+
+#include "mozilla/ContentEvents.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/IntegerRange.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsresult NS_NewDomSelection(nsISelection **aDomSelection);
+
+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
+
+// Helper used for HTMLCopy and GetTransferableForSelection since both routines
+// share common code.
+static nsresult
+SelectionCopyHelper(nsISelection *aSel, nsIDocument *aDoc,
+ bool doPutOnClipboard, int16_t aClipboardID,
+ uint32_t aFlags, nsITransferable ** aTransferable)
+{
+ // Clear the output parameter for the transferable, if provided.
+ if (aTransferable) {
+ *aTransferable = nullptr;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDocumentEncoder> docEncoder;
+ docEncoder = do_CreateInstance(NS_HTMLCOPY_ENCODER_CONTRACTID);
+ NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
+
+ // 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(kUnicodeMime);
+
+ // Do the first and potentially trial encoding as preformatted and raw.
+ uint32_t flags = aFlags | nsIDocumentEncoder::OutputPreformatted
+ | nsIDocumentEncoder::OutputRaw
+ | nsIDocumentEncoder::OutputForPlainTextClipboardCopy;
+
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDoc);
+ NS_ASSERTION(domDoc, "Need a document");
+
+ rv = docEncoder->Init(domDoc, mimeType, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->SetSelection(aSel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // SetSelection set the mime type to text/plain if the selection is inside a
+ // text widget.
+ rv = docEncoder->GetMimeType(mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool selForcedTextPlain = mimeType.EqualsLiteral(kTextMime);
+
+ nsAutoString buf;
+ rv = docEncoder->EncodeToString(buf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->GetMimeType(mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!selForcedTextPlain && mimeType.EqualsLiteral(kTextMime)) {
+ // SetSelection and EncodeToString use this case to signal that text/plain
+ // was forced because the document is either not an nsIHTMLDocument or it's
+ // XHTML. We want to pretty print XHTML but not non-nsIHTMLDocuments.
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDoc);
+ if (!htmlDoc) {
+ selForcedTextPlain = true;
+ }
+ }
+
+ // The mime type is ultimately text/html if the encoder successfully encoded
+ // the selection as text/html.
+ bool encodedTextHTML = mimeType.EqualsLiteral(kHTMLMime);
+
+ // First, prepare the text/plain clipboard flavor.
+ nsAutoString textPlainBuf;
+ if (selForcedTextPlain) {
+ // Nothing to do. buf contains the final, preformatted, raw text/plain.
+ textPlainBuf.Assign(buf);
+ } else {
+ // Redo the encoding, but this time use pretty printing.
+ flags =
+ nsIDocumentEncoder::OutputSelectionOnly |
+ nsIDocumentEncoder::OutputAbsoluteLinks |
+ nsIDocumentEncoder::SkipInvisibleContent |
+ nsIDocumentEncoder::OutputDropInvisibleBreak |
+ (aFlags & (nsIDocumentEncoder::OutputNoScriptContent |
+ nsIDocumentEncoder::OutputRubyAnnotation));
+
+ mimeType.AssignLiteral(kTextMime);
+ rv = docEncoder->Init(domDoc, mimeType, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->SetSelection(aSel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->EncodeToString(textPlainBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Second, prepare the text/html flavor.
+ nsAutoString textHTMLBuf;
+ nsAutoString htmlParentsBuf;
+ nsAutoString htmlInfoBuf;
+ if (encodedTextHTML) {
+ // Redo the encoding, but this time use the passed-in flags.
+ // Don't allow wrapping of CJK strings.
+ mimeType.AssignLiteral(kHTMLMime);
+ rv = docEncoder->Init(domDoc, mimeType,
+ aFlags |
+ nsIDocumentEncoder::OutputDisallowLineBreaking);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->SetSelection(aSel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->EncodeToStringWithContext(htmlParentsBuf, htmlInfoBuf,
+ textHTMLBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Get the Clipboard
+ nsCOMPtr<nsIClipboard> clipboard;
+ if (doPutOnClipboard) {
+ clipboard = do_GetService(kCClipboardCID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ if ((doPutOnClipboard && clipboard) || aTransferable != nullptr) {
+ // Create a transferable for putting data on the Clipboard
+ nsCOMPtr<nsITransferable> trans = do_CreateInstance(kCTransferableCID);
+ if (trans) {
+ trans->Init(aDoc->GetLoadContext());
+ if (encodedTextHTML) {
+ // 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);
+ trans->SetConverter(htmlConverter);
+
+ if (!textHTMLBuf.IsEmpty()) {
+ // Add the html DataFlavor to the transferable
+ rv = AppendString(trans, textHTMLBuf, kHTMLMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Add the htmlcontext DataFlavor to the transferable
+ // Even if parents is empty string, this flavor should
+ // be attached to the transferable
+ rv = AppendString(trans, htmlParentsBuf, kHTMLContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!htmlInfoBuf.IsEmpty()) {
+ // Add the htmlinfo DataFlavor to the transferable
+ rv = AppendString(trans, htmlInfoBuf, kHTMLInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!textPlainBuf.IsEmpty()) {
+ // unicode text
+ // Add the unicode DataFlavor to the transferable
+ // If we didn't have this, then nsDataObj::GetData matches text/unicode against
+ // the kURLMime flavour which is not desirable (eg. when pasting into Notepad)
+ rv = AppendString(trans, textPlainBuf, kUnicodeMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Try and get source URI of the items that are being dragged
+ nsIURI *uri = aDoc->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(trans, shortcut, kURLPrivateMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ } else {
+ if (!textPlainBuf.IsEmpty()) {
+ // Add the unicode DataFlavor to the transferable
+ rv = AppendString(trans, textPlainBuf, kUnicodeMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (doPutOnClipboard && clipboard) {
+ bool actuallyPutOnClipboard = true;
+ nsCopySupport::DoHooks(aDoc, trans, &actuallyPutOnClipboard);
+
+ // put the transferable on the clipboard
+ if (actuallyPutOnClipboard)
+ clipboard->SetData(trans, nullptr, aClipboardID);
+ }
+
+ // Return the transferable to the caller if requested.
+ if (aTransferable != nullptr) {
+ trans.swap(*aTransferable);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsCopySupport::HTMLCopy(nsISelection* aSel, nsIDocument* aDoc,
+ int16_t aClipboardID, bool aWithRubyAnnotation)
+{
+ uint32_t flags = nsIDocumentEncoder::SkipInvisibleContent;
+ if (aWithRubyAnnotation) {
+ flags |= nsIDocumentEncoder::OutputRubyAnnotation;
+ }
+ return SelectionCopyHelper(aSel, aDoc, true, aClipboardID, flags, nullptr);
+}
+
+nsresult
+nsCopySupport::ClearSelectionCache()
+{
+ nsresult rv;
+ nsCOMPtr<nsIClipboard> clipboard = do_GetService(kCClipboardCID, &rv);
+ clipboard->EmptyClipboard(nsIClipboard::kSelectionCache);
+ return rv;
+}
+
+nsresult
+nsCopySupport::GetTransferableForSelection(nsISelection* aSel,
+ nsIDocument* aDoc,
+ nsITransferable** aTransferable)
+{
+ return SelectionCopyHelper(aSel, aDoc, false, 0,
+ nsIDocumentEncoder::SkipInvisibleContent,
+ aTransferable);
+}
+
+nsresult
+nsCopySupport::GetTransferableForNode(nsINode* aNode,
+ nsIDocument* aDoc,
+ nsITransferable** aTransferable)
+{
+ nsCOMPtr<nsISelection> selection;
+ // 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
+ nsresult rv = NS_NewDomSelection(getter_AddRefs(selection));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode);
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+ RefPtr<nsRange> range = new nsRange(aNode);
+ rv = range->SelectNode(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ErrorResult result;
+ selection->AsSelection()->AddRangeInternal(*range, aDoc, result);
+ rv = result.StealNSResult();
+ NS_ENSURE_SUCCESS(rv, rv);
+ // It's not the primary selection - so don't skip invisible content.
+ uint32_t flags = 0;
+ return SelectionCopyHelper(selection, aDoc, false, 0, flags,
+ aTransferable);
+}
+
+nsresult nsCopySupport::DoHooks(nsIDocument *aDoc, nsITransferable *aTrans,
+ bool *aDoPutOnClipboard)
+{
+ NS_ENSURE_ARG(aDoc);
+
+ *aDoPutOnClipboard = true;
+
+ nsCOMPtr<nsISupports> container = aDoc->GetContainer();
+ nsCOMPtr<nsIClipboardDragDropHookList> hookObj = do_GetInterface(container);
+ if (!hookObj) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ hookObj->GetHookEnumerator(getter_AddRefs(enumerator));
+ if (!enumerator) return NS_ERROR_FAILURE;
+
+ // the logic here should follow the behavior specified in
+ // nsIClipboardDragDropHooks.h
+
+ nsCOMPtr<nsIClipboardDragDropHooks> override;
+ nsCOMPtr<nsISupports> isupp;
+ bool hasMoreHooks = false;
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreHooks))
+ && hasMoreHooks)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(isupp));
+ if (NS_FAILED(rv)) break;
+ override = do_QueryInterface(isupp);
+ if (override)
+ {
+#ifdef DEBUG
+ nsresult hookResult =
+#endif
+ override->OnCopyOrDrag(nullptr, aTrans, aDoPutOnClipboard);
+ NS_ASSERTION(NS_SUCCEEDED(hookResult), "OnCopyOrDrag hook failed");
+ if (!*aDoPutOnClipboard)
+ break;
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsCopySupport::GetContents(const nsACString& aMimeType, uint32_t aFlags, nsISelection *aSel, nsIDocument *aDoc, nsAString& outdata)
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIDocumentEncoder> docEncoder;
+
+ nsAutoCString encoderContractID(NS_DOC_ENCODER_CONTRACTID_BASE);
+ encoderContractID.Append(aMimeType);
+
+ docEncoder = do_CreateInstance(encoderContractID.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);
+
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aDoc);
+ NS_ASSERTION(domDoc, "Need a document");
+
+ rv = docEncoder->Init(domDoc, 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), kUnicodeMime);
+ 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);
+
+#ifdef XP_WIN
+ rv = AppendImagePromise(trans, imgRequest, aImageElement);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+ nsCOMPtr<nsISupportsInterfacePointer>
+ imgPtr(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = imgPtr->SetData(image);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // copy the image data onto the transferable
+ rv = trans->SetTransferData(kNativeImageMime, imgPtr,
+ sizeof(nsISupports*));
+ 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.
+ bool selectionSupported;
+ rv = clipboard->SupportsSelectionClipboard(&selectionSupported);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // put the transferable on the clipboard
+ if (selectionSupported) {
+ 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,
+ aString.Length() * sizeof(char16_t));
+}
+
+static nsresult AppendDOMNode(nsITransferable *aTransferable,
+ nsINode *aDOMNode)
+{
+ nsresult rv;
+
+ // selializer
+ nsCOMPtr<nsIDocumentEncoder>
+ docEncoder(do_CreateInstance(NS_HTMLCOPY_ENCODER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get document for the encoder
+ nsCOMPtr<nsIDocument> 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).
+ DebugOnly<nsCOMPtr<nsIHTMLDocument>> htmlDoc =
+ nsCOMPtr<nsIHTMLDocument>(do_QueryInterface(document, &rv));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ NS_ENSURE_TRUE(document->IsHTMLDocument(), NS_OK);
+
+ // init encoder with document and node
+ rv = docEncoder->NativeInit(document, NS_LITERAL_STRING(kHTMLMime),
+ nsIDocumentEncoder::OutputAbsoluteLinks |
+ nsIDocumentEncoder::OutputEncodeW3CEntities);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = docEncoder->SetNativeNode(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);
+
+ uint32_t imageStatus;
+ rv = aImgRequest->GetImageStatus(&imageStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!(imageStatus & imgIRequest::STATUS_FRAME_COMPLETE) ||
+ (imageStatus & imgIRequest::STATUS_ERROR)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> node = do_QueryInterface(aImageElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Fix the file extension in the URL if necessary
+ nsCOMPtr<nsIMIMEService> mimeService =
+ do_GetService(NS_MIMESERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(mimeService, NS_OK);
+
+ nsCOMPtr<nsIURI> imgUri;
+ rv = aImgRequest->GetCurrentURI(getter_AddRefs(imgUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURL> imgUrl = do_QueryInterface(imgUri);
+ NS_ENSURE_TRUE(imgUrl, NS_OK);
+
+ nsAutoCString extension;
+ rv = imgUrl->GetFileExtension(extension);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsXPIDLCString mimeType;
+ rv = aImgRequest->GetMimeType(getter_Copies(mimeType));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
+ getter_AddRefs(mimeInfo));
+ NS_ENSURE_TRUE(mimeInfo, NS_OK);
+
+ nsAutoCString spec;
+ rv = imgUrl->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // pass out the image source string
+ nsString imageSourceString;
+ CopyUTF8toUTF16(spec, imageSourceString);
+
+ bool validExtension;
+ if (extension.IsEmpty() ||
+ NS_FAILED(mimeInfo->ExtensionExists(extension,
+ &validExtension)) ||
+ !validExtension) {
+ // Fix the file extension in the URL
+ rv = imgUrl->Clone(getter_AddRefs(imgUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ imgUrl = do_QueryInterface(imgUri);
+
+ nsAutoCString primaryExtension;
+ mimeInfo->GetPrimaryExtension(primaryExtension);
+
+ imgUrl->SetFileExtension(primaryExtension);
+ }
+
+ nsAutoCString fileName;
+ imgUrl->GetFileName(fileName);
+
+ NS_UnescapeURL(fileName);
+
+ // make the filename safe for the filesystem
+ fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
+
+ nsString imageDestFileName;
+ CopyUTF8toUTF16(fileName, imageDestFileName);
+
+ rv = AppendString(aTransferable, imageSourceString, kFilePromiseURLMime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AppendString(aTransferable, imageDestFileName, kFilePromiseDestFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aTransferable->SetRequestingPrincipal(node->NodePrincipal());
+
+ // add the dataless file promise flavor
+ return aTransferable->AddDataFlavor(kFilePromiseMime);
+}
+#endif // XP_WIN
+
+nsIContent*
+nsCopySupport::GetSelectionForCopy(nsIDocument* aDocument, nsISelection** aSelection)
+{
+ *aSelection = nullptr;
+
+ nsIPresShell* presShell = aDocument->GetShell();
+ if (!presShell)
+ return nullptr;
+
+ nsCOMPtr<nsIContent> focusedContent;
+ nsCOMPtr<nsISelectionController> selectionController =
+ presShell->GetSelectionControllerForFocusedContent(
+ getter_AddRefs(focusedContent));
+ if (!selectionController) {
+ return nullptr;
+ }
+
+ selectionController->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ aSelection);
+ return focusedContent;
+}
+
+bool
+nsCopySupport::CanCopy(nsIDocument* aDocument)
+{
+ if (!aDocument)
+ return false;
+
+ nsCOMPtr<nsISelection> sel;
+ GetSelectionForCopy(aDocument, getter_AddRefs(sel));
+ NS_ENSURE_TRUE(sel, false);
+
+ bool isCollapsed;
+ sel->GetIsCollapsed(&isCollapsed);
+ return !isCollapsed;
+}
+
+static bool
+IsInsideRuby(nsINode* aNode)
+{
+ for (; aNode; aNode = aNode->GetParent()) {
+ if (aNode->IsHTMLElement(nsGkAtoms::ruby)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool
+IsSelectionInsideRuby(nsISelection* aSelection)
+{
+ int32_t rangeCount;
+ nsresult rv = aSelection->GetRangeCount(&rangeCount);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ for (auto i : MakeRange(rangeCount)) {
+ nsCOMPtr<nsIDOMRange> range;
+ aSelection->GetRangeAt(i, getter_AddRefs(range));
+ nsCOMPtr<nsIDOMNode> node;
+ range->GetCommonAncestorContainer(getter_AddRefs(node));
+ nsCOMPtr<nsINode> n = do_QueryInterface(node);
+ if (!IsInsideRuby(n)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
+ int32_t aClipboardType,
+ nsIPresShell* aPresShell,
+ nsISelection* aSelection,
+ bool* aActionTaken)
+{
+ if (aActionTaken) {
+ *aActionTaken = false;
+ }
+
+ NS_ASSERTION(aEventMessage == eCut || aEventMessage == eCopy ||
+ aEventMessage == ePaste,
+ "Invalid clipboard event type");
+
+ nsCOMPtr<nsIPresShell> presShell = aPresShell;
+ if (!presShell)
+ return false;
+
+ nsCOMPtr<nsIDocument> doc = presShell->GetDocument();
+ if (!doc)
+ return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = doc->GetWindow();
+ if (!piWindow)
+ return false;
+
+ // if a selection was not supplied, try to find it
+ nsCOMPtr<nsIContent> content;
+ nsCOMPtr<nsISelection> sel = aSelection;
+ if (!sel)
+ content = GetSelectionForCopy(doc, getter_AddRefs(sel));
+
+ // retrieve the event target node from the start of the selection
+ nsresult rv;
+ if (sel) {
+ nsCOMPtr<nsIDOMRange> range;
+ rv = sel->GetRangeAt(0, getter_AddRefs(range));
+ if (NS_SUCCEEDED(rv) && range) {
+ nsCOMPtr<nsIDOMNode> startContainer;
+ range->GetStartContainer(getter_AddRefs(startContainer));
+ if (startContainer)
+ content = do_QueryInterface(startContainer);
+ }
+ }
+
+ // if no content node was set, just get the root
+ if (!content) {
+ content = doc->GetRootElement();
+ if (!content)
+ return false;
+ }
+
+ // It seems to be unsafe to fire an event handler during reflow (bug 393696)
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::WarnScriptWasIgnored(doc);
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell();
+ const bool chromeShell =
+ docShell && docShell->ItemType() == nsIDocShellTreeItem::typeChrome;
+
+ // next, fire the cut, copy or paste event
+ bool doDefault = true;
+ RefPtr<DataTransfer> clipboardData;
+ if (chromeShell || Preferences::GetBool("dom.event.clipboardevents.enabled", true)) {
+ clipboardData =
+ new DataTransfer(doc->GetScopeObject(), aEventMessage,
+ aEventMessage == ePaste, aClipboardType);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ InternalClipboardEvent evt(true, aEventMessage);
+ evt.mClipboardData = clipboardData;
+ EventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt,
+ nullptr, &status);
+ // If the event was cancelled, don't do the clipboard operation
+ doDefault = (status != nsEventStatus_eConsumeNoDefault);
+ }
+
+ // 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 (aEventMessage == ePaste) {
+ // Clear and mark the clipboardData as readonly. This prevents someone
+ // from reading the clipboard contents after the paste event has fired.
+ if (clipboardData) {
+ clipboardData->ClearAll();
+ clipboardData->SetReadOnly();
+ }
+
+ if (aActionTaken) {
+ *aActionTaken = true;
+ }
+ return doDefault;
+ }
+
+ // Update the presentation in case the event handler modified the selection,
+ // see bug 602231.
+ presShell->FlushPendingNotifications(Flush_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
+ nsCOMPtr<nsIContent> srcNode = content;
+ if (content->IsInNativeAnonymousSubtree()) {
+ srcNode = content->FindFirstNonChromeOnlyAccessContent();
+ }
+
+ // check if we are looking at a password input
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(srcNode);
+ if (formControl) {
+ if (formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+ return false;
+ }
+ }
+
+ // when cutting non-editable content, do nothing
+ // XXX this is probably the wrong editable flag to check
+ if (aEventMessage != eCut || content->IsEditable()) {
+ // get the data from the selection if any
+ bool isCollapsed;
+ sel->GetIsCollapsed(&isCollapsed);
+ if (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);
+ // call the copy code
+ rv = HTMLCopy(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.
+ clipboardData->GetMozItemCount(&count);
+ 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
+ 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(NS_LITERAL_STRING("clipboard"), 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 000000000..c03a16047
--- /dev/null
+++ b/dom/base/nsCopySupport.h
@@ -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/. */
+
+#ifndef nsCopySupport_h__
+#define nsCopySupport_h__
+
+#include "nsError.h"
+#include "nsIDocument.h"
+#include "mozilla/EventForwards.h"
+
+class nsINode;
+class nsISelection;
+class nsIDocument;
+class nsIImageLoadingContent;
+class nsIContent;
+class nsITransferable;
+class nsACString;
+class nsAString;
+class nsIPresShell;
+class nsILoadContext;
+
+class nsCopySupport
+{
+ // class of static helper functions for copy support
+ public:
+ static nsresult ClearSelectionCache();
+ static nsresult HTMLCopy(nsISelection *aSel, nsIDocument *aDoc,
+ int16_t aClipboardID, bool aWithRubyAnnotation);
+ static nsresult DoHooks(nsIDocument *aDoc, nsITransferable *aTrans,
+ bool *aDoPutOnClipboard);
+
+ // 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, nsISelection *aSel, nsIDocument *aDoc, nsAString& outdata);
+
+ static nsresult ImageCopy(nsIImageLoadingContent* aImageElement,
+ nsILoadContext* aLoadContext,
+ int32_t aCopyFlags);
+
+ // Get the selection as a transferable. Similar to HTMLCopy except does
+ // not deal with the clipboard.
+ static nsresult GetTransferableForSelection(nsISelection* aSelection,
+ nsIDocument* aDocument,
+ nsITransferable** aTransferable);
+
+ // Same as GetTransferableForSelection, but doesn't skip invisible content.
+ static nsresult GetTransferableForNode(nsINode* aNode,
+ nsIDocument* 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 nsIContent* GetSelectionForCopy(nsIDocument* aDocument,
+ nsISelection** aSelection);
+
+ /**
+ * Returns true if a copy operation is currently permitted based on the
+ * current focus and selection within the specified document.
+ */
+ static bool CanCopy(nsIDocument* 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.
+ */
+ static bool FireClipboardEvent(mozilla::EventMessage aEventMessage,
+ int32_t aClipboardType,
+ nsIPresShell* aPresShell,
+ nsISelection* aSelection,
+ bool* aActionTaken = nullptr);
+};
+
+#endif
diff --git a/dom/base/nsDOMAttributeMap.cpp b/dom/base/nsDOMAttributeMap.cpp
new file mode 100644
index 000000000..381f267cd
--- /dev/null
+++ b/dom/base/nsDOMAttributeMap.cpp
@@ -0,0 +1,527 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDocument.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_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 (auto iter = tmp->mAttributeCache.Iter(); !iter.Done(); iter.Next()) {
+ cb.NoteXPCOMChild(static_cast<nsINode*>(iter.Data().get()));
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMAttributeMap)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMAttributeMap)
+ if (tmp->IsBlack()) {
+ 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->IsBlackAndDoesNotNeedTracing(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMAttributeMap)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+// QueryInterface implementation for nsDOMAttributeMap
+NS_INTERFACE_TABLE_HEAD(nsDOMAttributeMap)
+ NS_INTERFACE_TABLE(nsDOMAttributeMap, nsIDOMMozNamedAttrMap)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMAttributeMap)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMAttributeMap)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMAttributeMap)
+
+nsresult
+nsDOMAttributeMap::SetOwnerDocument(nsIDocument* aDocument)
+{
+ for (auto iter = mAttributeCache.Iter(); !iter.Done(); iter.Next()) {
+ nsresult rv = iter.Data()->SetOwnerDocument(aDocument);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ }
+ return NS_OK;
+}
+
+void
+nsDOMAttributeMap::DropAttribute(int32_t aNamespaceID, nsIAtom* aLocalName)
+{
+ nsAttrKey attr(aNamespaceID, aLocalName);
+ Attr *node = mAttributeCache.GetWeak(attr);
+ if (node) {
+ // Break link to map
+ node->SetMap(nullptr);
+
+ // Remove from cache
+ mAttributeCache.Remove(attr);
+ }
+}
+
+Attr*
+nsDOMAttributeMap::GetAttribute(mozilla::dom::NodeInfo* aNodeInfo)
+{
+ NS_ASSERTION(aNodeInfo, "GetAttribute() called with aNodeInfo == nullptr!");
+
+ nsAttrKey attr(aNodeInfo->NamespaceID(), aNodeInfo->NameAtom());
+
+ Attr* node = mAttributeCache.GetWeak(attr);
+ if (!node) {
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ RefPtr<Attr> newAttr =
+ new Attr(this, ni.forget(), EmptyString());
+ mAttributeCache.Put(attr, newAttr);
+ node = newAttr;
+ }
+
+ return node;
+}
+
+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);
+}
+
+NS_IMETHODIMP
+nsDOMAttributeMap::GetNamedItem(const nsAString& aAttrName,
+ nsIDOMAttr** aAttribute)
+{
+ NS_ENSURE_ARG_POINTER(aAttribute);
+
+ NS_IF_ADDREF(*aAttribute = GetNamedItem(aAttrName));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMAttributeMap::SetNamedItem(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn)
+{
+ Attr* attribute = static_cast<Attr*>(aAttr);
+ NS_ENSURE_ARG(attribute);
+
+ ErrorResult rv;
+ *aReturn = SetNamedItemNS(*attribute, rv).take();
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsDOMAttributeMap::SetNamedItemNS(nsIDOMAttr* aAttr, nsIDOMAttr** aReturn)
+{
+ Attr* attribute = static_cast<Attr*>(aAttr);
+ NS_ENSURE_ARG(attribute);
+
+ ErrorResult rv;
+ *aReturn = SetNamedItemNS(*attribute, rv).take();
+ return rv.StealNSResult();
+}
+
+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();
+ nsIAtom* 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(),
+ nsIDOMNode::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.Put(attrkey, &aAttr);
+ aAttr.SetMap(this);
+
+ rv = mContent->SetAttr(ni->NamespaceID(), ni->NameAtom(),
+ ni->GetPrefixAtom(), value, true);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ DropAttribute(ni->NamespaceID(), ni->NameAtom());
+ }
+
+ 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();
+}
+
+NS_IMETHODIMP
+nsDOMAttributeMap::RemoveNamedItem(const nsAString& aName,
+ nsIDOMAttr** aReturn)
+{
+ NS_ENSURE_ARG_POINTER(aReturn);
+
+ ErrorResult rv;
+ *aReturn = RemoveNamedItem(aName, rv).take();
+ return rv.StealNSResult();
+}
+
+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(),
+ nsIDOMNode::ATTRIBUTE_NODE);
+ return GetAttribute(ni);
+}
+
+Attr*
+nsDOMAttributeMap::Item(uint32_t aIndex)
+{
+ bool dummy;
+ return IndexedGetter(aIndex, dummy);
+}
+
+NS_IMETHODIMP
+nsDOMAttributeMap::Item(uint32_t aIndex, nsIDOMAttr** aReturn)
+{
+ NS_IF_ADDREF(*aReturn = Item(aIndex));
+ return NS_OK;
+}
+
+uint32_t
+nsDOMAttributeMap::Length() const
+{
+ NS_ENSURE_TRUE(mContent, 0);
+
+ return mContent->GetAttrCount();
+}
+
+nsresult
+nsDOMAttributeMap::GetLength(uint32_t *aLength)
+{
+ NS_ENSURE_ARG_POINTER(aLength);
+ *aLength = Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMAttributeMap::GetNamedItemNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ nsIDOMAttr** aReturn)
+{
+ NS_IF_ADDREF(*aReturn = GetNamedItemNS(aNamespaceURI, aLocalName));
+ return NS_OK;
+}
+
+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 =
+ nsContentUtils::NameSpaceManager()->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();
+ nsIAtom* 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,
+ nsIDOMNode::ATTRIBUTE_NODE);
+
+ return ni.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDOMAttributeMap::RemoveNamedItemNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ nsIDOMAttr** aReturn)
+{
+ NS_ENSURE_ARG_POINTER(aReturn);
+ ErrorResult rv;
+ *aReturn = RemoveNamedItemNS(aNamespaceURI, aLocalName, rv).take();
+ return rv.StealNSResult();
+}
+
+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 (auto iter = mAttributeCache.ConstIter(); !iter.Done(); iter.Next()) {
+ n += aMallocSizeOf(iter.Data().get());
+ }
+
+ // NB: mContent is non-owning and thus not counted.
+ return n;
+}
+
+/* virtual */ JSObject*
+nsDOMAttributeMap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NamedNodeMapBinding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/dom/base/nsDOMAttributeMap.h b/dom/base/nsDOMAttributeMap.h
new file mode 100644
index 000000000..31eb701e3
--- /dev/null
+++ b/dom/base/nsDOMAttributeMap.h
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/dom/Attr.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDOMMozNamedAttrMap.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+class nsIAtom;
+class nsIDocument;
+
+/**
+ * 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, nsIAtom* aName)
+ : mNamespaceID(aNs), mLocalName(aName) {}
+
+ nsAttrKey(const nsAttrKey& aAttr)
+ : mNamespaceID(aAttr.mNamespaceID), mLocalName(aAttr.mLocalName) {}
+};
+
+/**
+ * PLDHashEntryHdr implementation for nsAttrKey.
+ */
+class nsAttrHashKey : public PLDHashEntryHdr
+{
+public:
+ typedef const nsAttrKey& KeyType;
+ typedef const nsAttrKey* KeyTypePointer;
+
+ explicit nsAttrHashKey(KeyTypePointer aKey) : mKey(*aKey) {}
+ nsAttrHashKey(const nsAttrHashKey& aCopy) : mKey(aCopy.mKey) {}
+ ~nsAttrHashKey() {}
+
+ 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;
+};
+
+// Helper class that implements the nsIDOMMozNamedAttrMap interface.
+class nsDOMAttributeMap final : public nsIDOMMozNamedAttrMap
+ , public nsWrapperCache
+{
+public:
+ typedef mozilla::dom::Attr Attr;
+ typedef mozilla::dom::Element Element;
+ typedef mozilla::ErrorResult ErrorResult;
+
+ explicit nsDOMAttributeMap(Element *aContent);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(nsDOMAttributeMap)
+
+ // nsIDOMMozNamedAttrMap interface
+ NS_DECL_NSIDOMMOZNAMEDATTRMAP
+
+ void DropReference();
+
+ Element* GetContent()
+ {
+ return mContent;
+ }
+
+ /**
+ * Called when mContent is moved into a new document.
+ * Updates the nodeinfos of all owned nodes.
+ */
+ nsresult SetOwnerDocument(nsIDocument* aDocument);
+
+ /**
+ * Drop an attribute from the map's cache (does not remove the attribute
+ * from the node!)
+ */
+ void DropAttribute(int32_t aNamespaceID, nsIAtom* 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;
+
+ typedef nsRefPtrHashtable<nsAttrHashKey, Attr> AttrCache;
+
+ static void BlastSubtreeToPieces(nsINode *aNode);
+
+ Element* GetParentObject() const
+ {
+ return mContent;
+ }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // 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);
+};
+
+// XXX khuey yes this is crazy. The bindings code needs to see this include,
+// but if we pull it in at the top of the file we get a circular include
+// problem.
+#include "mozilla/dom/Element.h"
+
+#endif /* nsDOMAttributeMap_h */
diff --git a/dom/base/nsDOMCID.h b/dom/base/nsDOMCID.h
new file mode 100644
index 000000000..97dffb492
--- /dev/null
+++ b/dom/base/nsDOMCID.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+#include "nsIFactory.h"
+#include "nsIComponentManager.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} }
+
+//The dom cannot provide the crypto or pkcs11 classes that
+//were used in older days, so if someone wants to provide
+//the service they must implement an object and give it
+//this class ID
+#define NS_CRYPTO_CONTRACTID "@mozilla.org/security/crypto;1"
+#define NS_PKCS11_CONTRACTID "@mozilla.org/security/pkcs11;1"
+
+#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 000000000..0ee7c305f
--- /dev/null
+++ b/dom/base/nsDOMCaretPosition.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 "nsDOMCaretPosition.h"
+
+#include "mozilla/dom/CaretPositionBinding.h"
+#include "mozilla/dom/DOMRect.h"
+#include "nsRange.h"
+
+using namespace mozilla::dom;
+
+nsDOMCaretPosition::nsDOMCaretPosition(nsINode* aNode, uint32_t aOffset)
+ : mOffset(aOffset), mOffsetNode(aNode), mAnonymousContentNode(nullptr)
+{
+}
+
+nsDOMCaretPosition::~nsDOMCaretPosition()
+{
+}
+
+nsINode* nsDOMCaretPosition::GetOffsetNode() const
+{
+ return mOffsetNode;
+}
+
+already_AddRefed<DOMRect>
+nsDOMCaretPosition::GetClientRect() const
+{
+ if (!mOffsetNode) {
+ return nullptr;
+ }
+
+ RefPtr<DOMRect> rect;
+ RefPtr<nsRange> domRange;
+ nsCOMPtr<nsINode> node;
+
+ if (mAnonymousContentNode) {
+ node = mAnonymousContentNode;
+ } else {
+ node = mOffsetNode;
+ }
+
+ nsresult creationRv = nsRange::CreateRange(node, mOffset, node,
+ mOffset,
+ getter_AddRefs<nsRange>(domRange));
+ if (!NS_SUCCEEDED(creationRv)) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(domRange, "unable to retrieve valid dom range from CaretPosition");
+
+ rect = domRange->GetBoundingClientRect(false);
+
+ return rect.forget();
+}
+
+JSObject*
+nsDOMCaretPosition::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::CaretPositionBinding::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 000000000..f11768b2d
--- /dev/null
+++ b/dom/base/nsDOMCaretPosition.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/. */
+
+#ifndef nsDOMCaretPosition_h
+#define nsDOMCaretPosition_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "nsINode.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+class DOMRect;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * 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
+{
+ typedef mozilla::dom::DOMRect DOMRect;
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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();
+ }
+
+ virtual JSObject* WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+ override final;
+
+protected:
+ virtual ~nsDOMCaretPosition();
+
+ uint32_t mOffset;
+ nsCOMPtr<nsINode> mOffsetNode;
+ nsCOMPtr<nsINode> mAnonymousContentNode;
+};
+#endif
+
diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp
new file mode 100644
index 000000000..d125e5ad1
--- /dev/null
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -0,0 +1,2179 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+
+#ifdef XP_WIN
+#undef GetClassName
+#endif
+
+// JavaScript includes
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "WrapperFactory.h"
+#include "AccessCheck.h"
+#include "XrayWrapper.h"
+
+#include "xpcpublic.h"
+#include "xpcprivate.h"
+#include "xpc_make_class.h"
+#include "XPCWrapper.h"
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/RegisterBindings.h"
+
+#include "nscore.h"
+#include "nsDOMClassInfo.h"
+#include "nsIDOMClassInfo.h"
+#include "nsCRT.h"
+#include "nsCRTGlue.h"
+#include "nsICategoryManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsXPCOM.h"
+#include "nsISimpleEnumerator.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIXPConnect.h"
+#include "xptcall.h"
+#include "nsTArray.h"
+
+// General helper includes
+#include "nsGlobalWindow.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsContentUtils.h"
+#include "nsIDOMGlobalPropertyInitializer.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Telemetry.h"
+
+// Window scriptable helper includes
+#include "nsScriptNameSpaceManager.h"
+
+// DOM base includes
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMConstructor.h"
+
+// DOM core includes
+#include "nsError.h"
+#include "nsIDOMXULButtonElement.h"
+#include "nsIDOMXULCheckboxElement.h"
+#include "nsIDOMXULPopupElement.h"
+
+// Event related includes
+#include "nsIDOMEventTarget.h"
+
+// CSS related includes
+#include "nsCSSRules.h"
+#include "nsIDOMCSSRule.h"
+#include "nsMemory.h"
+
+// includes needed for the prototype chain interfaces
+#include "nsIDOMCSSKeyframeRule.h"
+#include "nsIDOMCSSKeyframesRule.h"
+#include "nsIDOMCSSImportRule.h"
+#include "nsIDOMCSSMediaRule.h"
+#include "nsIDOMCSSFontFaceRule.h"
+#include "nsIDOMCSSMozDocumentRule.h"
+#include "nsIDOMCSSSupportsRule.h"
+#include "nsIDOMCSSCounterStyleRule.h"
+#include "nsIDOMCSSPageRule.h"
+#include "nsIDOMCSSStyleRule.h"
+#include "nsIDOMXULCommandDispatcher.h"
+#include "nsIControllers.h"
+#ifdef MOZ_XUL
+#include "nsITreeSelection.h"
+#include "nsITreeContentView.h"
+#include "nsITreeView.h"
+#include "nsIXULTemplateBuilder.h"
+#endif
+
+#include "nsIEventListenerService.h"
+#include "nsIMessageManager.h"
+
+#include "mozilla/dom/TouchEvent.h"
+
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/HTMLCollectionBinding.h"
+
+#include "nsDebug.h"
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/Likely.h"
+#include "nsIInterfaceInfoManager.h"
+
+#ifdef MOZ_TIME_MANAGER
+#include "TimeManager.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// NOTE: DEFAULT_SCRIPTABLE_FLAGS and DOM_DEFAULT_SCRIPTABLE_FLAGS
+// are defined in nsIDOMClassInfo.h.
+
+#define DOMCLASSINFO_STANDARD_FLAGS \
+ (nsIClassInfo::MAIN_THREAD_ONLY | \
+ nsIClassInfo::DOM_OBJECT | \
+ nsIClassInfo::SINGLETON_CLASSINFO)
+
+
+#ifdef DEBUG
+#define NS_DEFINE_CLASSINFO_DATA_DEBUG(_class) \
+ eDOMClassInfo_##_class##_id,
+#else
+#define NS_DEFINE_CLASSINFO_DATA_DEBUG(_class) \
+ // nothing
+#endif
+
+#define NS_DEFINE_CLASSINFO_DATA_HELPER(_class, _helper, _flags, \
+ _chromeOnly, _allowXBL) \
+ { #_class, \
+ nullptr, \
+ XPC_MAKE_CLASS_OPS(_flags), \
+ XPC_MAKE_CLASS(#_class, _flags, \
+ &sClassInfoData[eDOMClassInfo_##_class##_id].mClassOps), \
+ _helper::doCreate, \
+ nullptr, \
+ nullptr, \
+ nullptr, \
+ _flags, \
+ true, \
+ _chromeOnly, \
+ _allowXBL, \
+ false, \
+ NS_DEFINE_CLASSINFO_DATA_DEBUG(_class) \
+ },
+
+#define NS_DEFINE_CLASSINFO_DATA(_class, _helper, _flags) \
+ NS_DEFINE_CLASSINFO_DATA_HELPER(_class, _helper, _flags, false, false)
+
+#define NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(_class, _helper, _flags) \
+ NS_DEFINE_CLASSINFO_DATA_HELPER(_class, _helper, _flags, true, false)
+
+#define NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(_class, _helper, _flags) \
+ NS_DEFINE_CLASSINFO_DATA_HELPER(_class, _helper, _flags, true, true)
+
+
+// This list of NS_DEFINE_CLASSINFO_DATA macros is what gives the DOM
+// classes their correct behavior when used through XPConnect. The
+// arguments that are passed to NS_DEFINE_CLASSINFO_DATA are
+//
+// 1. Class name as it should appear in JavaScript, this name is also
+// used to find the id of the class in nsDOMClassInfo
+// (i.e. e<classname>_id)
+// 2. Scriptable helper class
+// 3. nsIClassInfo/nsIXPCScriptable flags (i.e. for GetScriptableFlags)
+
+static nsDOMClassInfoData sClassInfoData[] = {
+ // Base classes
+
+ NS_DEFINE_CLASSINFO_DATA(DOMPrototype, nsDOMConstructorSH,
+ DOM_BASE_SCRIPTABLE_FLAGS |
+ nsIXPCScriptable::WANT_PRECREATE |
+ nsIXPCScriptable::WANT_RESOLVE |
+ nsIXPCScriptable::WANT_HASINSTANCE |
+ nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE)
+ NS_DEFINE_CLASSINFO_DATA(DOMConstructor, nsDOMConstructorSH,
+ DOM_BASE_SCRIPTABLE_FLAGS |
+ nsIXPCScriptable::WANT_PRECREATE |
+ nsIXPCScriptable::WANT_RESOLVE |
+ nsIXPCScriptable::WANT_HASINSTANCE |
+ nsIXPCScriptable::WANT_CALL |
+ nsIXPCScriptable::WANT_CONSTRUCT |
+ nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE)
+
+ // Misc Core related classes
+
+ // CSS classes
+ NS_DEFINE_CLASSINFO_DATA(CSSStyleRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CLASSINFO_DATA(CSSImportRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CLASSINFO_DATA(CSSMediaRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CLASSINFO_DATA(CSSNameSpaceRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+ // XUL classes
+#ifdef MOZ_XUL
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULCommandDispatcher, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+#endif
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULControllers, nsNonDOMObjectSH,
+ DEFAULT_SCRIPTABLE_FLAGS)
+#ifdef MOZ_XUL
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(TreeSelection, nsDOMGenericSH,
+ DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(TreeContentView, nsDOMGenericSH,
+ DEFAULT_SCRIPTABLE_FLAGS)
+#endif
+
+#ifdef MOZ_XUL
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULTemplateBuilder, nsDOMGenericSH,
+ DEFAULT_SCRIPTABLE_FLAGS)
+
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULTreeBuilder, nsDOMGenericSH,
+ DEFAULT_SCRIPTABLE_FLAGS)
+#endif
+
+ NS_DEFINE_CLASSINFO_DATA(CSSMozDocumentRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+ NS_DEFINE_CLASSINFO_DATA(CSSSupportsRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+ NS_DEFINE_CLASSINFO_DATA(CSSFontFaceRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+ NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ContentFrameMessageManager,
+ nsMessageManagerSH<nsEventTargetSH>,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS |
+ nsIXPCScriptable::WANT_ENUMERATE |
+ nsIXPCScriptable::IS_GLOBAL_OBJECT)
+ NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ContentProcessMessageManager,
+ nsMessageManagerSH<nsDOMGenericSH>,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS |
+ nsIXPCScriptable::WANT_ENUMERATE |
+ nsIXPCScriptable::IS_GLOBAL_OBJECT)
+ NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ChromeMessageBroadcaster, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ChromeMessageSender, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+
+ NS_DEFINE_CLASSINFO_DATA(CSSKeyframeRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CLASSINFO_DATA(CSSKeyframesRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+ NS_DEFINE_CLASSINFO_DATA(CSSCounterStyleRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+ NS_DEFINE_CLASSINFO_DATA(CSSPageRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+ NS_DEFINE_CLASSINFO_DATA(CSSFontFeatureValuesRule, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULControlElement, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULLabeledControlElement, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULButtonElement, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULCheckboxElement, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+ NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULPopupElement, nsDOMGenericSH,
+ DOM_DEFAULT_SCRIPTABLE_FLAGS)
+};
+
+nsIXPConnect *nsDOMClassInfo::sXPConnect = nullptr;
+bool nsDOMClassInfo::sIsInitialized = false;
+
+
+jsid nsDOMClassInfo::sConstructor_id = JSID_VOID;
+jsid nsDOMClassInfo::sWrappedJSObject_id = JSID_VOID;
+
+static const JSClass *sObjectClass = nullptr;
+
+/**
+ * Set our JSClass pointer for the Object class
+ */
+static void
+FindObjectClass(JSContext* cx, JSObject* aGlobalObject)
+{
+ NS_ASSERTION(!sObjectClass,
+ "Double set of sObjectClass");
+ JS::Rooted<JSObject*> obj(cx), proto(cx, aGlobalObject);
+ do {
+ obj = proto;
+ js::GetObjectProto(cx, obj, &proto);
+ } while (proto);
+
+ sObjectClass = js::GetObjectJSClass(obj);
+}
+
+// Helper to handle torn-down inner windows.
+static inline nsresult
+SetParentToWindow(nsGlobalWindow *win, JSObject **parent)
+{
+ MOZ_ASSERT(win);
+ MOZ_ASSERT(win->IsInnerWindow());
+ *parent = win->FastGetGlobalJSObject();
+
+ if (MOZ_UNLIKELY(!*parent)) {
+ // The inner window has been torn down. The scope is dying, so don't create
+ // any new wrappers.
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// static
+
+nsISupports *
+nsDOMClassInfo::GetNative(nsIXPConnectWrappedNative *wrapper, JSObject *obj)
+{
+ return wrapper ? wrapper->Native() : static_cast<nsISupports*>(js::GetObjectPrivate(obj));
+}
+
+nsresult
+nsDOMClassInfo::DefineStaticJSVals()
+{
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::UnprivilegedJunkScope())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+
+#define SET_JSID_TO_STRING(_id, _cx, _str) \
+ if (JSString *str = ::JS_AtomizeAndPinString(_cx, _str)) \
+ _id = INTERNED_STRING_TO_JSID(_cx, str); \
+ else \
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ SET_JSID_TO_STRING(sConstructor_id, cx, "constructor");
+ SET_JSID_TO_STRING(sWrappedJSObject_id, cx, "wrappedJSObject");
+
+ return NS_OK;
+}
+
+// static
+bool
+nsDOMClassInfo::ObjectIsNativeWrapper(JSContext* cx, JSObject* obj)
+{
+ return xpc::WrapperFactory::IsXrayWrapper(obj) &&
+ xpc::AccessCheck::wrapperSubsumes(obj);
+}
+
+nsDOMClassInfo::nsDOMClassInfo(nsDOMClassInfoData* aData) : mData(aData)
+{
+}
+
+NS_IMPL_ADDREF(nsDOMClassInfo)
+NS_IMPL_RELEASE(nsDOMClassInfo)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMClassInfo)
+ if (aIID.Equals(NS_GET_IID(nsXPCClassInfo)))
+ foundInterface = static_cast<nsIClassInfo*>(
+ static_cast<nsXPCClassInfo*>(this));
+ else
+ NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
+ NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIClassInfo)
+NS_INTERFACE_MAP_END
+
+
+static const JSClass sDOMConstructorProtoClass = {
+ "DOM Constructor.prototype", 0
+};
+
+
+static const char *
+CutPrefix(const char *aName) {
+ static const char prefix_nsIDOM[] = "nsIDOM";
+ static const char prefix_nsI[] = "nsI";
+
+ if (strncmp(aName, prefix_nsIDOM, sizeof(prefix_nsIDOM) - 1) == 0) {
+ return aName + sizeof(prefix_nsIDOM) - 1;
+ }
+
+ if (strncmp(aName, prefix_nsI, sizeof(prefix_nsI) - 1) == 0) {
+ return aName + sizeof(prefix_nsI) - 1;
+ }
+
+ return aName;
+}
+
+// static
+nsresult
+nsDOMClassInfo::RegisterClassProtos(int32_t aClassInfoID)
+{
+ nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
+ NS_ENSURE_TRUE(nameSpaceManager, NS_ERROR_NOT_INITIALIZED);
+ bool found_old;
+
+ const nsIID *primary_iid = sClassInfoData[aClassInfoID].mProtoChainInterface;
+
+ if (!primary_iid || primary_iid == &NS_GET_IID(nsISupports)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceInfoManager>
+ iim(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(iim, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIInterfaceInfo> if_info;
+ bool first = true;
+
+ iim->GetInfoForIID(primary_iid, getter_AddRefs(if_info));
+
+ while (if_info) {
+ const nsIID *iid = nullptr;
+
+ if_info->GetIIDShared(&iid);
+ NS_ENSURE_TRUE(iid, NS_ERROR_UNEXPECTED);
+
+ if (iid->Equals(NS_GET_IID(nsISupports))) {
+ break;
+ }
+
+ const char *name = nullptr;
+ if_info->GetNameShared(&name);
+ NS_ENSURE_TRUE(name, NS_ERROR_UNEXPECTED);
+
+ nameSpaceManager->RegisterClassProto(CutPrefix(name), iid, &found_old);
+
+ if (first) {
+ first = false;
+ } else if (found_old) {
+ break;
+ }
+
+ nsCOMPtr<nsIInterfaceInfo> tmp(if_info);
+ tmp->GetParent(getter_AddRefs(if_info));
+ }
+
+ return NS_OK;
+}
+
+#define _DOM_CLASSINFO_MAP_BEGIN(_class, _ifptr, _has_class_if) \
+ { \
+ nsDOMClassInfoData &d = sClassInfoData[eDOMClassInfo_##_class##_id]; \
+ d.mProtoChainInterface = _ifptr; \
+ d.mHasClassInterface = _has_class_if; \
+ static const nsIID *interface_list[] = {
+
+#define DOM_CLASSINFO_MAP_BEGIN(_class, _interface) \
+ _DOM_CLASSINFO_MAP_BEGIN(_class, &NS_GET_IID(_interface), true)
+
+#define DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(_class, _interface) \
+ _DOM_CLASSINFO_MAP_BEGIN(_class, &NS_GET_IID(_interface), false)
+
+#define DOM_CLASSINFO_MAP_ENTRY(_if) \
+ &NS_GET_IID(_if),
+
+#define DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(_if, _cond) \
+ (_cond) ? &NS_GET_IID(_if) : nullptr,
+
+#define DOM_CLASSINFO_MAP_END \
+ nullptr \
+ }; \
+ \
+ /* Compact the interface list */ \
+ size_t count = ArrayLength(interface_list); \
+ /* count is the number of array entries, which is one greater than the */ \
+ /* number of interfaces due to the terminating null */ \
+ for (size_t i = 0; i < count - 1; ++i) { \
+ if (!interface_list[i]) { \
+ /* We are moving the element at index i+1 and successors, */ \
+ /* so we must move only count - (i+1) elements total. */ \
+ memmove(&interface_list[i], &interface_list[i+1], \
+ sizeof(nsIID*) * (count - (i+1))); \
+ /* Make sure to examine the new pointer we ended up with at this */ \
+ /* slot, since it may be null too */ \
+ --i; \
+ --count; \
+ } \
+ } \
+ \
+ d.mInterfaces = interface_list; \
+ }
+
+nsresult
+nsDOMClassInfo::Init()
+{
+ /* Errors that can trigger early returns are done first,
+ otherwise nsDOMClassInfo is left in a half inited state. */
+ static_assert(sizeof(uintptr_t) == sizeof(void*),
+ "BAD! You'll need to adjust the size of uintptr_t to the "
+ "size of a pointer on your platform.");
+
+ NS_ENSURE_TRUE(!sIsInitialized, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
+ NS_ENSURE_TRUE(nameSpaceManager, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ADDREF(sXPConnect = nsContentUtils::XPConnect());
+
+ nsCOMPtr<nsIXPCFunctionThisTranslator> elt = new nsEventListenerThisTranslator();
+ sXPConnect->SetFunctionThisTranslator(NS_GET_IID(nsIDOMEventListener), elt);
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(DOMPrototype, nsIDOMDOMConstructor)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMConstructor)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(DOMConstructor, nsIDOMDOMConstructor)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMConstructor)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSStyleRule, nsIDOMCSSStyleRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSStyleRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSImportRule, nsIDOMCSSImportRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSImportRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSMediaRule, nsIDOMCSSMediaRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSMediaRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(CSSNameSpaceRule, nsIDOMCSSRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSRule)
+ DOM_CLASSINFO_MAP_END
+
+#ifdef MOZ_XUL
+ DOM_CLASSINFO_MAP_BEGIN(XULCommandDispatcher, nsIDOMXULCommandDispatcher)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULCommandDispatcher)
+ DOM_CLASSINFO_MAP_END
+#endif
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULControllers, nsIControllers)
+ DOM_CLASSINFO_MAP_ENTRY(nsIControllers)
+ DOM_CLASSINFO_MAP_END
+
+#ifdef MOZ_XUL
+ DOM_CLASSINFO_MAP_BEGIN(TreeSelection, nsITreeSelection)
+ DOM_CLASSINFO_MAP_ENTRY(nsITreeSelection)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(TreeContentView, nsITreeContentView)
+ DOM_CLASSINFO_MAP_ENTRY(nsITreeContentView)
+ DOM_CLASSINFO_MAP_ENTRY(nsITreeView)
+ DOM_CLASSINFO_MAP_END
+#endif
+
+#ifdef MOZ_XUL
+ DOM_CLASSINFO_MAP_BEGIN(XULTemplateBuilder, nsIXULTemplateBuilder)
+ DOM_CLASSINFO_MAP_ENTRY(nsIXULTemplateBuilder)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(XULTreeBuilder, nsIXULTreeBuilder)
+ DOM_CLASSINFO_MAP_ENTRY(nsIXULTreeBuilder)
+ DOM_CLASSINFO_MAP_ENTRY(nsIXULTemplateBuilder)
+ DOM_CLASSINFO_MAP_ENTRY(nsITreeView)
+ DOM_CLASSINFO_MAP_END
+#endif
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSMozDocumentRule, nsIDOMCSSMozDocumentRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSMozDocumentRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSSupportsRule, nsIDOMCSSSupportsRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSSupportsRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSFontFaceRule, nsIDOMCSSFontFaceRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSFontFaceRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(ContentFrameMessageManager, nsISupports)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+ DOM_CLASSINFO_MAP_ENTRY(nsIMessageListenerManager)
+ DOM_CLASSINFO_MAP_ENTRY(nsIMessageSender)
+ DOM_CLASSINFO_MAP_ENTRY(nsISyncMessageSender)
+ DOM_CLASSINFO_MAP_ENTRY(nsIContentFrameMessageManager)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(ContentProcessMessageManager, nsISupports)
+ DOM_CLASSINFO_MAP_ENTRY(nsIMessageListenerManager)
+ DOM_CLASSINFO_MAP_ENTRY(nsIMessageSender)
+ DOM_CLASSINFO_MAP_ENTRY(nsISyncMessageSender)
+ DOM_CLASSINFO_MAP_ENTRY(nsIContentProcessMessageManager)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(ChromeMessageBroadcaster, nsISupports)
+ DOM_CLASSINFO_MAP_ENTRY(nsIFrameScriptLoader)
+ DOM_CLASSINFO_MAP_ENTRY(nsIProcessScriptLoader)
+ DOM_CLASSINFO_MAP_ENTRY(nsIGlobalProcessScriptLoader)
+ DOM_CLASSINFO_MAP_ENTRY(nsIMessageListenerManager)
+ DOM_CLASSINFO_MAP_ENTRY(nsIMessageBroadcaster)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(ChromeMessageSender, nsISupports)
+ DOM_CLASSINFO_MAP_ENTRY(nsIProcessChecker)
+ DOM_CLASSINFO_MAP_ENTRY(nsIFrameScriptLoader)
+ DOM_CLASSINFO_MAP_ENTRY(nsIProcessScriptLoader)
+ DOM_CLASSINFO_MAP_ENTRY(nsIMessageListenerManager)
+ DOM_CLASSINFO_MAP_ENTRY(nsIMessageSender)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSKeyframeRule, nsIDOMCSSKeyframeRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSKeyframeRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSKeyframesRule, nsIDOMCSSKeyframesRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSKeyframesRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSCounterStyleRule, nsIDOMCSSCounterStyleRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSCounterStyleRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSPageRule, nsIDOMCSSPageRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSPageRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN(CSSFontFeatureValuesRule, nsIDOMCSSFontFeatureValuesRule)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSFontFeatureValuesRule)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULControlElement, nsIDOMXULControlElement)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULControlElement)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULLabeledControlElement, nsIDOMXULLabeledControlElement)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULLabeledControlElement)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULButtonElement, nsIDOMXULButtonElement)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULButtonElement)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULCheckboxElement, nsIDOMXULCheckboxElement)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULCheckboxElement)
+ DOM_CLASSINFO_MAP_END
+
+ DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULPopupElement, nsIDOMXULPopupElement)
+ DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULPopupElement)
+ DOM_CLASSINFO_MAP_END
+
+ static_assert(MOZ_ARRAY_LENGTH(sClassInfoData) == eDOMClassInfoIDCount,
+ "The number of items in sClassInfoData doesn't match the "
+ "number of nsIDOMClassInfo ID's, this is bad! Fix it!");
+
+#ifdef DEBUG
+ for (size_t i = 0; i < eDOMClassInfoIDCount; i++) {
+ if (!sClassInfoData[i].mConstructorFptr ||
+ sClassInfoData[i].mDebugID != i) {
+ MOZ_CRASH("Class info data out of sync, you forgot to update "
+ "nsDOMClassInfo.h and nsDOMClassInfo.cpp! Fix this, "
+ "mozilla will not work without this fixed!");
+ }
+ }
+
+ for (size_t i = 0; i < eDOMClassInfoIDCount; i++) {
+ if (!sClassInfoData[i].mInterfaces) {
+ MOZ_CRASH("Class info data without an interface list! Fix this, "
+ "mozilla will not work without this fixed!");
+ }
+ }
+#endif
+
+ // Initialize static JSString's
+ DefineStaticJSVals();
+
+ int32_t i;
+
+ for (i = 0; i < eDOMClassInfoIDCount; ++i) {
+ if (i == eDOMClassInfo_DOMPrototype_id) {
+ continue;
+ }
+
+ nsDOMClassInfoData& data = sClassInfoData[i];
+ nameSpaceManager->RegisterClassName(data.mName, i, data.mChromeOnly,
+ data.mAllowXBL, &data.mNameUTF16);
+ }
+
+ for (i = 0; i < eDOMClassInfoIDCount; ++i) {
+ RegisterClassProtos(i);
+ }
+
+ sIsInitialized = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetInterfaces(uint32_t *aCount, nsIID ***aArray)
+{
+ uint32_t count = 0;
+
+ while (mData->mInterfaces[count]) {
+ count++;
+ }
+
+ *aCount = count;
+
+ if (!count) {
+ *aArray = nullptr;
+
+ return NS_OK;
+ }
+
+ *aArray = static_cast<nsIID **>(moz_xmalloc(count * sizeof(nsIID *)));
+ NS_ENSURE_TRUE(*aArray, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t i;
+ for (i = 0; i < count; i++) {
+ nsIID *iid = static_cast<nsIID *>(nsMemory::Clone(mData->mInterfaces[i],
+ sizeof(nsIID)));
+
+ if (!iid) {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, *aArray);
+
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *((*aArray) + i) = iid;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ nsCOMPtr<nsIXPCScriptable> rval = this;
+ rval.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetContractID(char **aContractID)
+{
+ *aContractID = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetClassDescription(char **aClassDescription)
+{
+ return GetClassName(aClassDescription);
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetClassID(nsCID **aClassID)
+{
+ *aClassID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetClassIDNoAlloc(nsCID *aClassID)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetFlags(uint32_t *aFlags)
+{
+ *aFlags = DOMCLASSINFO_STANDARD_FLAGS;
+
+ return NS_OK;
+}
+
+// nsIXPCScriptable
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetClassName(char **aClassName)
+{
+ *aClassName = NS_strdup(mData->mName);
+
+ return NS_OK;
+}
+
+// virtual
+uint32_t
+nsDOMClassInfo::GetScriptableFlags()
+{
+ return mData->mScriptableFlags;
+}
+
+// virtual
+const js::Class*
+nsDOMClassInfo::GetClass()
+{
+ return &mData->mClass;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::PreCreate(nsISupports *nativeObj, JSContext *cx,
+ JSObject *globalObj, JSObject **parentObj)
+{
+ *parentObj = globalObj;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, jsid id, JS::Handle<JS::Value> val,
+ bool *_retval)
+{
+ NS_WARNING("nsDOMClassInfo::AddProperty Don't call me!");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::GetProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, jsid id, JS::Value *vp,
+ bool *_retval)
+{
+ NS_WARNING("nsDOMClassInfo::GetProperty Don't call me!");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::SetProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, jsid id, JS::Value *vp,
+ bool *_retval)
+{
+ NS_WARNING("nsDOMClassInfo::SetProperty Don't call me!");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::Enumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, bool *_retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::NewEnumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, JS::AutoIdVector &properties,
+ bool *_retval)
+{
+ NS_WARNING("nsDOMClassInfo::NewEnumerate Don't call me!");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::Resolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *aObj, jsid aId, bool *resolvedp, bool *_retval)
+{
+ JS::Rooted<JSObject*> obj(cx, aObj);
+ JS::Rooted<jsid> id(cx, aId);
+
+ if (id != sConstructor_id) {
+ *resolvedp = false;
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> global(cx, ::JS_GetGlobalForObject(cx, obj));
+
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ if (!JS_GetPropertyDescriptor(cx, global, mData->mName, &desc)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (desc.object() && !desc.hasGetterOrSetter() && desc.value().isObject()) {
+ // If val is not an (non-null) object there either is no
+ // constructor for this class, or someone messed with
+ // window.classname, just fall through and let the JS engine
+ // return the Object constructor.
+ if (!::JS_DefinePropertyById(cx, obj, id, desc.value(),
+ JSPROP_ENUMERATE,
+ JS_STUBGETTER, JS_STUBSETTER)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *resolvedp = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::Finalize(nsIXPConnectWrappedNative *wrapper, JSFreeOp *fop,
+ JSObject *obj)
+{
+ NS_WARNING("nsDOMClassInfo::Finalize Don't call me!");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::Call(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, const JS::CallArgs &args, bool *_retval)
+{
+ NS_WARNING("nsDOMClassInfo::Call Don't call me!");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::Construct(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, const JS::CallArgs &args,
+ bool *_retval)
+{
+ NS_WARNING("nsDOMClassInfo::Construct Don't call me!");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsDOMClassInfo::HasInstance(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, JS::Handle<JS::Value> val, bool *bp,
+ bool *_retval)
+{
+ NS_WARNING("nsDOMClassInfo::HasInstance Don't call me!");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+static nsresult
+ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx,
+ JS::Handle<JSObject*> obj, const char16_t *name,
+ const nsDOMClassInfoData *ci_data,
+ const nsGlobalNameStruct *name_struct,
+ nsScriptNameSpaceManager *nameSpaceManager,
+ JSObject *dot_prototype,
+ JS::MutableHandle<JS::PropertyDescriptor> ctorDesc);
+
+NS_IMETHODIMP
+nsDOMClassInfo::PostCreatePrototype(JSContext * cx, JSObject * aProto)
+{
+ JS::Rooted<JSObject*> proto(cx, aProto);
+
+ // This is called before any other location that requires
+ // sObjectClass, so compute it here. We assume that nobody has had a
+ // chance to monkey around with proto's prototype chain before this.
+ if (!sObjectClass) {
+ FindObjectClass(cx, proto);
+ NS_ASSERTION(sObjectClass && !strcmp(sObjectClass->name, "Object"),
+ "Incorrect object class!");
+ }
+
+#ifdef DEBUG
+ JS::Rooted<JSObject*> proto2(cx);
+ JS_GetPrototype(cx, proto, &proto2);
+ NS_ASSERTION(proto2 && JS_GetClass(proto2) == sObjectClass,
+ "Hmm, somebody did something evil?");
+#endif
+
+#ifdef DEBUG
+ if (mData->mHasClassInterface && mData->mProtoChainInterface &&
+ mData->mProtoChainInterface != &NS_GET_IID(nsISupports)) {
+ nsCOMPtr<nsIInterfaceInfoManager>
+ iim(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID));
+
+ if (iim) {
+ nsCOMPtr<nsIInterfaceInfo> if_info;
+ iim->GetInfoForIID(mData->mProtoChainInterface,
+ getter_AddRefs(if_info));
+
+ if (if_info) {
+ nsXPIDLCString name;
+ if_info->GetName(getter_Copies(name));
+ NS_ASSERTION(nsCRT::strcmp(CutPrefix(name), mData->mName) == 0,
+ "Class name and proto chain interface name mismatch!");
+ }
+ }
+ }
+#endif
+
+ // Make prototype delegation work correctly. Consider if a site sets
+ // HTMLElement.prototype.foopy = function () { ... } Now, calling
+ // document.body.foopy() needs to ensure that looking up foopy on
+ // document.body's prototype will find the right function.
+ JS::Rooted<JSObject*> global(cx, ::JS_GetGlobalForObject(cx, proto));
+
+ // Only do this if the global object is a window.
+ nsGlobalWindow* win;
+ if (NS_FAILED(UNWRAP_OBJECT(Window, &global, win))) {
+ // Not a window.
+ return NS_OK;
+ }
+
+ if (win->IsClosedOrClosing()) {
+ return NS_OK;
+ }
+
+ // Don't overwrite a property set by content.
+ bool contentDefinedProperty;
+ if (!::JS_AlreadyHasOwnUCProperty(cx, global, reinterpret_cast<const char16_t*>(mData->mNameUTF16),
+ NS_strlen(mData->mNameUTF16),
+ &contentDefinedProperty)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
+ NS_ENSURE_TRUE(nameSpaceManager, NS_OK);
+
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ nsresult rv = ResolvePrototype(sXPConnect, win, cx, global, mData->mNameUTF16,
+ mData, nullptr, nameSpaceManager, proto,
+ &desc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!contentDefinedProperty && desc.object() && !desc.value().isUndefined()) {
+ desc.attributesRef() |= JSPROP_RESOLVING;
+ if (!JS_DefineUCProperty(cx, global, mData->mNameUTF16,
+ NS_strlen(mData->mNameUTF16), desc)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsIClassInfo *
+NS_GetDOMClassInfoInstance(nsDOMClassInfoID aID)
+{
+ if (aID >= eDOMClassInfoIDCount) {
+ NS_ERROR("Bad ID!");
+
+ return nullptr;
+ }
+
+ nsresult rv = RegisterDOMNames();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ if (!sClassInfoData[aID].mCachedClassInfo) {
+ nsDOMClassInfoData& data = sClassInfoData[aID];
+
+ data.mCachedClassInfo = data.mConstructorFptr(&data);
+ NS_ENSURE_TRUE(data.mCachedClassInfo, nullptr);
+
+ NS_ADDREF(data.mCachedClassInfo);
+ }
+
+ return sClassInfoData[aID].mCachedClassInfo;
+}
+
+// static
+void
+nsDOMClassInfo::ShutDown()
+{
+ if (sClassInfoData[0].mConstructorFptr) {
+ uint32_t i;
+
+ for (i = 0; i < eDOMClassInfoIDCount; i++) {
+ NS_IF_RELEASE(sClassInfoData[i].mCachedClassInfo);
+ }
+ }
+
+ sConstructor_id = JSID_VOID;
+ sWrappedJSObject_id = JSID_VOID;
+
+ NS_IF_RELEASE(sXPConnect);
+ sIsInitialized = false;
+}
+
+static nsresult
+BaseStubConstructor(nsIWeakReference* aWeakOwner,
+ const nsGlobalNameStruct *name_struct, JSContext *cx,
+ JS::Handle<JSObject*> obj, const JS::CallArgs &args)
+{
+ MOZ_ASSERT(obj);
+ MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> native;
+ if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ MOZ_ASSERT(name_struct->mType ==
+ nsGlobalNameStruct::eTypeExternalConstructor);
+ native = do_CreateInstance(name_struct->mCID, &rv);
+ }
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Failed to create the object");
+ return rv;
+ }
+
+ js::AssertSameCompartment(cx, obj);
+ return nsContentUtils::WrapNative(cx, native, args.rval(), true);
+}
+
+static nsresult
+DefineInterfaceConstants(JSContext *cx, JS::Handle<JSObject*> obj, const nsIID *aIID)
+{
+ nsCOMPtr<nsIInterfaceInfoManager>
+ iim(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(iim, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIInterfaceInfo> if_info;
+
+ nsresult rv = iim->GetInfoForIID(aIID, getter_AddRefs(if_info));
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && if_info, rv);
+
+ uint16_t constant_count;
+
+ if_info->GetConstantCount(&constant_count);
+
+ if (!constant_count) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceInfo> parent_if_info;
+
+ rv = if_info->GetParent(getter_AddRefs(parent_if_info));
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && parent_if_info, rv);
+
+ uint16_t parent_constant_count, i;
+ parent_if_info->GetConstantCount(&parent_constant_count);
+
+ JS::Rooted<JS::Value> v(cx);
+ for (i = parent_constant_count; i < constant_count; i++) {
+ nsXPIDLCString name;
+ rv = if_info->GetConstant(i, &v, getter_Copies(name));
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv), rv);
+
+ if (!::JS_DefineProperty(cx, obj, name, v,
+ JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT,
+ JS_STUBGETTER, JS_STUBSETTER)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+class nsDOMConstructor final : public nsIDOMDOMConstructor
+{
+protected:
+ nsDOMConstructor(const char16_t* aName,
+ bool aIsConstructable,
+ nsPIDOMWindowInner* aOwner)
+ : mClassName(aName),
+ mConstructable(aIsConstructable),
+ mWeakOwner(do_GetWeakReference(aOwner))
+ {
+ }
+
+ ~nsDOMConstructor() {}
+
+public:
+
+ static nsresult Create(const char16_t* aName,
+ const nsGlobalNameStruct* aNameStruct,
+ nsPIDOMWindowInner* aOwner,
+ nsDOMConstructor** aResult);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMDOMCONSTRUCTOR
+
+ nsresult PreCreate(JSContext *cx, JSObject *globalObj, JSObject **parentObj);
+
+ nsresult Construct(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JS::Handle<JSObject*> obj, const JS::CallArgs &args,
+ bool *_retval);
+
+ nsresult HasInstance(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JS::Handle<JSObject*> obj, const JS::Value &val, bool *bp,
+ bool *_retval);
+
+ nsresult ResolveInterfaceConstants(JSContext *cx, JS::Handle<JSObject*> obj);
+
+private:
+ const nsGlobalNameStruct *GetNameStruct()
+ {
+ if (!mClassName) {
+ NS_ERROR("Can't get name");
+ return nullptr;
+ }
+
+ const nsGlobalNameStruct *nameStruct;
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ GetNameStruct(nsDependentString(mClassName), &nameStruct);
+
+ NS_ASSERTION(NS_FAILED(rv) || nameStruct, "Name isn't in hash.");
+
+ return nameStruct;
+ }
+
+ static nsresult GetNameStruct(const nsAString& aName,
+ const nsGlobalNameStruct **aNameStruct)
+ {
+ *aNameStruct = nullptr;
+
+ nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
+ if (!nameSpaceManager) {
+ NS_ERROR("Can't get namespace manager.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aNameStruct = nameSpaceManager->LookupName(aName);
+
+ // Return NS_OK here, aName just isn't a DOM class but nothing failed.
+ return NS_OK;
+ }
+
+ static bool IsConstructable(const nsGlobalNameStruct *aNameStruct)
+ {
+ return aNameStruct->mType == nsGlobalNameStruct::eTypeExternalConstructor;
+ }
+
+ const char16_t* mClassName;
+ const bool mConstructable;
+ nsWeakPtr mWeakOwner;
+};
+
+//static
+nsresult
+nsDOMConstructor::Create(const char16_t* aName,
+ const nsGlobalNameStruct* aNameStruct,
+ nsPIDOMWindowInner* aOwner,
+ nsDOMConstructor** aResult)
+{
+ *aResult = nullptr;
+ // Prevent creating a constructor if aOwner is inner window which doesn't have
+ // an outer window. If the outer window doesn't have an inner window or the
+ // caller can't access the outer window's current inner window then try to use
+ // the owner (so long as it is, in fact, an inner window). If that doesn't
+ // work then prevent creation also.
+ nsPIDOMWindowOuter* outerWindow = aOwner->GetOuterWindow();
+ nsPIDOMWindowInner* currentInner =
+ outerWindow ? outerWindow->GetCurrentInnerWindow() : aOwner;
+ if (!currentInner ||
+ (aOwner != currentInner &&
+ !nsContentUtils::CanCallerAccess(currentInner) &&
+ !(currentInner = aOwner)->IsInnerWindow())) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ bool constructable = aNameStruct && IsConstructable(aNameStruct);
+
+ *aResult = new nsDOMConstructor(aName, constructable, currentInner);
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ADDREF(nsDOMConstructor)
+NS_IMPL_RELEASE(nsDOMConstructor)
+NS_INTERFACE_MAP_BEGIN(nsDOMConstructor)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMDOMConstructor)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
+#ifdef DEBUG
+ {
+ const nsGlobalNameStruct *name_struct = GetNameStruct();
+ NS_ASSERTION(!name_struct ||
+ mConstructable == IsConstructable(name_struct),
+ "Can't change constructability dynamically!");
+ }
+#endif
+ foundInterface =
+ NS_GetDOMClassInfoInstance(mConstructable ?
+ eDOMClassInfo_DOMConstructor_id :
+ eDOMClassInfo_DOMPrototype_id);
+ if (!foundInterface) {
+ *aInstancePtr = nullptr;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else
+NS_INTERFACE_MAP_END
+
+nsresult
+nsDOMConstructor::PreCreate(JSContext *cx, JSObject *globalObj, JSObject **parentObj)
+{
+ nsCOMPtr<nsPIDOMWindowInner> owner(do_QueryReferent(mWeakOwner));
+ if (!owner) {
+ // Can't do anything.
+ return NS_OK;
+ }
+
+ nsGlobalWindow *win = nsGlobalWindow::Cast(owner);
+ return SetParentToWindow(win, parentObj);
+}
+
+nsresult
+nsDOMConstructor::Construct(nsIXPConnectWrappedNative *wrapper, JSContext * cx,
+ JS::Handle<JSObject*> obj, const JS::CallArgs &args,
+ bool *_retval)
+{
+ MOZ_ASSERT(obj);
+
+ const nsGlobalNameStruct *name_struct = GetNameStruct();
+ NS_ENSURE_TRUE(name_struct, NS_ERROR_FAILURE);
+
+ if (!IsConstructable(name_struct)) {
+ // ignore return value, we return false anyway
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ return BaseStubConstructor(mWeakOwner, name_struct, cx, obj, args);
+}
+
+nsresult
+nsDOMConstructor::HasInstance(nsIXPConnectWrappedNative *wrapper,
+ JSContext * cx, JS::Handle<JSObject*> obj,
+ const JS::Value &v, bool *bp, bool *_retval)
+
+{
+ // No need to look these up in the hash.
+ *bp = false;
+ if (v.isPrimitive()) {
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> dom_obj(cx, v.toObjectOrNull());
+ NS_ASSERTION(dom_obj, "nsDOMConstructor::HasInstance couldn't get object");
+
+ // This might not be the right object, if there are wrappers. Unwrap if we can.
+ JSObject *wrapped_obj = js::CheckedUnwrap(dom_obj, /* stopAtWindowProxy = */ false);
+ if (wrapped_obj)
+ dom_obj = wrapped_obj;
+
+ const JSClass *dom_class = JS_GetClass(dom_obj);
+ if (!dom_class) {
+ NS_ERROR("nsDOMConstructor::HasInstance can't get class.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const nsGlobalNameStruct *name_struct;
+ nsresult rv = GetNameStruct(NS_ConvertASCIItoUTF16(dom_class->name), &name_struct);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!name_struct) {
+ // This isn't a normal DOM object, see if this constructor lives on its
+ // prototype chain.
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ if (!JS_GetPropertyDescriptor(cx, obj, "prototype", &desc)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!desc.object() || desc.hasGetterOrSetter() || !desc.value().isObject()) {
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> dot_prototype(cx, &desc.value().toObject());
+
+ JS::Rooted<JSObject*> proto(cx, dom_obj);
+ JSAutoCompartment ac(cx, proto);
+
+ if (!JS_WrapObject(cx, &dot_prototype)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (;;) {
+ if (!JS_GetPrototype(cx, proto, &proto)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!proto) {
+ break;
+ }
+ if (proto == dot_prototype) {
+ *bp = true;
+ break;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (name_struct->mType != nsGlobalNameStruct::eTypeClassConstructor) {
+ // Doesn't have DOM interfaces.
+ return NS_OK;
+ }
+
+ const nsGlobalNameStruct *class_name_struct = GetNameStruct();
+ NS_ENSURE_TRUE(class_name_struct, NS_ERROR_FAILURE);
+
+ if (name_struct == class_name_struct) {
+ *bp = true;
+
+ return NS_OK;
+ }
+
+ const nsIID *class_iid;
+ if (class_name_struct->mType == nsGlobalNameStruct::eTypeClassProto) {
+ class_iid = &class_name_struct->mIID;
+ } else if (class_name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
+ class_iid =
+ sClassInfoData[class_name_struct->mDOMClassInfoID].mProtoChainInterface;
+ } else {
+ *bp = false;
+
+ return NS_OK;
+ }
+
+ NS_ASSERTION(name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor,
+ "The constructor was set up with a struct of the wrong type.");
+
+ const nsDOMClassInfoData *ci_data;
+ if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor &&
+ name_struct->mDOMClassInfoID >= 0) {
+ ci_data = &sClassInfoData[name_struct->mDOMClassInfoID];
+ } else {
+ ci_data = nullptr;
+ }
+
+ nsCOMPtr<nsIInterfaceInfoManager>
+ iim(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID));
+ if (!iim) {
+ NS_ERROR("nsDOMConstructor::HasInstance can't get interface info mgr.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIInterfaceInfo> if_info;
+ uint32_t count = 0;
+ const nsIID* class_interface;
+ while ((class_interface = ci_data->mInterfaces[count++])) {
+ if (class_iid->Equals(*class_interface)) {
+ *bp = true;
+
+ return NS_OK;
+ }
+
+ iim->GetInfoForIID(class_interface, getter_AddRefs(if_info));
+ if (!if_info) {
+ NS_ERROR("nsDOMConstructor::HasInstance can't get interface info.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if_info->HasAncestor(class_iid, bp);
+
+ if (*bp) {
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDOMConstructor::ResolveInterfaceConstants(JSContext *cx, JS::Handle<JSObject*> obj)
+{
+ const nsGlobalNameStruct *class_name_struct = GetNameStruct();
+ if (!class_name_struct)
+ return NS_ERROR_UNEXPECTED;
+
+ const nsIID *class_iid;
+ if (class_name_struct->mType == nsGlobalNameStruct::eTypeClassProto) {
+ class_iid = &class_name_struct->mIID;
+ } else if (class_name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
+ class_iid =
+ sClassInfoData[class_name_struct->mDOMClassInfoID].mProtoChainInterface;
+ } else {
+ return NS_OK;
+ }
+
+ nsresult rv = DefineInterfaceConstants(cx, obj, class_iid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMConstructor::ToString(nsAString &aResult)
+{
+ aResult.AssignLiteral("[object ");
+ aResult.Append(mClassName);
+ aResult.Append(char16_t(']'));
+
+ return NS_OK;
+}
+
+
+static nsresult
+GetXPCProto(nsIXPConnect *aXPConnect, JSContext *cx, nsGlobalWindow *aWin,
+ const nsGlobalNameStruct *aNameStruct,
+ JS::MutableHandle<JSObject*> aProto)
+{
+ NS_ASSERTION(aNameStruct->mType == nsGlobalNameStruct::eTypeClassConstructor,
+ "Wrong type!");
+
+ int32_t id = aNameStruct->mDOMClassInfoID;
+ MOZ_ASSERT(id >= 0, "Negative DOM classinfo?!?");
+
+ nsDOMClassInfoID ci_id = (nsDOMClassInfoID)id;
+
+ nsCOMPtr<nsIClassInfo> ci = NS_GetDOMClassInfoInstance(ci_id);
+ NS_ENSURE_TRUE(ci, NS_ERROR_UNEXPECTED);
+
+ nsresult rv =
+ aXPConnect->GetWrappedNativePrototype(cx, aWin->GetGlobalJSObject(), ci, aProto.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return JS_WrapObject(cx, aProto) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// Either ci_data must be non-null or name_struct must be non-null and of type
+// eTypeClassProto.
+static nsresult
+ResolvePrototype(nsIXPConnect *aXPConnect, nsGlobalWindow *aWin, JSContext *cx,
+ JS::Handle<JSObject*> obj, const char16_t *name,
+ const nsDOMClassInfoData *ci_data,
+ const nsGlobalNameStruct *name_struct,
+ nsScriptNameSpaceManager *nameSpaceManager,
+ JSObject* aDot_prototype,
+ JS::MutableHandle<JS::PropertyDescriptor> ctorDesc)
+{
+ JS::Rooted<JSObject*> dot_prototype(cx, aDot_prototype);
+ NS_ASSERTION(ci_data ||
+ (name_struct &&
+ name_struct->mType == nsGlobalNameStruct::eTypeClassProto),
+ "Wrong type or missing ci_data!");
+
+ RefPtr<nsDOMConstructor> constructor;
+ nsresult rv = nsDOMConstructor::Create(name, name_struct, aWin->AsInner(),
+ getter_AddRefs(constructor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JS::Value> v(cx);
+
+ js::AssertSameCompartment(cx, obj);
+ rv = nsContentUtils::WrapNative(cx, constructor,
+ &NS_GET_IID(nsIDOMDOMConstructor), &v,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FillPropertyDescriptor(ctorDesc, obj, 0, v);
+ // And make sure we wrap the value into the right compartment. Note that we
+ // do this with ctorDesc.value(), not with v, because we need v to be in the
+ // right compartment (that of the reflector of |constructor|) below.
+ if (!JS_WrapValue(cx, ctorDesc.value())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ JS::Rooted<JSObject*> class_obj(cx, &v.toObject());
+
+ const nsIID *primary_iid = &NS_GET_IID(nsISupports);
+
+ if (!ci_data) {
+ primary_iid = &name_struct->mIID;
+ }
+ else if (ci_data->mProtoChainInterface) {
+ primary_iid = ci_data->mProtoChainInterface;
+ }
+
+ nsCOMPtr<nsIInterfaceInfo> if_info;
+ nsCOMPtr<nsIInterfaceInfo> parent;
+ const char *class_parent_name = nullptr;
+
+ if (!primary_iid->Equals(NS_GET_IID(nsISupports))) {
+ JSAutoCompartment ac(cx, class_obj);
+
+ rv = DefineInterfaceConstants(cx, class_obj, primary_iid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInterfaceInfoManager>
+ iim(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(iim, NS_ERROR_NOT_AVAILABLE);
+
+ iim->GetInfoForIID(primary_iid, getter_AddRefs(if_info));
+ NS_ENSURE_TRUE(if_info, NS_ERROR_UNEXPECTED);
+
+ const nsIID *iid = nullptr;
+
+ if (ci_data && !ci_data->mHasClassInterface) {
+ if_info->GetIIDShared(&iid);
+ } else {
+ if_info->GetParent(getter_AddRefs(parent));
+ NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
+
+ parent->GetIIDShared(&iid);
+ }
+
+ if (iid) {
+ if (!iid->Equals(NS_GET_IID(nsISupports))) {
+ if (ci_data && !ci_data->mHasClassInterface) {
+ // If the class doesn't have a class interface the primary
+ // interface is the interface that should be
+ // constructor.prototype.__proto__.
+
+ if_info->GetNameShared(&class_parent_name);
+ } else {
+ // If the class does have a class interface (or there's no
+ // real class for this name) then the parent of the
+ // primary interface is what we want on
+ // constructor.prototype.__proto__.
+
+ NS_ASSERTION(parent, "Whoa, this is bad, null parent here!");
+
+ parent->GetNameShared(&class_parent_name);
+ }
+ }
+ }
+ }
+
+ {
+ JS::Rooted<JSObject*> winobj(cx, aWin->FastGetGlobalJSObject());
+
+ JS::Rooted<JSObject*> proto(cx);
+
+ if (class_parent_name) {
+ JSAutoCompartment ac(cx, winobj);
+
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ if (!JS_GetPropertyDescriptor(cx, winobj, CutPrefix(class_parent_name), &desc)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (desc.object() && !desc.hasGetterOrSetter() && desc.value().isObject()) {
+ JS::Rooted<JSObject*> obj(cx, &desc.value().toObject());
+ if (!JS_GetPropertyDescriptor(cx, obj, "prototype", &desc)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (desc.object() && !desc.hasGetterOrSetter() && desc.value().isObject()) {
+ proto = &desc.value().toObject();
+ }
+ }
+ }
+
+ if (dot_prototype) {
+ JSAutoCompartment ac(cx, dot_prototype);
+ JS::Rooted<JSObject*> xpc_proto_proto(cx);
+ if (!::JS_GetPrototype(cx, dot_prototype, &xpc_proto_proto)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (proto &&
+ (!xpc_proto_proto ||
+ JS_GetClass(xpc_proto_proto) == sObjectClass)) {
+ if (!JS_WrapObject(cx, &proto) ||
+ !JS_SetPrototype(cx, dot_prototype, proto)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ } else {
+ JSAutoCompartment ac(cx, winobj);
+ if (!proto) {
+ proto = JS_GetObjectPrototype(cx, winobj);
+ }
+ dot_prototype = ::JS_NewObjectWithUniqueType(cx,
+ &sDOMConstructorProtoClass,
+ proto);
+ NS_ENSURE_TRUE(dot_prototype, NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ v.setObject(*dot_prototype);
+
+ JSAutoCompartment ac(cx, class_obj);
+
+ // Per ECMA, the prototype property is {DontEnum, DontDelete, ReadOnly}
+ if (!JS_WrapValue(cx, &v) ||
+ !JS_DefineProperty(cx, class_obj, "prototype", v,
+ JSPROP_PERMANENT | JSPROP_READONLY,
+ JS_STUBGETTER, JS_STUBSETTER)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+static bool
+OldBindingConstructorEnabled(const nsGlobalNameStruct *aStruct,
+ nsGlobalWindow *aWin, JSContext *cx)
+{
+ MOZ_ASSERT(aStruct->mType == nsGlobalNameStruct::eTypeProperty ||
+ aStruct->mType == nsGlobalNameStruct::eTypeClassConstructor);
+
+ // Don't expose chrome only constructors to content windows.
+ if (aStruct->mChromeOnly) {
+ bool expose;
+ if (aStruct->mAllowXBL) {
+ expose = IsChromeOrXBL(cx, nullptr);
+ } else {
+ expose = nsContentUtils::IsSystemPrincipal(aWin->GetPrincipal());
+ }
+
+ if (!expose) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static nsresult
+LookupComponentsShim(JSContext *cx, JS::Handle<JSObject*> global,
+ nsPIDOMWindowInner *win,
+ JS::MutableHandle<JS::PropertyDescriptor> desc);
+
+// static
+bool
+nsWindowSH::NameStructEnabled(JSContext* aCx, nsGlobalWindow *aWin,
+ const nsAString& aName,
+ const nsGlobalNameStruct& aNameStruct)
+{
+ const nsGlobalNameStruct* nameStruct = &aNameStruct;
+ return (nameStruct->mType != nsGlobalNameStruct::eTypeProperty &&
+ nameStruct->mType != nsGlobalNameStruct::eTypeClassConstructor) ||
+ OldBindingConstructorEnabled(nameStruct, aWin, aCx);
+}
+
+#ifdef RELEASE_OR_BETA
+#define USE_CONTROLLERS_SHIM
+#endif
+
+#ifdef USE_CONTROLLERS_SHIM
+static const JSClass ControllersShimClass = {
+ "XULControllers", 0
+};
+#endif
+
+// static
+nsresult
+nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
+ JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc)
+{
+ if (id == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
+ return LookupComponentsShim(cx, obj, aWin->AsInner(), desc);
+ }
+
+#ifdef USE_CONTROLLERS_SHIM
+ // Note: We use |obj| rather than |aWin| to get the principal here, because
+ // this is called during Window setup when the Document isn't necessarily
+ // hooked up yet.
+ if (id == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS) &&
+ !xpc::IsXrayWrapper(obj) &&
+ !nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(obj)))
+ {
+ if (aWin->GetDoc()) {
+ aWin->GetDoc()->WarnOnceAbout(nsIDocument::eWindow_Controllers);
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(obj));
+ JS::Rooted<JSObject*> shim(cx, JS_NewObject(cx, &ControllersShimClass));
+ if (NS_WARN_IF(!shim)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ FillPropertyDescriptor(desc, obj, JS::ObjectValue(*shim), /* readOnly = */ false);
+ return NS_OK;
+ }
+#endif
+
+ nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
+ NS_ENSURE_TRUE(nameSpaceManager, NS_ERROR_NOT_INITIALIZED);
+
+ // Note - Our only caller is nsGlobalWindow::DoResolve, which checks that
+ // JSID_IS_STRING(id) is true.
+ nsAutoJSString name;
+ if (!name.init(cx, JSID_TO_STRING(id))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ const char16_t *class_name = nullptr;
+ const nsGlobalNameStruct *name_struct =
+ nameSpaceManager->LookupName(name, &class_name);
+
+ if (!name_struct) {
+ return NS_OK;
+ }
+
+ // The class_name had better match our name
+ MOZ_ASSERT(name.Equals(class_name));
+
+ NS_ENSURE_TRUE(class_name, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = NS_OK;
+
+ if (name_struct->mType == nsGlobalNameStruct::eTypeClassConstructor) {
+ if (!OldBindingConstructorEnabled(name_struct, aWin, cx)) {
+ return NS_OK;
+ }
+
+ // Create the XPConnect prototype for our classinfo, PostCreateProto will
+ // set up the prototype chain. This will go ahead and define things on the
+ // actual window's global.
+ JS::Rooted<JSObject*> dot_prototype(cx);
+ rv = GetXPCProto(nsDOMClassInfo::sXPConnect, cx, aWin, name_struct,
+ &dot_prototype);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(dot_prototype);
+
+ bool isXray = xpc::WrapperFactory::IsXrayWrapper(obj);
+ MOZ_ASSERT_IF(obj != aWin->GetGlobalJSObject(), isXray);
+ if (!isXray) {
+ // GetXPCProto already defined the property for us
+ FillPropertyDescriptor(desc, obj, JS::UndefinedValue(), false);
+ return NS_OK;
+ }
+
+ // This is the Xray case. Look up the constructor object for this
+ // prototype.
+ return ResolvePrototype(nsDOMClassInfo::sXPConnect, aWin, cx, obj,
+ class_name,
+ &sClassInfoData[name_struct->mDOMClassInfoID],
+ name_struct, nameSpaceManager, dot_prototype,
+ desc);
+ }
+
+ if (name_struct->mType == nsGlobalNameStruct::eTypeClassProto) {
+ // We don't have a XPConnect prototype object, let ResolvePrototype create
+ // one.
+ return ResolvePrototype(nsDOMClassInfo::sXPConnect, aWin, cx, obj,
+ class_name, nullptr,
+ name_struct, nameSpaceManager, nullptr, desc);
+ }
+
+ if (name_struct->mType == nsGlobalNameStruct::eTypeExternalConstructor) {
+ RefPtr<nsDOMConstructor> constructor;
+ rv = nsDOMConstructor::Create(class_name, name_struct, aWin->AsInner(),
+ getter_AddRefs(constructor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JS::Value> val(cx);
+ js::AssertSameCompartment(cx, obj);
+ rv = nsContentUtils::WrapNative(cx, constructor,
+ &NS_GET_IID(nsIDOMDOMConstructor), &val,
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(val.isObject(), "Why didn't we get a JSObject?");
+
+ FillPropertyDescriptor(desc, obj, 0, val);
+
+ return NS_OK;
+ }
+
+ if (name_struct->mType == nsGlobalNameStruct::eTypeProperty) {
+ if (!OldBindingConstructorEnabled(name_struct, aWin, cx))
+ return NS_OK;
+
+ // Before defining a global property, check for a named subframe of the
+ // same name. If it exists, we don't want to shadow it.
+ if (nsCOMPtr<nsPIDOMWindowOuter> childWin = aWin->GetChildWindow(name)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupports> native(do_CreateInstance(name_struct->mCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JS::Value> prop_val(cx, JS::UndefinedValue()); // Property value.
+
+ nsCOMPtr<nsIDOMGlobalPropertyInitializer> gpi(do_QueryInterface(native));
+ if (gpi) {
+ rv = gpi->Init(aWin->AsInner(), &prop_val);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (prop_val.isPrimitive() && !prop_val.isNull()) {
+ rv = nsContentUtils::WrapNative(cx, native, &prop_val, true);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!JS_WrapValue(cx, &prop_val)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ FillPropertyDescriptor(desc, obj, prop_val, false);
+
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+struct InterfaceShimEntry {
+ const char *geckoName;
+ const char *domName;
+};
+
+// 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" },
+ { "nsIDOMCSSPrimitiveValue", "CSSPrimitiveValue" },
+ { "nsIDOMCSSRule", "CSSRule" },
+ { "nsIDOMCSSValue", "CSSValue" },
+ { "nsIDOMEvent", "Event" },
+ { "nsIDOMNSEvent", "Event" },
+ { "nsIDOMKeyEvent", "KeyEvent" },
+ { "nsIDOMMouseEvent", "MouseEvent" },
+ { "nsIDOMMouseScrollEvent", "MouseScrollEvent" },
+ { "nsIDOMMutationEvent", "MutationEvent" },
+ { "nsIDOMSimpleGestureEvent", "SimpleGestureEvent" },
+ { "nsIDOMUIEvent", "UIEvent" },
+ { "nsIDOMHTMLMediaElement", "HTMLMediaElement" },
+ { "nsIDOMOfflineResourceList", "OfflineResourceList" },
+ { "nsIDOMRange", "Range" },
+ { "nsIDOMSVGLength", "SVGLength" },
+ { "nsIDOMNodeFilter", "NodeFilter" },
+ { "nsIDOMXPathResult", "XPathResult" } };
+
+static nsresult
+LookupComponentsShim(JSContext *cx, JS::Handle<JSObject*> global,
+ nsPIDOMWindowInner *win,
+ JS::MutableHandle<JS::PropertyDescriptor> desc)
+{
+ // Keep track of how often this happens.
+ Telemetry::Accumulate(Telemetry::COMPONENTS_SHIM_ACCESSED_BY_CONTENT, true);
+
+ // Warn once.
+ nsCOMPtr<nsIDocument> doc = win->GetExtantDoc();
+ if (doc) {
+ doc->WarnOnceAbout(nsIDocument::eComponents, /* asError = */ true);
+ }
+
+ // Create a fake Components object.
+ AssertSameCompartment(cx, global);
+ JS::Rooted<JSObject*> components(cx, JS_NewPlainObject(cx));
+ NS_ENSURE_TRUE(components, NS_ERROR_OUT_OF_MEMORY);
+
+ // Create a fake interfaces object.
+ JS::Rooted<JSObject*> interfaces(cx, JS_NewPlainObject(cx));
+ NS_ENSURE_TRUE(interfaces, NS_ERROR_OUT_OF_MEMORY);
+ bool ok =
+ JS_DefineProperty(cx, components, "interfaces", interfaces,
+ JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY,
+ JS_STUBGETTER, JS_STUBSETTER);
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+
+ // 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(cx, JS::UndefinedValue());
+ ok = JS_GetProperty(cx, global, domName, &v);
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ if (!v.isObject()) {
+ NS_WARNING("Unable to find interface object on global");
+ continue;
+ }
+
+ // Define the shim on the interfaces object.
+ ok = JS_DefineProperty(cx, interfaces, geckoName, v,
+ JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY,
+ JS_STUBGETTER, JS_STUBSETTER);
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ FillPropertyDescriptor(desc, global, JS::ObjectValue(*components), false);
+
+ return NS_OK;
+}
+
+// EventTarget helper
+
+NS_IMETHODIMP
+nsEventTargetSH::PreCreate(nsISupports *nativeObj, JSContext *cx,
+ JSObject *aGlobalObj, JSObject **parentObj)
+{
+ JS::Rooted<JSObject*> globalObj(cx, aGlobalObj);
+ DOMEventTargetHelper* target = DOMEventTargetHelper::FromSupports(nativeObj);
+
+ nsCOMPtr<nsIScriptGlobalObject> native_parent;
+ target->GetParentObject(getter_AddRefs(native_parent));
+
+ *parentObj = native_parent ? native_parent->GetGlobalJSObject() : globalObj;
+
+ return *parentObj ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsEventTargetSH::AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, jsid id, JS::Handle<JS::Value> val,
+ bool *_retval)
+{
+ nsEventTargetSH::PreserveWrapper(GetNative(wrapper, obj));
+
+ return NS_OK;
+}
+
+void
+nsEventTargetSH::PreserveWrapper(nsISupports *aNative)
+{
+ DOMEventTargetHelper* target = DOMEventTargetHelper::FromSupports(aNative);
+ target->PreserveWrapper(aNative);
+}
+
+// nsIDOMEventListener::HandleEvent() 'this' converter helper
+
+NS_INTERFACE_MAP_BEGIN(nsEventListenerThisTranslator)
+ NS_INTERFACE_MAP_ENTRY(nsIXPCFunctionThisTranslator)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+
+NS_IMPL_ADDREF(nsEventListenerThisTranslator)
+NS_IMPL_RELEASE(nsEventListenerThisTranslator)
+
+
+NS_IMETHODIMP
+nsEventListenerThisTranslator::TranslateThis(nsISupports *aInitialThis,
+ nsISupports **_retval)
+{
+ nsCOMPtr<nsIDOMEvent> event(do_QueryInterface(aInitialThis));
+ NS_ENSURE_TRUE(event, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<EventTarget> target = event->InternalDOMEvent()->GetCurrentTarget();
+ target.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMConstructorSH::PreCreate(nsISupports *nativeObj, JSContext *cx,
+ JSObject *aGlobalObj, JSObject **parentObj)
+{
+ JS::Rooted<JSObject*> globalObj(cx, aGlobalObj);
+ nsDOMConstructor *wrapped = static_cast<nsDOMConstructor *>(nativeObj);
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIDOMDOMConstructor> is_constructor =
+ do_QueryInterface(nativeObj);
+ NS_ASSERTION(is_constructor, "How did we not get a constructor?");
+ }
+#endif
+
+ return wrapped->PreCreate(cx, globalObj, parentObj);
+}
+
+NS_IMETHODIMP
+nsDOMConstructorSH::Resolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *aObj, jsid aId, bool *resolvedp,
+ bool *_retval)
+{
+ JS::Rooted<JSObject*> obj(cx, aObj);
+ JS::Rooted<jsid> id(cx, aId);
+ // For regular DOM constructors, we have our interface constants defined on
+ // us by nsWindowSH::GlobalResolve. However, XrayWrappers can't see these
+ // interface constants (as they look like expando properties) so we have to
+ // specially resolve those constants here, but only for Xray wrappers.
+ if (!ObjectIsNativeWrapper(cx, obj)) {
+ return NS_OK;
+ }
+
+ JS::Rooted<JSObject*> nativePropsObj(cx, xpc::XrayUtils::GetNativePropertiesObject(cx, obj));
+ nsDOMConstructor *wrapped =
+ static_cast<nsDOMConstructor *>(wrapper->Native());
+ nsresult rv = wrapped->ResolveInterfaceConstants(cx, nativePropsObj);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now re-lookup the ID to see if we should report back that we resolved the
+ // looked-for constant. Note that we don't have to worry about infinitely
+ // recurring back here because the Xray wrapper's holder object doesn't call
+ // Resolve hooks.
+ bool found;
+ if (!JS_HasPropertyById(cx, nativePropsObj, id, &found)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ if (found) {
+ *resolvedp = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMConstructorSH::Call(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *aObj, const JS::CallArgs &args, bool *_retval)
+{
+ JS::Rooted<JSObject*> obj(cx, aObj);
+ MOZ_ASSERT(obj);
+
+ nsDOMConstructor *wrapped =
+ static_cast<nsDOMConstructor *>(wrapper->Native());
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIDOMDOMConstructor> is_constructor =
+ do_QueryWrappedNative(wrapper);
+ NS_ASSERTION(is_constructor, "How did we not get a constructor?");
+ }
+#endif
+
+ return wrapped->Construct(wrapper, cx, obj, args, _retval);
+}
+
+NS_IMETHODIMP
+nsDOMConstructorSH::Construct(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *aObj, const JS::CallArgs &args, bool *_retval)
+{
+ JS::Rooted<JSObject*> obj(cx, aObj);
+ MOZ_ASSERT(obj);
+
+ nsDOMConstructor *wrapped =
+ static_cast<nsDOMConstructor *>(wrapper->Native());
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIDOMDOMConstructor> is_constructor =
+ do_QueryWrappedNative(wrapper);
+ NS_ASSERTION(is_constructor, "How did we not get a constructor?");
+ }
+#endif
+
+ return wrapped->Construct(wrapper, cx, obj, args, _retval);
+}
+
+NS_IMETHODIMP
+nsDOMConstructorSH::HasInstance(nsIXPConnectWrappedNative *wrapper,
+ JSContext *cx, JSObject *aObj, JS::Handle<JS::Value> val,
+ bool *bp, bool *_retval)
+{
+ JS::Rooted<JSObject*> obj(cx, aObj);
+ nsDOMConstructor *wrapped =
+ static_cast<nsDOMConstructor *>(wrapper->Native());
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIDOMDOMConstructor> is_constructor =
+ do_QueryWrappedNative(wrapper);
+ NS_ASSERTION(is_constructor, "How did we not get a constructor?");
+ }
+#endif
+
+ return wrapped->HasInstance(wrapper, cx, obj, val, bp, _retval);
+}
+
+NS_IMETHODIMP
+nsNonDOMObjectSH::GetFlags(uint32_t *aFlags)
+{
+ // This is NOT a DOM Object. Use this helper class for cases when you need
+ // to do something like implement nsISecurityCheckedComponent in a meaningful
+ // way.
+ *aFlags = nsIClassInfo::MAIN_THREAD_ONLY | nsIClassInfo::SINGLETON_CLASSINFO;
+ return NS_OK;
+}
+
+// nsContentFrameMessageManagerSH
+
+template<typename Super>
+NS_IMETHODIMP
+nsMessageManagerSH<Super>::Resolve(nsIXPConnectWrappedNative* wrapper,
+ JSContext* cx, JSObject* obj_,
+ jsid id_, bool* resolvedp,
+ bool* _retval)
+{
+ JS::Rooted<JSObject*> obj(cx, obj_);
+ JS::Rooted<jsid> id(cx, id_);
+
+ *_retval = SystemGlobalResolve(cx, obj, id, resolvedp);
+ NS_ENSURE_TRUE(*_retval, NS_ERROR_FAILURE);
+
+ if (*resolvedp) {
+ return NS_OK;
+ }
+
+ return Super::Resolve(wrapper, cx, obj, id, resolvedp, _retval);
+}
+
+template<typename Super>
+NS_IMETHODIMP
+nsMessageManagerSH<Super>::Enumerate(nsIXPConnectWrappedNative* wrapper,
+ JSContext* cx, JSObject* obj_,
+ bool* _retval)
+{
+ JS::Rooted<JSObject*> obj(cx, obj_);
+
+ *_retval = SystemGlobalEnumerate(cx, obj);
+ NS_ENSURE_TRUE(*_retval, NS_ERROR_FAILURE);
+
+ // Don't call up to our superclass, since neither nsDOMGenericSH nor
+ // nsEventTargetSH have WANT_ENUMERATE.
+ return NS_OK;
+}
diff --git a/dom/base/nsDOMClassInfo.h b/dom/base/nsDOMClassInfo.h
new file mode 100644
index 000000000..68891da1f
--- /dev/null
+++ b/dom/base/nsDOMClassInfo.h
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsDOMClassInfo_h___
+#define nsDOMClassInfo_h___
+
+#include "mozilla/Attributes.h"
+#include "nsDOMClassInfoID.h"
+#include "nsIXPCScriptable.h"
+#include "nsIScriptGlobalObject.h"
+#include "js/Class.h"
+#include "js/Id.h"
+#include "nsIXPConnect.h"
+
+#ifdef XP_WIN
+#undef GetClassName
+#endif
+
+struct nsGlobalNameStruct;
+class nsGlobalWindow;
+
+struct nsDOMClassInfoData;
+
+typedef nsIClassInfo* (*nsDOMClassInfoConstructorFnc)
+ (nsDOMClassInfoData* aData);
+
+typedef nsresult (*nsDOMConstructorFunc)(nsISupports** aNewObject);
+
+struct nsDOMClassInfoData
+{
+ // XXX: mName is the same as the name gettable from the callback. This
+ // redundancy should be removed eventually.
+ const char *mName;
+ const char16_t *mNameUTF16;
+ const js::ClassOps mClassOps;
+ const js::Class mClass;
+ nsDOMClassInfoConstructorFnc mConstructorFptr;
+
+ nsIClassInfo *mCachedClassInfo;
+ const nsIID *mProtoChainInterface;
+ const nsIID **mInterfaces;
+ uint32_t mScriptableFlags : 31; // flags must not use more than 31 bits!
+ uint32_t mHasClassInterface : 1;
+ bool mChromeOnly : 1;
+ bool mAllowXBL : 1;
+ bool mDisabled : 1;
+#ifdef DEBUG
+ uint32_t mDebugID;
+#endif
+};
+
+class nsWindowSH;
+
+class nsDOMClassInfo : public nsXPCClassInfo
+{
+ friend class nsWindowSH;
+
+protected:
+ virtual ~nsDOMClassInfo() {};
+
+public:
+ explicit nsDOMClassInfo(nsDOMClassInfoData* aData);
+
+ NS_DECL_NSIXPCSCRIPTABLE
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSICLASSINFO
+
+ static nsresult Init();
+ static void ShutDown();
+
+ static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
+ {
+ return new nsDOMClassInfo(aData);
+ }
+
+ /*
+ * The following two functions exist because of the way that Xray wrappers
+ * work. In order to allow scriptable helpers to define non-IDL defined but
+ * still "safe" properties for Xray wrappers, we call into the scriptable
+ * helper with |obj| being the wrapper.
+ *
+ * Ideally, that would be the end of the story, however due to complications
+ * dealing with document.domain, it's possible to end up in a scriptable
+ * helper with a wrapper, even though we should be treating the lookup as a
+ * transparent one.
+ *
+ * Note: So ObjectIsNativeWrapper(cx, obj) check usually means "through xray
+ * wrapper this part is not visible".
+ */
+ static bool ObjectIsNativeWrapper(JSContext* cx, JSObject* obj);
+
+ static nsISupports *GetNative(nsIXPConnectWrappedNative *wrapper, JSObject *obj);
+
+ static nsIXPConnect *XPConnect()
+ {
+ return sXPConnect;
+ }
+
+protected:
+ friend nsIClassInfo* NS_GetDOMClassInfoInstance(nsDOMClassInfoID aID);
+
+ const nsDOMClassInfoData* mData;
+
+ virtual void PreserveWrapper(nsISupports *aNative) override
+ {
+ }
+
+ static nsresult RegisterClassProtos(int32_t aDOMClassInfoID);
+
+ static nsIXPConnect *sXPConnect;
+
+ // nsIXPCScriptable code
+ static nsresult DefineStaticJSVals();
+
+ static bool sIsInitialized;
+
+public:
+ static jsid sConstructor_id;
+ static jsid sWrappedJSObject_id;
+};
+
+// THIS ONE ISN'T SAFE!! It assumes that the private of the JSObject is
+// an nsISupports.
+inline
+const nsQueryInterface
+do_QueryWrappedNative(nsIXPConnectWrappedNative *wrapper, JSObject *obj)
+{
+ return nsQueryInterface(nsDOMClassInfo::GetNative(wrapper, obj));
+}
+
+// THIS ONE ISN'T SAFE!! It assumes that the private of the JSObject is
+// an nsISupports.
+inline
+const nsQueryInterfaceWithError
+do_QueryWrappedNative(nsIXPConnectWrappedNative *wrapper, JSObject *obj,
+ nsresult *aError)
+
+{
+ return nsQueryInterfaceWithError(nsDOMClassInfo::GetNative(wrapper, obj),
+ aError);
+}
+
+typedef nsDOMClassInfo nsDOMGenericSH;
+
+// Makes sure that the wrapper is preserved if new properties are added.
+class nsEventTargetSH : public nsDOMGenericSH
+{
+protected:
+ explicit nsEventTargetSH(nsDOMClassInfoData* aData) : nsDOMGenericSH(aData)
+ {
+ }
+
+ virtual ~nsEventTargetSH()
+ {
+ }
+public:
+ NS_IMETHOD PreCreate(nsISupports *nativeObj, JSContext *cx,
+ JSObject *globalObj, JSObject **parentObj) override;
+ NS_IMETHOD AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, jsid id, JS::Handle<JS::Value> val,
+ bool *_retval) override;
+
+ virtual void PreserveWrapper(nsISupports *aNative) override;
+
+ static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
+ {
+ return new nsEventTargetSH(aData);
+ }
+};
+
+// A place to hang some static methods that we should really consider
+// moving to be nsGlobalWindow member methods. See bug 1062418.
+class nsWindowSH
+{
+protected:
+ static nsresult GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
+ JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc);
+
+ friend class nsGlobalWindow;
+public:
+ static bool NameStructEnabled(JSContext* aCx, nsGlobalWindow *aWin,
+ const nsAString& aName,
+ const nsGlobalNameStruct& aNameStruct);
+};
+
+
+// Event handler 'this' translator class, this is called by XPConnect
+// when a "function interface" (nsIDOMEventListener) is called, this
+// class extracts 'this' fomr the first argument to the called
+// function (nsIDOMEventListener::HandleEvent(in nsIDOMEvent)), this
+// class will pass back nsIDOMEvent::currentTarget to be used as
+// 'this'.
+
+class nsEventListenerThisTranslator : public nsIXPCFunctionThisTranslator
+{
+ virtual ~nsEventListenerThisTranslator()
+ {
+ }
+
+public:
+ nsEventListenerThisTranslator()
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIXPCFunctionThisTranslator
+ NS_DECL_NSIXPCFUNCTIONTHISTRANSLATOR
+};
+
+class nsDOMConstructorSH : public nsDOMGenericSH
+{
+protected:
+ explicit nsDOMConstructorSH(nsDOMClassInfoData* aData) : nsDOMGenericSH(aData)
+ {
+ }
+
+public:
+ NS_IMETHOD PreCreate(nsISupports *nativeObj, JSContext *cx,
+ JSObject *globalObj, JSObject **parentObj) override;
+ NS_IMETHOD PostCreatePrototype(JSContext * cx, JSObject * proto) override
+ {
+ return NS_OK;
+ }
+ NS_IMETHOD Resolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, jsid id, bool *resolvedp,
+ bool *_retval) override;
+ NS_IMETHOD Call(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, const JS::CallArgs &args, bool *_retval) override;
+
+ NS_IMETHOD Construct(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, const JS::CallArgs &args, bool *_retval) override;
+
+ NS_IMETHOD HasInstance(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+ JSObject *obj, JS::Handle<JS::Value> val, bool *bp,
+ bool *_retval) override;
+
+ static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
+ {
+ return new nsDOMConstructorSH(aData);
+ }
+};
+
+class nsNonDOMObjectSH : public nsDOMGenericSH
+{
+protected:
+ explicit nsNonDOMObjectSH(nsDOMClassInfoData* aData) : nsDOMGenericSH(aData)
+ {
+ }
+
+ virtual ~nsNonDOMObjectSH()
+ {
+ }
+
+public:
+ NS_IMETHOD GetFlags(uint32_t *aFlags) override;
+
+ static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
+ {
+ return new nsNonDOMObjectSH(aData);
+ }
+};
+
+template<typename Super>
+class nsMessageManagerSH : public Super
+{
+protected:
+ explicit nsMessageManagerSH(nsDOMClassInfoData* aData)
+ : Super(aData)
+ {
+ }
+
+ virtual ~nsMessageManagerSH()
+ {
+ }
+public:
+ NS_IMETHOD Resolve(nsIXPConnectWrappedNative* wrapper, JSContext* cx,
+ JSObject* obj_, jsid id_, bool* resolvedp,
+ bool* _retval) override;
+ NS_IMETHOD Enumerate(nsIXPConnectWrappedNative* wrapper, JSContext* cx,
+ JSObject* obj_, bool* _retval) override;
+
+ static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
+ {
+ return new nsMessageManagerSH(aData);
+ }
+};
+
+#endif /* nsDOMClassInfo_h___ */
diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h
new file mode 100644
index 000000000..53a065070
--- /dev/null
+++ b/dom/base/nsDOMClassInfoClasses.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/. */
+// IWYU pragma: private, include "nsDOMClassInfoID.h"
+
+DOMCI_CLASS(DOMPrototype)
+DOMCI_CLASS(DOMConstructor)
+
+// CSS classes
+DOMCI_CLASS(CSSStyleRule)
+DOMCI_CLASS(CSSImportRule)
+DOMCI_CLASS(CSSMediaRule)
+DOMCI_CLASS(CSSNameSpaceRule)
+
+// XUL classes
+#ifdef MOZ_XUL
+DOMCI_CLASS(XULCommandDispatcher)
+#endif
+DOMCI_CLASS(XULControllers)
+#ifdef MOZ_XUL
+DOMCI_CLASS(TreeSelection)
+DOMCI_CLASS(TreeContentView)
+#endif
+
+#ifdef MOZ_XUL
+DOMCI_CLASS(XULTemplateBuilder)
+DOMCI_CLASS(XULTreeBuilder)
+#endif
+
+DOMCI_CLASS(CSSMozDocumentRule)
+DOMCI_CLASS(CSSSupportsRule)
+
+// @font-face in CSS
+DOMCI_CLASS(CSSFontFaceRule)
+
+DOMCI_CLASS(ContentFrameMessageManager)
+DOMCI_CLASS(ContentProcessMessageManager)
+DOMCI_CLASS(ChromeMessageBroadcaster)
+DOMCI_CLASS(ChromeMessageSender)
+
+DOMCI_CLASS(CSSKeyframeRule)
+DOMCI_CLASS(CSSKeyframesRule)
+
+// @counter-style in CSS
+DOMCI_CLASS(CSSCounterStyleRule)
+
+DOMCI_CLASS(CSSPageRule)
+
+DOMCI_CLASS(CSSFontFeatureValuesRule)
+
+DOMCI_CLASS(XULControlElement)
+DOMCI_CLASS(XULLabeledControlElement)
+DOMCI_CLASS(XULButtonElement)
+DOMCI_CLASS(XULCheckboxElement)
+DOMCI_CLASS(XULPopupElement)
diff --git a/dom/base/nsDOMClassInfoID.h b/dom/base/nsDOMClassInfoID.h
new file mode 100644
index 000000000..1ef6451b4
--- /dev/null
+++ b/dom/base/nsDOMClassInfoID.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/. */
+
+/**
+ * This file defines enum values for all of the DOM objects which have
+ * an entry in nsDOMClassInfo.
+ */
+
+#ifndef nsDOMClassInfoID_h__
+#define nsDOMClassInfoID_h__
+
+#include "nsIXPCScriptable.h"
+
+#define DOMCI_CLASS(_dom_class) \
+ eDOMClassInfo_##_dom_class##_id,
+
+enum nsDOMClassInfoID {
+
+#include "nsDOMClassInfoClasses.h"
+
+ // This one better be the last one in this list
+ eDOMClassInfoIDCount
+};
+
+#undef DOMCI_CLASS
+
+/**
+ * nsIClassInfo helper macros
+ */
+
+#ifdef MOZILLA_INTERNAL_API
+
+class nsIClassInfo;
+class nsXPCClassInfo;
+
+extern nsIClassInfo*
+NS_GetDOMClassInfoInstance(nsDOMClassInfoID aID);
+
+#define NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(_class) \
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo)) || \
+ aIID.Equals(NS_GET_IID(nsXPCClassInfo))) { \
+ foundInterface = NS_GetDOMClassInfoInstance(eDOMClassInfo_##_class##_id); \
+ if (!foundInterface) { \
+ *aInstancePtr = nullptr; \
+ return NS_ERROR_OUT_OF_MEMORY; \
+ } \
+ } else
+
+#define NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_CONDITIONAL(_class, condition) \
+ if ((condition) && \
+ (aIID.Equals(NS_GET_IID(nsIClassInfo)) || \
+ aIID.Equals(NS_GET_IID(nsXPCClassInfo)))) { \
+ foundInterface = NS_GetDOMClassInfoInstance(eDOMClassInfo_##_class##_id); \
+ if (!foundInterface) { \
+ *aInstancePtr = nullptr; \
+ return NS_ERROR_OUT_OF_MEMORY; \
+ } \
+ } else
+
+#endif // MOZILLA_INTERNAL_API
+
+#endif // nsDOMClassInfoID_h__
diff --git a/dom/base/nsDOMDataChannel.cpp b/dom/base/nsDOMDataChannel.cpp
new file mode 100644
index 000000000..a0419d123
--- /dev/null
+++ b/dom/base/nsDOMDataChannel.cpp
@@ -0,0 +1,635 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsDOMDataChannel.h"
+#include "nsIDOMDataChannel.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsProxyRelease.h"
+
+#include "DataChannel.h"
+#include "DataChannelLog.h"
+
+#undef LOG
+#define LOG(args) MOZ_LOG(mozilla::gDataChannelLog, mozilla::LogLevel::Debug, args)
+
+// 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)
+ LOG(("%p: Close()ing %p", this, mDataChannel.get()));
+ mDataChannel->SetListener(nullptr, nullptr);
+ mDataChannel->Close();
+}
+
+/* virtual */ JSObject*
+nsDOMDataChannel::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DataChannelBinding::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_INHERITED(nsDOMDataChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMDataChannel)
+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 = CheckInnerWindowCorrectness();
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = nsContentUtils::GetUTFOrigin(principal,mOrigin);
+ LOG(("%s: origin = %s\n",__FUNCTION__,NS_LossyConvertUTF16toASCII(mOrigin).get()));
+ return rv;
+}
+
+NS_IMPL_EVENT_HANDLER(nsDOMDataChannel, open)
+NS_IMPL_EVENT_HANDLER(nsDOMDataChannel, error)
+NS_IMPL_EVENT_HANDLER(nsDOMDataChannel, close)
+NS_IMPL_EVENT_HANDLER(nsDOMDataChannel, message)
+
+// Most of the GetFoo()/SetFoo()s don't need to touch shared resources and
+// are safe after Close()
+NS_IMETHODIMP
+nsDOMDataChannel::GetLabel(nsAString& aLabel)
+{
+ mDataChannel->GetLabel(aLabel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDataChannel::GetProtocol(nsAString& aProtocol)
+{
+ mDataChannel->GetProtocol(aProtocol);
+ return NS_OK;
+}
+
+uint16_t
+nsDOMDataChannel::Id() const
+{
+ return mDataChannel->GetStream();
+}
+
+NS_IMETHODIMP
+nsDOMDataChannel::GetId(uint16_t *aId)
+{
+ *aId = Id();
+ return NS_OK;
+}
+
+// XXX should be GetType()? Open question for the spec
+bool
+nsDOMDataChannel::Reliable() const
+{
+ return mDataChannel->GetType() == mozilla::DataChannelConnection::RELIABLE;
+}
+
+NS_IMETHODIMP
+nsDOMDataChannel::GetReliable(bool* aReliable)
+{
+ *aReliable = Reliable();
+ return NS_OK;
+}
+
+bool
+nsDOMDataChannel::Ordered() const
+{
+ return mDataChannel->GetOrdered();
+}
+
+NS_IMETHODIMP
+nsDOMDataChannel::GetOrdered(bool* aOrdered)
+{
+ *aOrdered = Ordered();
+ return NS_OK;
+}
+
+RTCDataChannelState
+nsDOMDataChannel::ReadyState() const
+{
+ return static_cast<RTCDataChannelState>(mDataChannel->GetReadyState());
+}
+
+
+NS_IMETHODIMP
+nsDOMDataChannel::GetReadyState(nsAString& aReadyState)
+{
+ // mState is handled on multiple threads and needs locking
+ uint16_t readyState = mozilla::DataChannel::CLOSED;
+ if (!mSentClose) {
+ readyState = mDataChannel->GetReadyState();
+ }
+ // From the WebRTC spec
+ const char * stateName[] = {
+ "connecting",
+ "open",
+ "closing",
+ "closed"
+ };
+ MOZ_ASSERT(/*readyState >= mozilla::DataChannel::CONNECTING && */ // Always true due to datatypes
+ readyState <= mozilla::DataChannel::CLOSED);
+ aReadyState.AssignASCII(stateName[readyState]);
+
+ return NS_OK;
+}
+
+uint32_t
+nsDOMDataChannel::BufferedAmount() const
+{
+ if (!mSentClose) {
+ return mDataChannel->GetBufferedAmount();
+ }
+ return 0;
+}
+
+uint32_t
+nsDOMDataChannel::BufferedAmountLowThreshold() const
+{
+ return mDataChannel->GetBufferedAmountLowThreshold();
+}
+
+NS_IMETHODIMP
+nsDOMDataChannel::GetBufferedAmount(uint32_t* aBufferedAmount)
+{
+ *aBufferedAmount = BufferedAmount();
+ return NS_OK;
+}
+
+void
+nsDOMDataChannel::SetBufferedAmountLowThreshold(uint32_t aThreshold)
+{
+ mDataChannel->SetBufferedAmountLowThreshold(aThreshold);
+}
+
+NS_IMETHODIMP nsDOMDataChannel::GetBinaryType(nsAString & aBinaryType)
+{
+ switch (mBinaryType) {
+ case DC_BINARY_TYPE_ARRAYBUFFER:
+ aBinaryType.AssignLiteral("arraybuffer");
+ break;
+ case DC_BINARY_TYPE_BLOB:
+ aBinaryType.AssignLiteral("blob");
+ break;
+ default:
+ NS_ERROR("Should not happen");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDataChannel::SetBinaryType(const nsAString& aBinaryType)
+{
+ if (aBinaryType.EqualsLiteral("arraybuffer")) {
+ mBinaryType = DC_BINARY_TYPE_ARRAYBUFFER;
+ } else if (aBinaryType.EqualsLiteral("blob")) {
+ mBinaryType = DC_BINARY_TYPE_BLOB;
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDataChannel::Close()
+{
+ mDataChannel->Close();
+ UpdateMustKeepAlive();
+ return NS_OK;
+}
+
+// All of the following is copy/pasted from WebSocket.cpp.
+void
+nsDOMDataChannel::Send(const nsAString& aData, ErrorResult& aRv)
+{
+ NS_ConvertUTF16toUTF8 msgString(aData);
+ Send(nullptr, msgString, msgString.Length(), false, aRv);
+}
+
+void
+nsDOMDataChannel::Send(Blob& aData, ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ nsCOMPtr<nsIInputStream> msgStream;
+ aData.GetInternalStream(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(msgStream, EmptyCString(), msgLength, true, aRv);
+}
+
+void
+nsDOMDataChannel::Send(const ArrayBuffer& aData, ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ aData.ComputeLengthAndData();
+
+ static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+ uint32_t len = aData.Length();
+ char* data = reinterpret_cast<char*>(aData.Data());
+
+ nsDependentCSubstring msgString(data, len);
+ Send(nullptr, msgString, len, true, aRv);
+}
+
+void
+nsDOMDataChannel::Send(const ArrayBufferView& aData, ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ aData.ComputeLengthAndData();
+
+ static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+ uint32_t len = aData.Length();
+ char* data = reinterpret_cast<char*>(aData.Data());
+
+ nsDependentCSubstring msgString(data, len);
+ Send(nullptr, msgString, len, true, aRv);
+}
+
+void
+nsDOMDataChannel::Send(nsIInputStream* aMsgStream,
+ const nsACString& aMsgString,
+ uint32_t aMsgLength,
+ bool aIsBinary,
+ 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");
+
+ bool sent;
+ if (aMsgStream) {
+ sent = mDataChannel->SendBinaryStream(aMsgStream, aMsgLength);
+ } else {
+ if (aIsBinary) {
+ sent = mDataChannel->SendBinaryMsg(aMsgString);
+ } else {
+ sent = mDataChannel->SendMsg(aMsgString);
+ }
+ }
+ if (!sent) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+nsresult
+nsDOMDataChannel::DoOnMessageAvailable(const nsACString& aData,
+ bool aBinary)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("DoOnMessageAvailable%s\n",aBinary ? ((mBinaryType == DC_BINARY_TYPE_BLOB) ? " (blob)" : " (binary)") : ""));
+
+ nsresult rv = CheckInnerWindowCorrectness();
+ 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(GetOwner(), aData, EmptyString());
+ MOZ_ASSERT(blob);
+
+ 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 {
+ NS_RUNTIMEABORT("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, NS_LITERAL_STRING("message"), false, false,
+ jsData, mOrigin, EmptyString(), nullptr,
+ Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__));
+ rv = DispatchDOMEvent(nullptr, static_cast<Event*>(event), nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the message event!!!");
+ }
+ return rv;
+}
+
+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 = CheckInnerWindowCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+
+ event->InitEvent(aName, false, false);
+ event->SetTrusted(true);
+
+ return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
+nsresult
+nsDOMDataChannel::OnChannelConnected(nsISupports* aContext)
+{
+ LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__));
+
+ return OnSimpleEvent(aContext, NS_LITERAL_STRING("open"));
+}
+
+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();
+ LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__));
+
+ rv = OnSimpleEvent(aContext, NS_LITERAL_STRING("close"));
+ // no more events can happen
+ mSentClose = true;
+ } else {
+ rv = NS_OK;
+ }
+ DontKeepAliveAnyMore();
+ return rv;
+}
+
+nsresult
+nsDOMDataChannel::OnBufferLow(nsISupports* aContext)
+{
+ LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__));
+
+ return OnSimpleEvent(aContext, NS_LITERAL_STRING("bufferedamountlow"));
+}
+
+nsresult
+nsDOMDataChannel::NotBuffered(nsISupports* aContext)
+{
+ // In the rare case that we held off GC to let the buffer drain
+ UpdateMustKeepAlive();
+ return NS_OK;
+}
+
+void
+nsDOMDataChannel::AppReady()
+{
+ if (!mSentClose) { // may not be possible, simpler to just test anyways
+ mDataChannel->AppReady();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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:
+ case DataChannel::WAITING_TO_OPEN:
+ {
+ 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) {
+ // release our self-reference (safely) by putting it in an event (always)
+ NS_ReleaseOnMainThread(mSelfRef.forget(), true);
+ } 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.
+ NS_ReleaseOnMainThread(mSelfRef.forget(), true);
+ }
+
+ mCheckMustKeepAlive = false;
+}
+
+void
+nsDOMDataChannel::EventListenerAdded(nsIAtom* aType)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ UpdateMustKeepAlive();
+}
+
+void
+nsDOMDataChannel::EventListenerRemoved(nsIAtom* aType)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ UpdateMustKeepAlive();
+}
+
+
+/* static */
+nsresult
+NS_NewDOMDataChannel(already_AddRefed<mozilla::DataChannel>&& aDataChannel,
+ nsPIDOMWindowInner* aWindow,
+ nsIDOMDataChannel** aDomDataChannel)
+{
+ RefPtr<nsDOMDataChannel> domdc =
+ new nsDOMDataChannel(aDataChannel, aWindow);
+
+ nsresult rv = domdc->Init(aWindow);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return CallQueryInterface(domdc, aDomDataChannel);
+}
+
+/* static */
+void
+NS_DataChannelAppReady(nsIDOMDataChannel* aDomDataChannel)
+{
+ ((nsDOMDataChannel *)aDomDataChannel)->AppReady();
+}
diff --git a/dom/base/nsDOMDataChannel.h b/dom/base/nsDOMDataChannel.h
new file mode 100644
index 000000000..8d1e1a0ec
--- /dev/null
+++ b/dom/base/nsDOMDataChannel.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 nsDOMDataChannel_h
+#define nsDOMDataChannel_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/DataChannelBinding.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/net/DataChannelListener.h"
+#include "nsIDOMDataChannel.h"
+#include "nsIInputStream.h"
+
+
+namespace mozilla {
+namespace dom {
+class Blob;
+}
+
+class DataChannel;
+};
+
+class nsDOMDataChannel final : public mozilla::DOMEventTargetHelper,
+ public nsIDOMDataChannel,
+ public mozilla::DataChannelListener
+{
+public:
+ nsDOMDataChannel(already_AddRefed<mozilla::DataChannel>& aDataChannel,
+ nsPIDOMWindowInner* aWindow);
+
+ nsresult Init(nsPIDOMWindowInner* aDOMWindow);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMDATACHANNEL
+
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(mozilla::DOMEventTargetHelper)
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMDataChannel,
+ mozilla::DOMEventTargetHelper)
+
+ // EventTarget
+ virtual void EventListenerAdded(nsIAtom* aType) override;
+ virtual void EventListenerRemoved(nsIAtom* aType) override;
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+ override;
+ nsPIDOMWindowInner* GetParentObject() const
+ {
+ return GetOwner();
+ }
+
+ // WebIDL
+ // Uses XPIDL GetLabel.
+ bool Reliable() 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)
+ // Uses XPIDL 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);
+
+ // Uses XPIDL GetProtocol.
+ bool Ordered() const;
+ uint16_t Id() 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;
+
+ virtual void
+ AppReady();
+
+ // 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(nsIInputStream* aMsgStream, const nsACString& aMsgString,
+ uint32_t aMsgLength, bool aIsBinary, mozilla::ErrorResult& aRv);
+
+ // 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 000000000..416561fcb
--- /dev/null
+++ b/dom/base/nsDOMDataChannelDeclarations.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 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"
+#include "nsIDOMDataChannel.h"
+
+namespace mozilla {
+ class DataChannel;
+}
+
+class nsPIDOMWindowInner;
+
+nsresult
+NS_NewDOMDataChannel(already_AddRefed<mozilla::DataChannel>&& dataChannel,
+ nsPIDOMWindowInner* aWindow,
+ nsIDOMDataChannel** domDataChannel);
+
+// Tell DataChannel it's ok to deliver open and message events
+void NS_DataChannelAppReady(nsIDOMDataChannel* domDataChannel);
+
+#endif // nsDOMDataChannelDeclarations_h
diff --git a/dom/base/nsDOMJSUtils.h b/dom/base/nsDOMJSUtils.h
new file mode 100644
index 000000000..afdba7113
--- /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 "nsIScriptContext.h"
+#include "jsapi.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 000000000..024ce5e2e
--- /dev/null
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -0,0 +1,1175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Maybe.h"
+#include "mozilla/OwningNonNull.h"
+
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsError.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTextFragment.h"
+#include "nsThreadUtils.h"
+
+using mozilla::Maybe;
+using mozilla::Move;
+using mozilla::NonOwningAnimationTarget;
+using mozilla::dom::TreeOrderComparator;
+using mozilla::dom::Animation;
+using mozilla::dom::Element;
+
+AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
+ nsDOMMutationObserver::sScheduledMutationObservers = nullptr;
+
+nsDOMMutationObserver* nsDOMMutationObserver::sCurrentObserver = 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() &&
+ (Observer()->IsChrome() || !aContent->IsInAnonymousSubtree());
+}
+
+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::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove) {
+ if (!NativeAnonymousChildList()) {
+ return;
+ }
+
+ nsINode* parent = aContent->GetParentNode();
+ if (!parent ||
+ (!Subtree() && Target() != parent) ||
+ (Subtree() && RegisterTarget()->SubtreeRoot() != parent->SubtreeRoot())) {
+ return;
+ }
+
+ nsDOMMutationRecord* m =
+ Observer()->CurrentRecord(nsGkAtoms::nativeAnonymousChildList);
+
+ if (m->mTarget) {
+ return;
+ }
+ m->mTarget = parent;
+
+ if (aIsRemove) {
+ m->mRemovedNodes = new nsSimpleContentList(parent);
+ m->mRemovedNodes->AppendElement(aContent);
+ } else {
+ m->mAddedNodes = new nsSimpleContentList(parent);
+ m->mAddedNodes->AppendElement(aContent);
+ }
+}
+
+void
+nsMutationReceiver::AttributeWillChange(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ 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 {
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID,
+ m->mAttrNamespace);
+ }
+ }
+
+ if (AttributeOldValue() && m->mPrevValue.IsVoid()) {
+ if (!aElement->GetAttr(aNameSpaceID, aAttribute, m->mPrevValue)) {
+ m->mPrevValue.SetIsVoid(true);
+ }
+ }
+}
+
+void
+nsMutationReceiver::CharacterDataWillChange(nsIDocument *aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ 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(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ nsINode* parent = NODE_FROM(aContainer, aDocument);
+ 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(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ nsINode* parent = NODE_FROM(aContainer, aDocument);
+ 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(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ if (!IsObservable(aChild)) {
+ return;
+ }
+
+ nsINode* parent = NODE_FROM(aContainer, aDocument);
+ 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;
+ nsCOMArray<nsMutationReceiver>* transientReceivers = nullptr;
+ Observer()->mTransientReceivers.Get(aChild, &transientReceivers);
+ if (!transientReceivers) {
+ transientReceivers = new nsCOMArray<nsMutationReceiver>();
+ Observer()->mTransientReceivers.Put(aChild, transientReceivers);
+ } else {
+ 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;
+ }
+ m->mTarget = parent;
+ m->mRemovedNodes = new nsSimpleContentList(parent);
+ m->mRemovedNodes->AppendElement(aChild);
+ m->mPreviousSibling = aPreviousSibling;
+ m->mNextSibling = parent->GetChildAt(aIndexInContainer);
+ }
+ // We need to schedule always, so that after microtask mTransientReceivers
+ // can be cleared correctly.
+ Observer()->ScheduleForRun();
+}
+
+void nsMutationReceiver::NodeWillBeDestroyed(const nsINode *aNode)
+{
+ NS_ASSERTION(!mParent, "Shouldn't have mParent here!");
+ Disconnect(true);
+}
+
+void
+nsAnimationReceiver::RecordAnimationMutation(Animation* aAnimation,
+ AnimationMutation aMutationType)
+{
+ mozilla::dom::AnimationEffectReadOnly* effect = aAnimation->GetEffect();
+ if (!effect) {
+ return;
+ }
+
+ mozilla::dom::KeyframeEffectReadOnly* keyframeEffect =
+ effect->AsKeyframeEffect();
+ if (!keyframeEffect) {
+ return;
+ }
+
+ Maybe<NonOwningAnimationTarget> animationTarget = keyframeEffect->GetTarget();
+ 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 != mozilla::CSSPseudoElementType::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_SCRIPT_OBJECTS
+ 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();
+}
+
+void
+nsDOMMutationObserver::RescheduleForRun()
+{
+ if (!sScheduledMutationObservers) {
+ 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 mozilla::dom::MutationObserverInit& aOptions,
+ mozilla::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 nativeAnonymousChildList = aOptions.mNativeAnonymousChildList;
+ bool characterDataOldValue =
+ aOptions.mCharacterDataOldValue.WasPassed() &&
+ aOptions.mCharacterDataOldValue.Value();
+ bool animations = aOptions.mAnimations;
+
+ 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 ||
+ nativeAnonymousChildList)) {
+ aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+ return;
+ }
+
+ if (aOptions.mAttributeOldValue.WasPassed() &&
+ aOptions.mAttributeOldValue.Value() &&
+ aOptions.mAttributes.WasPassed() &&
+ !aOptions.mAttributes.Value()) {
+ aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+ return;
+ }
+
+ if (aOptions.mAttributeFilter.WasPassed() &&
+ aOptions.mAttributes.WasPassed() &&
+ !aOptions.mAttributes.Value()) {
+ aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+ return;
+ }
+
+ if (aOptions.mCharacterDataOldValue.WasPassed() &&
+ aOptions.mCharacterDataOldValue.Value() &&
+ aOptions.mCharacterData.WasPassed() &&
+ !aOptions.mCharacterData.Value()) {
+ aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+ return;
+ }
+
+ nsCOMArray<nsIAtom> filters;
+ bool allAttrs = true;
+ if (aOptions.mAttributeFilter.WasPassed()) {
+ allAttrs = false;
+ const mozilla::dom::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->SetNativeAnonymousChildList(nativeAnonymousChildList);
+ r->SetAttributeFilter(Move(filters));
+ r->SetAllAttributes(allAttrs);
+ r->SetAnimations(animations);
+ r->RemoveClones();
+
+#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() = current.forget();
+ }
+ 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.mNativeAnonymousChildList = mr->NativeAnonymousChildList();
+ info.mAnimations = mr->Animations();
+ nsCOMArray<nsIAtom>& filters = mr->AttributeFilter();
+ if (filters.Count()) {
+ info.mAttributeFilter.Construct();
+ mozilla::dom::Sequence<nsString>& filtersAsStrings =
+ info.mAttributeFilter.Value();
+ nsString* strings = filtersAsStrings.AppendElements(filters.Count(),
+ mozilla::fallible);
+ if (!strings) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ for (int32_t j = 0; j < filters.Count(); ++j) {
+ filters[j]->ToString(strings[j]);
+ }
+ }
+ info.mObservedNode = mr->Target();
+ }
+}
+
+// static
+already_AddRefed<nsDOMMutationObserver>
+nsDOMMutationObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal,
+ mozilla::dom::MutationCallback& aCb,
+ mozilla::ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ MOZ_ASSERT(window->IsInnerWindow());
+ bool isChrome = nsContentUtils::IsChromeDoc(window->GetExtantDoc());
+ RefPtr<nsDOMMutationObserver> observer =
+ new nsDOMMutationObserver(window.forget(), aCb, isChrome);
+ return observer.forget();
+}
+
+
+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();
+
+ mCallback->Call(this, mutations, *this);
+}
+
+class AsyncMutationHandler : public mozilla::Runnable
+{
+public:
+ NS_IMETHOD Run() override
+ {
+ nsDOMMutationObserver::HandleMutations();
+ return NS_OK;
+ }
+};
+
+void
+nsDOMMutationObserver::HandleMutationsInternal()
+{
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
+ return;
+ }
+ static RefPtr<nsDOMMutationObserver> sCurrentObserver;
+ if (sCurrentObserver && !sCurrentObserver->Suppressed()) {
+ // In normal cases sScheduledMutationObservers will be handled
+ // after previous mutations are handled. But in case some
+ // callback calls a sync API, which spins the eventloop, we need to still
+ // process other mutations happening during that sync call.
+ // This does *not* catch all cases, but should work for stuff running
+ // in separate tabs.
+ return;
+ }
+
+ mozilla::AutoSlowOperation aso;
+
+ nsTArray<RefPtr<nsDOMMutationObserver> >* suppressedObservers = nullptr;
+
+ while (sScheduledMutationObservers) {
+ AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* observers =
+ sScheduledMutationObservers;
+ sScheduledMutationObservers = nullptr;
+ for (uint32_t i = 0; i < observers->Length(); ++i) {
+ sCurrentObserver = static_cast<nsDOMMutationObserver*>((*observers)[i]);
+ if (!sCurrentObserver->Suppressed()) {
+ sCurrentObserver->HandleMutation();
+ } else {
+ if (!suppressedObservers) {
+ suppressedObservers = new nsTArray<RefPtr<nsDOMMutationObserver> >;
+ }
+ if (!suppressedObservers->Contains(sCurrentObserver)) {
+ suppressedObservers->AppendElement(sCurrentObserver);
+ }
+ }
+ }
+ delete observers;
+ aso.CheckForInterrupt();
+ }
+
+ if (suppressedObservers) {
+ for (uint32_t i = 0; i < suppressedObservers->Length(); ++i) {
+ static_cast<nsDOMMutationObserver*>(suppressedObservers->ElementAt(i))->
+ RescheduleForRun();
+ }
+ delete suppressedObservers;
+ suppressedObservers = nullptr;
+ }
+ sCurrentObserver = nullptr;
+}
+
+nsDOMMutationRecord*
+nsDOMMutationObserver::CurrentRecord(nsIAtom* 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->ElementAt(sMutationLevel - 1);
+ 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.RemoveElementAt(sMutationLevel - 1);
+ }
+ }
+ sCurrentlyHandlingObservers->RemoveElementAt(sMutationLevel - 1);
+ }
+ --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()) {
+ nsCOMArray<nsMutationReceiver>* transientReceivers = nullptr;
+ ob->mTransientReceivers.Get(removed, &transientReceivers);
+ if (!transientReceivers) {
+ transientReceivers = new nsCOMArray<nsMutationReceiver>();
+ ob->mTransientReceivers.Put(removed, transientReceivers);
+ }
+ 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 000000000..cde32c57b
--- /dev/null
+++ b/dom/base/nsDOMMutationObserver.h
@@ -0,0 +1,927 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/Attributes.h"
+#include "mozilla/Move.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsPIDOMWindow.h"
+#include "nsIScriptContext.h"
+#include "nsStubAnimationObserver.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsIVariant.h"
+#include "nsContentList.h"
+#include "mozilla/dom/Element.h"
+#include "nsClassHashtable.h"
+#include "nsNodeUtils.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/MutationObserverBinding.h"
+#include "nsIDocument.h"
+#include "mozilla/dom/Animation.h"
+#include "nsIAnimationObserver.h"
+
+class nsDOMMutationObserver;
+using mozilla::dom::MutationObservingInfo;
+
+class nsDOMMutationRecord final : public nsISupports,
+ public nsWrapperCache
+{
+ virtual ~nsDOMMutationRecord() {}
+
+public:
+ typedef nsTArray<RefPtr<mozilla::dom::Animation>> AnimationArray;
+
+ nsDOMMutationRecord(nsIAtom* aType, nsISupports* aOwner)
+ : mType(aType), mAttrNamespace(NullString()), mPrevValue(NullString()), mOwner(aOwner)
+ {
+ }
+
+ nsISupports* GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::MutationRecordBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationRecord)
+
+ void GetType(mozilla::dom::DOMString& aRetVal) const
+ {
+ aRetVal.SetOwnedAtom(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.SetOwnedAtom(mAttrName, mozilla::dom::DOMString::eTreatNullAsNull);
+ }
+
+ void GetAttributeNamespace(mozilla::dom::DOMString& aRetVal) const
+ {
+ aRetVal.SetOwnedString(mAttrNamespace);
+ }
+
+ void GetOldValue(mozilla::dom::DOMString& aRetVal) const
+ {
+ aRetVal.SetOwnedString(mPrevValue);
+ }
+
+ void GetAddedAnimations(AnimationArray& aRetVal) const
+ {
+ aRetVal = mAddedAnimations;
+ }
+
+ void GetRemovedAnimations(AnimationArray& aRetVal) const
+ {
+ aRetVal = mRemovedAnimations;
+ }
+
+ void GetChangedAnimations(AnimationArray& aRetVal) const
+ {
+ aRetVal = mChangedAnimations;
+ }
+
+ nsCOMPtr<nsINode> mTarget;
+ nsCOMPtr<nsIAtom> mType;
+ nsCOMPtr<nsIAtom> 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() { }
+
+ 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()
+ {
+ return mParent ? mParent->CharacterDataOldValue() : mCharacterDataOldValue;
+ }
+ void SetCharacterDataOldValue(bool aOldValue)
+ {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mCharacterDataOldValue = aOldValue;
+ }
+
+ bool NativeAnonymousChildList()
+ {
+ return mParent ? mParent->NativeAnonymousChildList() : mNativeAnonymousChildList;
+ }
+ void SetNativeAnonymousChildList(bool aOldValue)
+ {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mNativeAnonymousChildList = aOldValue;
+ }
+
+ bool Attributes() { return mParent ? mParent->Attributes() : mAttributes; }
+ void SetAttributes(bool aAttributes)
+ {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAttributes = aAttributes;
+ }
+
+ bool AllAttributes()
+ {
+ return mParent ? mParent->AllAttributes()
+ : mAllAttributes;
+ }
+ void SetAllAttributes(bool aAll)
+ {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAllAttributes = aAll;
+ }
+
+ bool Animations() { return mParent ? mParent->Animations() : mAnimations; }
+ void SetAnimations(bool aAnimations)
+ {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAnimations = aAnimations;
+ }
+
+ bool AttributeOldValue() {
+ return mParent ? mParent->AttributeOldValue()
+ : mAttributeOldValue;
+ }
+ void SetAttributeOldValue(bool aOldValue)
+ {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAttributeOldValue = aOldValue;
+ }
+
+ nsCOMArray<nsIAtom>& AttributeFilter() { return mAttributeFilter; }
+ void SetAttributeFilter(nsCOMArray<nsIAtom>&& aFilter)
+ {
+ NS_ASSERTION(!mParent, "Shouldn't have parent");
+ mAttributeFilter.Clear();
+ mAttributeFilter = mozilla::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)
+ {
+ }
+
+ nsMutationReceiverBase(nsINode* aRegisterTarget,
+ nsMutationReceiverBase* aParent)
+ : mTarget(nullptr), mObserver(nullptr), mParent(aParent),
+ mRegisterTarget(aRegisterTarget), mKungFuDeathGrip(aParent->Target())
+ {
+ 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,
+ nsIAtom* 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;
+ }
+
+ nsCOMArray<nsIAtom>& filters = AttributeFilter();
+ for (int32_t i = 0; i < filters.Count(); ++i) {
+ if (filters[i] == aAttr) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // 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:
+ bool mSubtree;
+ bool mChildList;
+ bool mCharacterData;
+ bool mCharacterDataOldValue;
+ bool mNativeAnonymousChildList;
+ bool mAttributes;
+ bool mAllAttributes;
+ bool mAttributeOldValue;
+ bool mAnimations;
+ nsCOMArray<nsIAtom> mAttributeFilter;
+};
+
+
+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_AND_IMPL_ZEROING_OPERATOR_NEW
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
+ NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ virtual void AttributeSetToCurrentValue(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute) override
+ {
+ // We can reuse AttributeWillChange implementation.
+ AttributeWillChange(aDocument, aElement, aNameSpaceID, aAttribute,
+ nsIDOMMutationEvent::MODIFICATION, nullptr);
+ }
+
+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() {}
+
+ 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(already_AddRefed<nsPIDOMWindowInner>&& aOwner,
+ mozilla::dom::MutationCallback& aCb,
+ bool aChrome)
+ : mOwner(aOwner), mLastPendingMutation(nullptr), mPendingMutationCount(0),
+ mCallback(&aCb), mWaitingForRun(false), mIsChrome(aChrome),
+ 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& aGlobal,
+ mozilla::dom::MutationCallback& aCb,
+ mozilla::ErrorResult& aRv);
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::MutationObserverBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+ nsISupports* GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ bool IsChrome()
+ {
+ return mIsChrome;
+ }
+
+ void Observe(nsINode& aTarget,
+ const mozilla::dom::MutationObserverInit& aOptions,
+ mozilla::ErrorResult& aRv);
+
+ void Disconnect();
+
+ void TakeRecords(nsTArray<RefPtr<nsDOMMutationRecord> >& aRetVal);
+
+ void HandleMutation();
+
+ void GetObservingInfo(nsTArray<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 = record.forget();
+ mLastPendingMutation = mFirstPendingMutation;
+ } else {
+ MOZ_ASSERT(mFirstPendingMutation);
+ mLastPendingMutation->mNext = record.forget();
+ mLastPendingMutation = mLastPendingMutation->mNext;
+ }
+ ++mPendingMutationCount;
+ }
+
+ void ClearPendingRecords()
+ {
+ mFirstPendingMutation = nullptr;
+ mLastPendingMutation = nullptr;
+ mPendingMutationCount = 0;
+ }
+
+ // static methods
+ static void HandleMutations()
+ {
+ if (sScheduledMutationObservers) {
+ HandleMutationsInternal();
+ }
+ }
+
+ 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);
+
+ already_AddRefed<nsIVariant> TakeRecords();
+
+ void GetAllSubtreeObserversFor(nsINode* aNode,
+ nsTArray<nsMutationReceiver*>& aObservers);
+ void ScheduleForRun();
+ void RescheduleForRun();
+
+ nsDOMMutationRecord* CurrentRecord(nsIAtom* aType);
+ bool HasCurrentRecord(const nsAString& aType);
+
+ bool Suppressed()
+ {
+ if (mOwner) {
+ nsCOMPtr<nsIDocument> d = mOwner->GetExtantDoc();
+ return d && d->IsInSyncOperation();
+ }
+ return false;
+ }
+
+ static void HandleMutationsInternal();
+
+ 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 mIsChrome;
+ bool mMergeAttributeRecords;
+
+ uint64_t mId;
+
+ static uint64_t sCount;
+ static AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* sScheduledMutationObservers;
+ static nsDOMMutationObserver* sCurrentObserver;
+
+ 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(nsIDocument* aDocument)
+ {
+ Init(aDocument);
+ }
+
+ void Init(nsIDocument* 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;
+ default:
+ NS_NOTREACHED("shouldn't have observed an animation being added "
+ "twice");
+ }
+ } 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;
+ default:
+ NS_NOTREACHED("shouldn't have observed an animation being removed "
+ "twice");
+ }
+ } 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.LookupOrAdd(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;
+ typedef nsTArray<Entry> EntryArray;
+ 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);
+}
+
+#endif
diff --git a/dom/base/nsDOMNavigationTiming.cpp b/dom/base/nsDOMNavigationTiming.cpp
new file mode 100644
index 000000000..31b2932fb
--- /dev/null
+++ b/dom/base/nsDOMNavigationTiming.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "prtime.h"
+#include "nsIURI.h"
+#include "nsPrintfCString.h"
+#include "mozilla/dom/PerformanceNavigation.h"
+#include "mozilla/TimeStamp.h"
+
+nsDOMNavigationTiming::nsDOMNavigationTiming()
+{
+ Clear();
+}
+
+nsDOMNavigationTiming::~nsDOMNavigationTiming()
+{
+}
+
+void
+nsDOMNavigationTiming::Clear()
+{
+ mNavigationType = TYPE_RESERVED;
+ mNavigationStartHighRes = 0;
+ mBeforeUnloadStart = 0;
+ mUnloadStart = 0;
+ mUnloadEnd = 0;
+ mLoadEventStart = 0;
+ mLoadEventEnd = 0;
+ mDOMLoading = 0;
+ mDOMInteractive = 0;
+ mDOMContentLoadedEventStart = 0;
+ mDOMContentLoadedEventEnd = 0;
+ mDOMComplete = 0;
+
+ mLoadEventStartSet = false;
+ mLoadEventEndSet = false;
+ mDOMLoadingSet = false;
+ mDOMInteractiveSet = false;
+ mDOMContentLoadedEventStartSet = false;
+ mDOMContentLoadedEventEndSet = false;
+ mDOMCompleteSet = false;
+ mDocShellHasBeenActiveSinceNavigationStart = false;
+}
+
+DOMTimeMilliSec
+nsDOMNavigationTiming::TimeStampToDOM(mozilla::TimeStamp aStamp) const
+{
+ if (aStamp.IsNull()) {
+ return 0;
+ }
+ mozilla::TimeDuration duration = aStamp - mNavigationStartTimeStamp;
+ return GetNavigationStart() + static_cast<int64_t>(duration.ToMilliseconds());
+}
+
+DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart()
+{
+ return TimeStampToDOM(mozilla::TimeStamp::Now());
+}
+
+void
+nsDOMNavigationTiming::NotifyNavigationStart(DocShellState aDocShellState)
+{
+ mNavigationStartHighRes = (double)PR_Now() / PR_USEC_PER_MSEC;
+ mNavigationStartTimeStamp = mozilla::TimeStamp::Now();
+ mDocShellHasBeenActiveSinceNavigationStart = (aDocShellState == DocShellState::eActive);
+}
+
+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::NotifyBeforeUnload()
+{
+ mBeforeUnloadStart = DurationFromStart();
+}
+
+void
+nsDOMNavigationTiming::NotifyUnloadAccepted(nsIURI* aOldURI)
+{
+ mUnloadStart = mBeforeUnloadStart;
+ mUnloadedURI = aOldURI;
+}
+
+void
+nsDOMNavigationTiming::NotifyUnloadEventStart()
+{
+ mUnloadStart = DurationFromStart();
+}
+
+void
+nsDOMNavigationTiming::NotifyUnloadEventEnd()
+{
+ mUnloadEnd = DurationFromStart();
+}
+
+void
+nsDOMNavigationTiming::NotifyLoadEventStart()
+{
+ if (!mLoadEventStartSet) {
+ mLoadEventStart = DurationFromStart();
+ mLoadEventStartSet = true;
+ }
+}
+
+void
+nsDOMNavigationTiming::NotifyLoadEventEnd()
+{
+ if (!mLoadEventEndSet) {
+ mLoadEventEnd = DurationFromStart();
+ mLoadEventEndSet = true;
+ }
+}
+
+void
+nsDOMNavigationTiming::SetDOMLoadingTimeStamp(nsIURI* aURI, mozilla::TimeStamp aValue)
+{
+ if (!mDOMLoadingSet) {
+ mLoadedURI = aURI;
+ mDOMLoading = TimeStampToDOM(aValue);
+ mDOMLoadingSet = true;
+ }
+}
+
+void
+nsDOMNavigationTiming::NotifyDOMLoading(nsIURI* aURI)
+{
+ if (!mDOMLoadingSet) {
+ mLoadedURI = aURI;
+ mDOMLoading = DurationFromStart();
+ mDOMLoadingSet = true;
+ }
+}
+
+void
+nsDOMNavigationTiming::NotifyDOMInteractive(nsIURI* aURI)
+{
+ if (!mDOMInteractiveSet) {
+ mLoadedURI = aURI;
+ mDOMInteractive = DurationFromStart();
+ mDOMInteractiveSet = true;
+ }
+}
+
+void
+nsDOMNavigationTiming::NotifyDOMComplete(nsIURI* aURI)
+{
+ if (!mDOMCompleteSet) {
+ mLoadedURI = aURI;
+ mDOMComplete = DurationFromStart();
+ mDOMCompleteSet = true;
+ }
+}
+
+void
+nsDOMNavigationTiming::NotifyDOMContentLoadedStart(nsIURI* aURI)
+{
+ if (!mDOMContentLoadedEventStartSet) {
+ mLoadedURI = aURI;
+ mDOMContentLoadedEventStart = DurationFromStart();
+ mDOMContentLoadedEventStartSet = true;
+ }
+}
+
+void
+nsDOMNavigationTiming::NotifyDOMContentLoadedEnd(nsIURI* aURI)
+{
+ if (!mDOMContentLoadedEventEndSet) {
+ mLoadedURI = aURI;
+ mDOMContentLoadedEventEnd = DurationFromStart();
+ mDOMContentLoadedEventEndSet = true;
+ }
+}
+
+void
+nsDOMNavigationTiming::NotifyNonBlankPaintForRootContentDocument()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mNavigationStartTimeStamp.IsNull());
+
+ if (!mNonBlankPaintTimeStamp.IsNull()) {
+ return;
+ }
+
+ mNonBlankPaintTimeStamp = TimeStamp::Now();
+ TimeDuration elapsed = mNonBlankPaintTimeStamp - mNavigationStartTimeStamp;
+
+ if (profiler_is_active()) {
+ nsAutoCString spec;
+ if (mLoadedURI) {
+ mLoadedURI->GetSpec(spec);
+ }
+ nsPrintfCString marker("Non-blank paint after %dms for URL %s, %s",
+ int(elapsed.ToMilliseconds()), spec.get(),
+ mDocShellHasBeenActiveSinceNavigationStart ? "foreground tab" : "this tab was inactive some of the time between navigation start and first non-blank paint");
+ PROFILER_MARKER(marker.get());
+ }
+
+ if (mDocShellHasBeenActiveSinceNavigationStart) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_NON_BLANK_PAINT_MS,
+ mNavigationStartTimeStamp,
+ mNonBlankPaintTimeStamp);
+ }
+}
+
+void
+nsDOMNavigationTiming::NotifyDocShellStateChanged(DocShellState aDocShellState)
+{
+ mDocShellHasBeenActiveSinceNavigationStart &=
+ (aDocShellState == DocShellState::eActive);
+}
+
+DOMTimeMilliSec
+nsDOMNavigationTiming::GetUnloadEventStart()
+{
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsresult rv = ssm->CheckSameOriginURI(mLoadedURI, mUnloadedURI, false);
+ if (NS_SUCCEEDED(rv)) {
+ return mUnloadStart;
+ }
+ return 0;
+}
+
+DOMTimeMilliSec
+nsDOMNavigationTiming::GetUnloadEventEnd()
+{
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsresult rv = ssm->CheckSameOriginURI(mLoadedURI, mUnloadedURI, false);
+ if (NS_SUCCEEDED(rv)) {
+ return mUnloadEnd;
+ }
+ return 0;
+}
diff --git a/dom/base/nsDOMNavigationTiming.h b/dom/base/nsDOMNavigationTiming.h
new file mode 100644
index 000000000..9babece96
--- /dev/null
+++ b/dom/base/nsDOMNavigationTiming.h
@@ -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/. */
+
+#ifndef nsDOMNavigationTiming_h___
+#define nsDOMNavigationTiming_h___
+
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIURI;
+
+typedef unsigned long long DOMTimeMilliSec;
+typedef double DOMHighResTimeStamp;
+
+class nsDOMNavigationTiming final
+{
+public:
+ enum Type {
+ TYPE_NAVIGATE = 0,
+ TYPE_RELOAD = 1,
+ TYPE_BACK_FORWARD = 2,
+ TYPE_RESERVED = 255,
+ };
+
+ nsDOMNavigationTiming();
+
+ 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 mNavigationStartTimeStamp;
+ }
+
+ DOMTimeMilliSec GetUnloadEventStart();
+ DOMTimeMilliSec GetUnloadEventEnd();
+ DOMTimeMilliSec GetDomLoading() const
+ {
+ return mDOMLoading;
+ }
+ DOMTimeMilliSec GetDomInteractive() const
+ {
+ return mDOMInteractive;
+ }
+ DOMTimeMilliSec GetDomContentLoadedEventStart() const
+ {
+ return mDOMContentLoadedEventStart;
+ }
+ DOMTimeMilliSec GetDomContentLoadedEventEnd() const
+ {
+ return mDOMContentLoadedEventEnd;
+ }
+ DOMTimeMilliSec GetDomComplete() const
+ {
+ return mDOMComplete;
+ }
+ DOMTimeMilliSec GetLoadEventStart() const
+ {
+ return mLoadEventStart;
+ }
+ DOMTimeMilliSec GetLoadEventEnd() const
+ {
+ return mLoadEventEnd;
+ }
+
+ enum class DocShellState : uint8_t {
+ eActive,
+ eInactive
+ };
+
+ void NotifyNavigationStart(DocShellState aDocShellState);
+ void NotifyFetchStart(nsIURI* aURI, Type aNavigationType);
+ 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);
+
+ void NotifyNonBlankPaintForRootContentDocument();
+ void NotifyDocShellStateChanged(DocShellState aDocShellState);
+
+ DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const;
+
+ inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp)
+ {
+ mozilla::TimeDuration duration = aStamp - mNavigationStartTimeStamp;
+ return duration.ToMilliseconds();
+ }
+
+private:
+ nsDOMNavigationTiming(const nsDOMNavigationTiming &) = delete;
+ ~nsDOMNavigationTiming();
+
+ void Clear();
+
+ nsCOMPtr<nsIURI> mUnloadedURI;
+ nsCOMPtr<nsIURI> mLoadedURI;
+
+ Type mNavigationType;
+ DOMHighResTimeStamp mNavigationStartHighRes;
+ mozilla::TimeStamp mNavigationStartTimeStamp;
+ mozilla::TimeStamp mNonBlankPaintTimeStamp;
+ DOMTimeMilliSec DurationFromStart();
+
+ DOMTimeMilliSec mBeforeUnloadStart;
+ DOMTimeMilliSec mUnloadStart;
+ DOMTimeMilliSec mUnloadEnd;
+ DOMTimeMilliSec mLoadEventStart;
+ DOMTimeMilliSec mLoadEventEnd;
+
+ DOMTimeMilliSec mDOMLoading;
+ DOMTimeMilliSec mDOMInteractive;
+ DOMTimeMilliSec mDOMContentLoadedEventStart;
+ DOMTimeMilliSec mDOMContentLoadedEventEnd;
+ DOMTimeMilliSec mDOMComplete;
+
+ // Booleans to keep track of what things we've already been notified
+ // about. We don't update those once we've been notified about them
+ // once.
+ bool mLoadEventStartSet : 1;
+ bool mLoadEventEndSet : 1;
+ bool mDOMLoadingSet : 1;
+ bool mDOMInteractiveSet : 1;
+ bool mDOMContentLoadedEventStartSet : 1;
+ bool mDOMContentLoadedEventEndSet : 1;
+ bool mDOMCompleteSet : 1;
+ bool mDocShellHasBeenActiveSinceNavigationStart : 1;
+};
+
+#endif /* nsDOMNavigationTiming_h___ */
diff --git a/dom/base/nsDOMSerializer.cpp b/dom/base/nsDOMSerializer.cpp
new file mode 100644
index 000000000..f3b95a607
--- /dev/null
+++ b/dom/base/nsDOMSerializer.cpp
@@ -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/. */
+
+#include "nsDOMSerializer.h"
+
+#include "nsIDocument.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDOMDocument.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentCID.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsINode.h"
+
+using namespace mozilla;
+
+nsDOMSerializer::nsDOMSerializer()
+{
+}
+
+nsDOMSerializer::~nsDOMSerializer()
+{
+}
+
+// QueryInterface implementation for nsDOMSerializer
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMSerializer)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMSerializer)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMSerializer, mOwner)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMSerializer)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMSerializer)
+
+
+static nsresult
+SetUpEncoder(nsIDOMNode *aRoot, const nsACString& aCharset,
+ nsIDocumentEncoder **aEncoder)
+{
+ *aEncoder = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIDocumentEncoder> encoder =
+ do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "application/xhtml+xml", &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool entireDocument = true;
+ nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(aRoot));
+ if (!domDoc) {
+ entireDocument = false;
+ rv = aRoot->GetOwnerDocument(getter_AddRefs(domDoc));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // This method will fail if no document
+ rv = encoder->Init(domDoc, NS_LITERAL_STRING("application/xhtml+xml"),
+ nsIDocumentEncoder::OutputRaw |
+ nsIDocumentEncoder::OutputDontRewriteEncodingDeclaration);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString charset(aCharset);
+ if (charset.IsEmpty()) {
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+ NS_ASSERTION(doc, "Need a document");
+ charset = doc->GetDocumentCharacterSet();
+ }
+ rv = encoder->SetCharset(charset);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // If we are working on the entire document we do not need to
+ // specify which part to serialize
+ if (!entireDocument) {
+ rv = encoder->SetNode(aRoot);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ encoder.forget(aEncoder);
+ }
+
+ return rv;
+}
+
+void
+nsDOMSerializer::SerializeToString(nsINode& aRoot, nsAString& aStr,
+ ErrorResult& rv)
+{
+ rv = nsDOMSerializer::SerializeToString(aRoot.AsDOMNode(), aStr);
+}
+
+NS_IMETHODIMP
+nsDOMSerializer::SerializeToString(nsIDOMNode *aRoot, nsAString& _retval)
+{
+ NS_ENSURE_ARG_POINTER(aRoot);
+
+ _retval.Truncate();
+
+ if (!nsContentUtils::CanCallerAccess(aRoot)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsIDocumentEncoder> encoder;
+ nsresult rv = SetUpEncoder(aRoot, EmptyCString(), getter_AddRefs(encoder));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return encoder->EncodeToString(_retval);
+}
+
+void
+nsDOMSerializer::SerializeToStream(nsINode& aRoot, nsIOutputStream* aStream,
+ const nsAString& aCharset, ErrorResult& rv)
+{
+ rv = nsDOMSerializer::SerializeToStream(aRoot.AsDOMNode(), aStream,
+ NS_ConvertUTF16toUTF8(aCharset));
+}
+
+NS_IMETHODIMP
+nsDOMSerializer::SerializeToStream(nsIDOMNode *aRoot,
+ nsIOutputStream *aStream,
+ const nsACString& aCharset)
+{
+ NS_ENSURE_ARG_POINTER(aRoot);
+ NS_ENSURE_ARG_POINTER(aStream);
+ // The charset arg can be empty, in which case we get the document's
+ // charset and use that when serializing.
+
+ if (!nsContentUtils::CanCallerAccess(aRoot)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsIDocumentEncoder> encoder;
+ nsresult rv = SetUpEncoder(aRoot, aCharset, getter_AddRefs(encoder));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return encoder->EncodeToStream(aStream);
+}
diff --git a/dom/base/nsDOMSerializer.h b/dom/base/nsDOMSerializer.h
new file mode 100644
index 000000000..efcf895e5
--- /dev/null
+++ b/dom/base/nsDOMSerializer.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 nsDOMSerializer_h_
+#define nsDOMSerializer_h_
+
+#include "nsIDOMSerializer.h"
+#include "nsWrapperCache.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/XMLSerializerBinding.h"
+
+class nsINode;
+
+class nsDOMSerializer final : public nsIDOMSerializer,
+ public nsWrapperCache
+{
+public:
+ nsDOMSerializer();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMSerializer)
+
+ // nsIDOMSerializer
+ NS_DECL_NSIDOMSERIALIZER
+
+ // WebIDL API
+ static already_AddRefed<nsDOMSerializer>
+ Constructor(const mozilla::dom::GlobalObject& aOwner,
+ mozilla::ErrorResult& rv)
+ {
+ RefPtr<nsDOMSerializer> domSerializer = new nsDOMSerializer(aOwner.GetAsSupports());
+ return domSerializer.forget();
+ }
+
+ void
+ SerializeToString(nsINode& aRoot, nsAString& aStr,
+ mozilla::ErrorResult& rv);
+
+ void
+ SerializeToStream(nsINode& aRoot, nsIOutputStream* aStream,
+ const nsAString& aCharset, mozilla::ErrorResult& rv);
+
+ nsISupports* GetParentObject() const
+ {
+ return mOwner;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return mozilla::dom::XMLSerializerBinding::Wrap(aCx, this, aGivenProto);
+ }
+
+private:
+ virtual ~nsDOMSerializer();
+
+ explicit nsDOMSerializer(nsISupports* aOwner) : mOwner(aOwner)
+ {
+ MOZ_ASSERT(aOwner);
+ }
+
+ nsCOMPtr<nsISupports> mOwner;
+};
+
+
+#endif
+
diff --git a/dom/base/nsDOMString.h b/dom/base/nsDOMString.h
new file mode 100644
index 000000000..2524272ef
--- /dev/null
+++ b/dom/base/nsDOMString.h
@@ -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/. */
+
+#ifndef nsDOMString_h___
+#define nsDOMString_h___
+
+#include "nsStringGlue.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 000000000..39ff60e12
--- /dev/null
+++ b/dom/base/nsDOMTokenList.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsError.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, nsIAtom* 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() { }
+
+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;
+}
+
+uint32_t
+nsDOMTokenList::Length()
+{
+ const nsAttrValue* attr = GetParsedAttr();
+ if (!attr) {
+ return 0;
+ }
+
+ 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 = true;
+ attr->AtomAt(aIndex)->ToString(aResult);
+ } else {
+ aFound = false;
+ }
+}
+
+void
+nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv)
+{
+ if (!mElement) {
+ return;
+ }
+
+ rv = mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true);
+}
+
+nsresult
+nsDOMTokenList::CheckToken(const nsAString& aStr)
+{
+ if (aStr.IsEmpty()) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ nsAString::const_iterator iter, end;
+ aStr.BeginReading(iter);
+ aStr.EndReading(end);
+
+ while (iter != end) {
+ if (nsContentUtils::IsHTMLWhitespace(*iter))
+ return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
+ ++iter;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens)
+{
+ for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
+ nsresult rv = CheckToken(aTokens[i]);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+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) {
+ aAttr->ToString(resultStr);
+ }
+
+ bool oneWasAdded = false;
+ 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 (oneWasAdded ||
+ (!resultStr.IsEmpty() &&
+ !nsContentUtils::IsHTMLWhitespace(resultStr.Last()))) {
+ resultStr.Append(' ');
+ resultStr.Append(aToken);
+ } else {
+ resultStr.Append(aToken);
+ }
+
+ oneWasAdded = true;
+ addedClasses.AppendElement(aToken);
+ }
+
+ mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
+}
+
+void
+nsDOMTokenList::Add(const nsTArray<nsString>& aTokens, ErrorResult& aError)
+{
+ aError = CheckTokens(aTokens);
+ 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");
+
+ nsAutoString input;
+ aAttr->ToString(input);
+
+ WhitespaceTokenizer tokenizer(input);
+ nsAutoString output;
+
+ while (tokenizer.hasMoreTokens()) {
+ auto& currentToken = tokenizer.nextToken();
+ if (!aTokens.Contains(currentToken)) {
+ if (!output.IsEmpty()) {
+ output.Append(char16_t(' '));
+ }
+ output.Append(currentToken);
+ }
+ }
+
+ mElement->SetAttr(kNameSpaceID_None, mAttrAtom, output, true);
+}
+
+void
+nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens, ErrorResult& aError)
+{
+ aError = CheckTokens(aTokens);
+ 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)
+{
+ aError = CheckToken(aToken);
+ 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;
+}
+
+void
+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.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ aError = CheckToken(aToken);
+ if (aError.Failed()) {
+ return;
+ }
+
+ aError = CheckToken(aNewToken);
+ if (aError.Failed()) {
+ return;
+ }
+
+ const nsAttrValue* attr = GetParsedAttr();
+ if (!attr) {
+ return;
+ }
+
+ ReplaceInternal(attr, aToken, aNewToken);
+}
+
+void
+nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr,
+ const nsAString& aToken,
+ const nsAString& aNewToken)
+{
+ nsAutoString attribute;
+ aAttr->ToString(attribute);
+
+ nsAutoString result;
+ WhitespaceTokenizer tokenizer(attribute);
+
+ bool sawIt = false;
+ while (tokenizer.hasMoreTokens()) {
+ auto currentToken = tokenizer.nextToken();
+ if (currentToken.Equals(aToken) || currentToken.Equals(aNewToken)) {
+ if (!sawIt) {
+ sawIt = true;
+ if (!result.IsEmpty()) {
+ result.Append(char16_t(' '));
+ }
+ result.Append(aNewToken);
+ }
+ } else {
+ if (!result.IsEmpty()) {
+ result.Append(char16_t(' '));
+ }
+ result.Append(currentToken);
+ }
+ }
+
+ if (sawIt) {
+ mElement->SetAttr(kNameSpaceID_None, mAttrAtom, result, true);
+ }
+}
+
+bool
+nsDOMTokenList::Supports(const nsAString& aToken,
+ ErrorResult& aError)
+{
+ if (!mSupportedTokens) {
+ aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>(
+ mElement->LocalName(),
+ nsDependentAtomString(mAttrAtom));
+ return false;
+ }
+
+ for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens;
+ *supportedToken;
+ ++supportedToken) {
+ if (aToken.LowerCaseEqualsASCII(*supportedToken)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsDOMTokenList::Stringify(nsAString& aResult)
+{
+ if (!mElement) {
+ aResult.Truncate();
+ return;
+ }
+
+ mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult);
+}
+
+JSObject*
+nsDOMTokenList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return DOMTokenListBinding::Wrap(cx, this, aGivenProto);
+}
+
diff --git a/dom/base/nsDOMTokenList.h b/dom/base/nsDOMTokenList.h
new file mode 100644
index 000000000..e44e042d5
--- /dev/null
+++ b/dom/base/nsDOMTokenList.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/. */
+
+/*
+ * 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/Element.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMTokenListSupportedTokens.h"
+
+namespace mozilla {
+class ErrorResult;
+
+} // namespace mozilla
+
+class nsAttrValue;
+class nsIAtom;
+
+// nsISupports must be on the primary inheritance chain
+
+class nsDOMTokenList : public nsISupports,
+ public nsWrapperCache
+{
+protected:
+ typedef mozilla::dom::Element Element;
+ typedef nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace>
+ WhitespaceTokenizer;
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMTokenList)
+
+ nsDOMTokenList(Element* aElement, nsIAtom* aAttrAtom,
+ const mozilla::dom::DOMTokenListSupportedTokenArray = nullptr);
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ Element* GetParentObject()
+ {
+ return mElement;
+ }
+
+ 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);
+ void 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) { Stringify(aResult); }
+ void SetValue(const nsAString& aValue, mozilla::ErrorResult& rv);
+ void Stringify(nsAString& aResult);
+
+protected:
+ virtual ~nsDOMTokenList();
+
+ nsresult CheckToken(const nsAString& aStr);
+ nsresult CheckTokens(const nsTArray<nsString>& aStr);
+ void AddInternal(const nsAttrValue* aAttr,
+ const nsTArray<nsString>& aTokens);
+ void RemoveInternal(const nsAttrValue* aAttr,
+ const nsTArray<nsString>& aTokens);
+ void ReplaceInternal(const nsAttrValue* aAttr,
+ const nsAString& aToken,
+ const nsAString& aNewToken);
+ inline const nsAttrValue* GetParsedAttr();
+
+ nsCOMPtr<Element> mElement;
+ nsCOMPtr<nsIAtom> mAttrAtom;
+ const mozilla::dom::DOMTokenListSupportedTokenArray mSupportedTokens;
+};
+
+#endif // nsDOMTokenList_h___
diff --git a/dom/base/nsDOMWindowList.cpp b/dom/base/nsDOMWindowList.cpp
new file mode 100644
index 000000000..e0094f844
--- /dev/null
+++ b/dom/base/nsDOMWindowList.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/. */
+
+// Local Includes
+#include "nsDOMWindowList.h"
+
+// Helper classes
+#include "nsCOMPtr.h"
+
+// Interfaces needed
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIWebNavigation.h"
+
+nsDOMWindowList::nsDOMWindowList(nsIDocShell *aDocShell)
+{
+ SetDocShell(aDocShell);
+}
+
+nsDOMWindowList::~nsDOMWindowList()
+{
+}
+
+NS_IMPL_ADDREF(nsDOMWindowList)
+NS_IMPL_RELEASE(nsDOMWindowList)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMWindowList)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMWindowCollection)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+nsDOMWindowList::SetDocShell(nsIDocShell* aDocShell)
+{
+ mDocShellNode = aDocShell; // Weak Reference
+
+ return NS_OK;
+}
+
+void
+nsDOMWindowList::EnsureFresh()
+{
+ nsCOMPtr<nsIWebNavigation> shellAsNav = do_QueryInterface(mDocShellNode);
+
+ if (shellAsNav) {
+ nsCOMPtr<nsIDOMDocument> domdoc;
+ shellAsNav->GetDocument(getter_AddRefs(domdoc));
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
+
+ if (doc) {
+ doc->FlushPendingNotifications(Flush_ContentAndNotify);
+ }
+ }
+}
+
+uint32_t
+nsDOMWindowList::GetLength()
+{
+ EnsureFresh();
+
+ NS_ENSURE_TRUE(mDocShellNode, 0);
+
+ int32_t length;
+ nsresult rv = mDocShellNode->GetChildCount(&length);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ return uint32_t(length);
+}
+
+NS_IMETHODIMP
+nsDOMWindowList::GetLength(uint32_t* aLength)
+{
+ *aLength = GetLength();
+ return NS_OK;
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsDOMWindowList::IndexedGetter(uint32_t aIndex)
+{
+ nsCOMPtr<nsIDocShellTreeItem> item = GetDocShellTreeItemAt(aIndex);
+ if (!item) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = item->GetWindow();
+ MOZ_ASSERT(window);
+
+ return window.forget();
+}
+
+NS_IMETHODIMP
+nsDOMWindowList::Item(uint32_t aIndex, mozIDOMWindowProxy** aReturn)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = IndexedGetter(aIndex);
+ window.forget(aReturn);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowList::NamedItem(const nsAString& aName, mozIDOMWindowProxy** aReturn)
+{
+ nsCOMPtr<nsIDocShellTreeItem> item;
+
+ *aReturn = nullptr;
+
+ EnsureFresh();
+
+ if (mDocShellNode) {
+ mDocShellNode->FindChildWithName(aName, false, false, nullptr,
+ nullptr, getter_AddRefs(item));
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject(do_GetInterface(item));
+ if (globalObject) {
+ CallQueryInterface(globalObject.get(), aReturn);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/dom/base/nsDOMWindowList.h b/dom/base/nsDOMWindowList.h
new file mode 100644
index 000000000..77a52e975
--- /dev/null
+++ b/dom/base/nsDOMWindowList.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 nsDOMWindowList_h___
+#define nsDOMWindowList_h___
+
+#include "nsCOMPtr.h"
+#include "nsIDOMWindowCollection.h"
+#include <stdint.h>
+#include "nsIDocShell.h"
+
+class nsIDocShell;
+class nsIDOMWindow;
+
+class nsDOMWindowList : public nsIDOMWindowCollection
+{
+public:
+ explicit nsDOMWindowList(nsIDocShell* aDocShell);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMWINDOWCOLLECTION
+
+ uint32_t GetLength();
+ already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
+
+ //local methods
+ NS_IMETHOD SetDocShell(nsIDocShell* aDocShell);
+ already_AddRefed<nsIDocShellTreeItem> GetDocShellTreeItemAt(uint32_t aIndex)
+ {
+ EnsureFresh();
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ if (mDocShellNode) {
+ mDocShellNode->GetChildAt(aIndex, getter_AddRefs(item));
+ }
+ return item.forget();
+ }
+
+protected:
+ virtual ~nsDOMWindowList();
+
+ // Note: this function may flush and cause mDocShellNode to become null.
+ void EnsureFresh();
+
+ nsIDocShell* mDocShellNode; //Weak Reference
+};
+
+#endif // nsDOMWindowList_h___
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
new file mode 100644
index 000000000..291df5f27
--- /dev/null
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -0,0 +1,4156 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/LayerTransactionChild.h"
+#include "nsPresContext.h"
+#include "nsDOMClassInfoID.h"
+#include "nsError.h"
+#include "nsIDOMEvent.h"
+#include "nsQueryContentEventResult.h"
+#include "nsGlobalWindow.h"
+#include "nsIDocument.h"
+#include "nsFocusManager.h"
+#include "nsFrameManager.h"
+#include "nsRefreshDriver.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsFrame.h"
+#include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "ClientLayerManager.h"
+#include "nsQueryObject.h"
+#ifdef MOZ_FMP4
+#include "MP4Decoder.h"
+#endif
+#include "CubebUtils.h"
+
+#include "nsIScrollableFrame.h"
+
+#include "nsContentUtils.h"
+
+#include "nsIFrame.h"
+#include "nsIWidget.h"
+#include "nsCharsetSource.h"
+#include "nsJSEnvironment.h"
+#include "nsJSUtils.h"
+
+#include "mozilla/ChaosMode.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TouchEvents.h"
+
+#include "nsViewManager.h"
+
+#include "nsIDOMHTMLCanvasElement.h"
+#include "nsLayoutUtils.h"
+#include "nsComputedDOMStyle.h"
+#include "nsIPresShell.h"
+#include "nsCSSProps.h"
+#include "nsTArrayHelpers.h"
+#include "nsIDocShell.h"
+#include "nsIContentViewer.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_X11) && defined(MOZ_WIDGET_GTK)
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#endif
+
+#include "Layers.h"
+#include "gfxPrefs.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/IDBFactoryBinding.h"
+#include "mozilla/dom/IDBMutableFileBinding.h"
+#include "mozilla/dom/IDBMutableFile.h"
+#include "mozilla/dom/IndexedDatabaseManager.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/quota/PersistenceType.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/layers/FrameUniformityData.h"
+#include "mozilla/layers/ShadowLayers.h"
+#include "nsPrintfCString.h"
+#include "nsViewportInfo.h"
+#include "nsIFormControl.h"
+#include "nsIScriptError.h"
+//#include "nsWidgetsCID.h"
+#include "FrameLayerBuilder.h"
+#include "nsDisplayList.h"
+#include "nsROCSSPrimitiveValue.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "GeckoProfiler.h"
+#include "mozilla/Preferences.h"
+#include "nsIContentIterator.h"
+#include "nsIDOMStyleSheet.h"
+#include "nsIStyleSheetService.h"
+#include "nsContentPermissionHelper.h"
+#include "nsCSSPseudoElements.h" // for CSSPseudoElementType
+#include "nsNetUtil.h"
+#include "nsDocument.h"
+#include "HTMLImageElement.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/layers/APZCTreeManager.h" // for layers::ZoomToRectBehavior
+#include "mozilla/dom/Promise.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+
+#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() { };
+
+ 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;
+};
+
+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(nsGlobalWindow *aWindow)
+{
+ nsCOMPtr<nsISupports> supports = do_QueryObject(aWindow);
+ mWindow = do_GetWeakReference(supports);
+ NS_ASSERTION(aWindow->IsOuterWindow(), "How did that happen?");
+}
+
+nsDOMWindowUtils::~nsDOMWindowUtils()
+{
+ OldWindowSize::GetAndRemove(mWindow);
+}
+
+nsIPresShell*
+nsDOMWindowUtils::GetPresShell()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (!window)
+ return nullptr;
+
+ nsIDocShell *docShell = window->GetDocShell();
+ if (!docShell)
+ return nullptr;
+
+ return docShell->GetPresShell();
+}
+
+nsPresContext*
+nsDOMWindowUtils::GetPresContext()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (!window)
+ return nullptr;
+ nsIDocShell *docShell = window->GetDocShell();
+ if (!docShell)
+ return nullptr;
+ RefPtr<nsPresContext> presContext;
+ docShell->GetPresContext(getter_AddRefs(presContext));
+ return presContext;
+}
+
+nsIDocument*
+nsDOMWindowUtils::GetDocument()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (!window) {
+ return nullptr;
+ }
+ return window->GetExtantDoc();
+}
+
+LayerTransactionChild*
+nsDOMWindowUtils::GetLayerTransaction()
+{
+ nsIWidget* widget = GetWidget();
+ if (!widget)
+ return nullptr;
+
+ LayerManager* manager = widget->GetLayerManager();
+ if (!manager)
+ return nullptr;
+
+ ShadowLayerForwarder* forwarder = manager->AsShadowForwarder();
+ return forwarder && forwarder->HasShadowManager() ?
+ forwarder->GetShadowManager() :
+ nullptr;
+}
+
+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;
+
+ nsIDocument* doc = GetDocument();
+ *aIsForced = doc &&
+ doc->GetDocumentCharacterSetSource() >= kCharsetFromParentForced;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetDocumentMetadata(const nsAString& aName,
+ nsAString& aValue)
+{
+ nsIDocument* doc = GetDocument();
+ if (doc) {
+ nsCOMPtr<nsIAtom> name = NS_Atomize(aName);
+ doc->GetHeaderData(name, aValue);
+ return NS_OK;
+ }
+
+ aValue.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::Redraw(uint32_t aCount, uint32_t *aDurationOut)
+{
+ if (aCount == 0)
+ aCount = 1;
+
+ if (nsIPresShell* presShell = GetPresShell()) {
+ nsIFrame *rootFrame = presShell->GetRootFrame();
+
+ if (rootFrame) {
+ PRIntervalTime iStart = PR_IntervalNow();
+
+ for (uint32_t i = 0; i < aCount; i++)
+ rootFrame->InvalidateFrame();
+
+#if defined(MOZ_X11) && defined(MOZ_WIDGET_GTK)
+ XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), False);
+#endif
+
+ *aDurationOut = PR_IntervalToMilliseconds(PR_IntervalNow() - iStart);
+
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::UpdateLayerTree()
+{
+ if (nsIPresShell* presShell = GetPresShell()) {
+ presShell->FlushPendingNotifications(Flush_Display);
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ nsView* view = vm->GetRootView();
+ if (view) {
+ presShell->Paint(view, view->GetBounds(),
+ nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetContentViewerSize(uint32_t *aDisplayWidth, uint32_t *aDisplayHeight)
+{
+ nsIPresShell* 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)
+{
+ nsIDocument* 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::SetDisplayPortForElement(float aXPx, float aYPx,
+ float aWidthPx, float aHeightPx,
+ nsIDOMElement* aElement,
+ uint32_t aPriority)
+{
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+
+ if (!content) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (content->GetUncomposedDoc() != presShell->GetDocument()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DisplayPortPropertyData* currentData =
+ static_cast<DisplayPortPropertyData*>(content->GetProperty(nsGkAtoms::DisplayPort));
+ if (currentData && currentData->mPriority > aPriority) {
+ return NS_OK;
+ }
+
+ nsRect displayport(nsPresContext::CSSPixelsToAppUnits(aXPx),
+ nsPresContext::CSSPixelsToAppUnits(aYPx),
+ nsPresContext::CSSPixelsToAppUnits(aWidthPx),
+ nsPresContext::CSSPixelsToAppUnits(aHeightPx));
+
+ content->SetProperty(nsGkAtoms::DisplayPort,
+ new DisplayPortPropertyData(displayport, aPriority),
+ nsINode::DeleteProperty<DisplayPortPropertyData>);
+
+ if (gfxPrefs::LayoutUseContainersForRootFrames()) {
+ nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+ if (rootScrollFrame &&
+ content == rootScrollFrame->GetContent() &&
+ nsLayoutUtils::UsesAsyncScrolling(rootScrollFrame))
+ {
+ // We are setting a root displayport for a document.
+ // The pres shell needs a special flag set.
+ presShell->SetIgnoreViewportScrolling(true);
+ }
+ }
+
+ nsIFrame* rootFrame = presShell->FrameManager()->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) {
+ LayerManager* manager = widget->GetLayerManager();
+ manager->BeginTransaction();
+ using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
+ nsLayoutUtils::PaintFrame(nullptr, rootFrame, nsRegion(),
+ NS_RGB(255, 255, 255),
+ nsDisplayListBuilderMode::PAINTING,
+ PaintFrameFlags::PAINT_WIDGET_LAYERS |
+ PaintFrameFlags::PAINT_EXISTING_TRANSACTION);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetDisplayPortMarginsForElement(float aLeftMargin,
+ float aTopMargin,
+ float aRightMargin,
+ float aBottomMargin,
+ nsIDOMElement* aElement,
+ uint32_t aPriority)
+{
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+
+ if (!content) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (content->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);
+
+ nsLayoutUtils::SetDisplayPortMargins(content, presShell, displayportMargins,
+ aPriority);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetDisplayPortBaseForElement(int32_t aX,
+ int32_t aY,
+ int32_t aWidth,
+ int32_t aHeight,
+ nsIDOMElement* aElement)
+{
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+
+ if (!content) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (content->GetUncomposedDoc() != presShell->GetDocument()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsLayoutUtils::SetDisplayPortBase(content, nsRect(aX, aY, aWidth, aHeight));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetResolution(float aResolution)
+{
+ if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->SetResolution(aResolution);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetResolutionAndScaleTo(float aResolution)
+{
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->SetResolutionAndScaleTo(aResolution);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetRestoreResolution(float aResolution,
+ uint32_t aDisplayWidth,
+ uint32_t aDisplayHeight)
+{
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->SetRestoreResolution(aResolution,
+ LayoutDeviceIntSize(aDisplayWidth, aDisplayHeight));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetResolution(float* aResolution)
+{
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResolution = presShell->GetResolution();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsResolutionSet(bool* aIsResolutionSet) {
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aIsResolutionSet = presShell->IsResolutionSet();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetIsFirstPaint(bool aIsFirstPaint)
+{
+ if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsIPresShell* presShell = GetPresShell();
+ if (presShell) {
+ presShell->SetIsFirstPaint(aIsFirstPaint);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsFirstPaint(bool *aIsFirstPaint)
+{
+ if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsIPresShell* presShell = GetPresShell();
+ if (presShell) {
+ *aIsFirstPaint = presShell->GetIsFirstPaint();
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPresShellId(uint32_t *aPresShellId)
+{
+ nsIPresShell* presShell = GetPresShell();
+ if (presShell) {
+ *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,
+ uint8_t aOptionalArgCount,
+ bool *aPreventDefault)
+{
+ return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers,
+ aIgnoreRootScrollFrame, aPressure,
+ aInputSourceArg, 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,
+ uint8_t aOptionalArgCount)
+{
+ PROFILER_LABEL("nsDOMWindowUtils", "SendMouseEventToWindow",
+ js::ProfileEntry::Category::EVENTS);
+
+ return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers,
+ aIgnoreRootScrollFrame, aPressure,
+ aInputSourceArg, 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,
+ bool aToWindow,
+ bool *aPreventDefault,
+ bool aIsDOMEventSynthesized,
+ bool aIsWidgetEventSynthesized,
+ int32_t aButtons)
+{
+ nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+ return nsContentUtils::SendMouseEvent(presShell, aType, aX, aY, aButton,
+ aButtons, aClickCount, aModifiers, aIgnoreRootScrollFrame, aPressure,
+ aInputSourceArg, aToWindow, aPreventDefault, aIsDOMEventSynthesized,
+ aIsWidgetEventSynthesized);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendPointerEventCommon(const nsAString& aType,
+ float aX,
+ float aY,
+ int32_t aButton,
+ int32_t aClickCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ float aPressure,
+ unsigned short aInputSourceArg,
+ int32_t aPointerId,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aTiltX,
+ int32_t aTiltY,
+ bool aIsPrimary,
+ bool aIsSynthesized,
+ uint8_t aOptionalArgCount,
+ bool aToWindow,
+ bool* aPreventDefault)
+{
+ // 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("pointerdown")) {
+ msg = ePointerDown;
+ } else if (aType.EqualsLiteral("pointerup")) {
+ msg = ePointerUp;
+ } else if (aType.EqualsLiteral("pointermove")) {
+ msg = ePointerMove;
+ } else if (aType.EqualsLiteral("pointerover")) {
+ msg = ePointerOver;
+ } else if (aType.EqualsLiteral("pointerout")) {
+ msg = ePointerOut;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
+ aInputSourceArg = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
+ }
+
+ WidgetPointerEvent event(true, msg, widget);
+ event.mModifiers = nsContentUtils::GetWidgetModifiers(aModifiers);
+ event.button = aButton;
+ event.buttons = nsContentUtils::GetButtonsFlagForButton(aButton);
+ event.pressure = aPressure;
+ event.inputSource = aInputSourceArg;
+ event.pointerId = aPointerId;
+ event.mWidth = aWidth;
+ event.mHeight = aHeight;
+ event.tiltX = aTiltX;
+ event.tiltY = aTiltY;
+ event.mIsPrimary =
+ (nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == aInputSourceArg) ? true : aIsPrimary;
+ event.mClickCount = aClickCount;
+ event.mTime = PR_IntervalNow();
+ event.mFlags.mIsSynthesizedForTests = aOptionalArgCount >= 10 ? aIsSynthesized : true;
+
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ event.mRefPoint =
+ nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+ event.mIgnoreRootScrollFrame = aIgnoreRootScrollFrame;
+
+ nsEventStatus status;
+ if (aToWindow) {
+ nsCOMPtr<nsIPresShell> presShell;
+ nsView* view = nsContentUtils::GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
+ if (!presShell || !view) {
+ return NS_ERROR_FAILURE;
+ }
+ status = nsEventStatus_eIgnore;
+ return presShell->HandleEvent(view->GetFrame(), &event, false, &status);
+ }
+ nsresult rv = widget->DispatchEvent(&event, status);
+ if (aPreventDefault) {
+ *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendPointerEvent(const nsAString& aType,
+ float aX,
+ float aY,
+ int32_t aButton,
+ int32_t aClickCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ float aPressure,
+ unsigned short aInputSourceArg,
+ int32_t aPointerId,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aTiltX,
+ int32_t aTiltY,
+ bool aIsPrimary,
+ bool aIsSynthesized,
+ uint8_t aOptionalArgCount,
+ bool* aPreventDefault)
+{
+ PROFILER_LABEL("nsDOMWindowUtils", "SendPointerEvent",
+ js::ProfileEntry::Category::EVENTS);
+
+ return SendPointerEventCommon(aType, aX, aY, aButton, aClickCount,
+ aModifiers, aIgnoreRootScrollFrame,
+ aPressure, aInputSourceArg, aPointerId,
+ aWidth, aHeight, aTiltX, aTiltY,
+ aIsPrimary, aIsSynthesized,
+ aOptionalArgCount, false, aPreventDefault);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendPointerEventToWindow(const nsAString& aType,
+ float aX,
+ float aY,
+ int32_t aButton,
+ int32_t aClickCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ float aPressure,
+ unsigned short aInputSourceArg,
+ int32_t aPointerId,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aTiltX,
+ int32_t aTiltY,
+ bool aIsPrimary,
+ bool aIsSynthesized,
+ uint8_t aOptionalArgCount)
+{
+ PROFILER_LABEL("nsDOMWindowUtils", "SendPointerEventToWindow",
+ js::ProfileEntry::Category::EVENTS);
+
+ return SendPointerEventCommon(aType, aX, aY, aButton, aClickCount,
+ aModifiers, aIgnoreRootScrollFrame,
+ aPressure, aInputSourceArg, aPointerId,
+ aWidth, aHeight, aTiltX, aTiltY,
+ aIsPrimary, aIsSynthesized,
+ aOptionalArgCount, true, nullptr);
+}
+
+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;
+
+ wheelEvent.mTime = PR_Now() / 1000;
+
+ nsPresContext* presContext = GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ wheelEvent.mRefPoint =
+ nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+
+ widget->DispatchInputEvent(&wheelEvent);
+
+ 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,
+ uint32_t *aIdentifiers,
+ int32_t *aXs,
+ int32_t *aYs,
+ uint32_t *aRxs,
+ uint32_t *aRys,
+ float *aRotationAngles,
+ float *aForces,
+ uint32_t aCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ bool *aPreventDefault)
+{
+ return SendTouchEventCommon(aType, aIdentifiers, aXs, aYs, aRxs, aRys,
+ aRotationAngles, aForces, aCount, aModifiers,
+ aIgnoreRootScrollFrame, false, aPreventDefault);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendTouchEventToWindow(const nsAString& aType,
+ uint32_t* aIdentifiers,
+ int32_t* aXs,
+ int32_t* aYs,
+ uint32_t* aRxs,
+ uint32_t* aRys,
+ float* aRotationAngles,
+ float* aForces,
+ uint32_t aCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ bool* aPreventDefault)
+{
+ return SendTouchEventCommon(aType, aIdentifiers, aXs, aYs, aRxs, aRys,
+ aRotationAngles, aForces, aCount, aModifiers,
+ aIgnoreRootScrollFrame, true, aPreventDefault);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendTouchEventCommon(const nsAString& aType,
+ uint32_t* aIdentifiers,
+ int32_t* aXs,
+ int32_t* aYs,
+ uint32_t* aRxs,
+ uint32_t* aRys,
+ float* aRotationAngles,
+ float* aForces,
+ uint32_t aCount,
+ 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);
+ event.mTime = PR_Now();
+
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_FAILURE;
+ }
+ event.mTouches.SetCapacity(aCount);
+ for (uint32_t i = 0; i < aCount; ++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]);
+
+ event.mTouches.AppendElement(t);
+ }
+
+ nsEventStatus status;
+ if (aToWindow) {
+ nsCOMPtr<nsIPresShell> presShell;
+ nsView* view = nsContentUtils::GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
+ if (!presShell || !view) {
+ return NS_ERROR_FAILURE;
+ }
+ status = nsEventStatus_eIgnore;
+ *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
+ return presShell->HandleEvent(view->GetFrame(), &event, false, &status);
+ }
+
+ nsresult rv = widget->DispatchEvent(&event, status);
+ *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendKeyEvent(const nsAString& aType,
+ int32_t aKeyCode,
+ int32_t aCharCode,
+ int32_t aModifiers,
+ uint32_t aAdditionalFlags,
+ bool* aDefaultActionTaken)
+{
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+
+ return nsContentUtils::SendKeyEvent(widget, aType, aKeyCode, aCharCode,
+ aModifiers, aAdditionalFlags,
+ aDefaultActionTaken);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ int32_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(NewRunnableMethod
+ <int32_t, int32_t, uint32_t, nsString, nsString, nsIObserver*>
+ (widget, &nsIWidget::SynthesizeNativeKeyEvent, aNativeKeyboardLayout,
+ aNativeKeyCode, aModifiers, aCharacters, aUnmodifiedCharacters, aObserver));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeMouseEvent(int32_t aScreenX,
+ int32_t aScreenY,
+ int32_t aNativeMessage,
+ int32_t aModifierFlags,
+ nsIDOMElement* aElement,
+ nsIObserver* aObserver)
+{
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
+ if (!widget)
+ return NS_ERROR_FAILURE;
+
+ NS_DispatchToMainThread(NewRunnableMethod
+ <LayoutDeviceIntPoint, int32_t, int32_t, nsIObserver*>
+ (widget, &nsIWidget::SynthesizeNativeMouseEvent,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aNativeMessage, aModifierFlags,
+ aObserver));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeMouseMove(int32_t aScreenX,
+ int32_t aScreenY,
+ nsIDOMElement* aElement,
+ nsIObserver* aObserver)
+{
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
+ if (!widget)
+ return NS_ERROR_FAILURE;
+
+ NS_DispatchToMainThread(NewRunnableMethod
+ <LayoutDeviceIntPoint, nsIObserver*>
+ (widget, &nsIWidget::SynthesizeNativeMouseMove,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), 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,
+ nsIDOMElement* aElement,
+ nsIObserver* aObserver)
+{
+ // get the widget to send the event to
+ nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_DispatchToMainThread(NewRunnableMethod
+ <mozilla::LayoutDeviceIntPoint, uint32_t, double, double, double, uint32_t, uint32_t, nsIObserver*>
+ (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)
+{
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aPressure < 0 || aPressure > 1 || aOrientation > 359) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ NS_DispatchToMainThread(NewRunnableMethod
+ <uint32_t, nsIWidget::TouchPointerState, LayoutDeviceIntPoint, double, uint32_t, nsIObserver*>
+ (widget, &nsIWidget::SynthesizeNativeTouchPoint, aPointerId,
+ (nsIWidget::TouchPointerState)aTouchState,
+ LayoutDeviceIntPoint(aScreenX, aScreenY),
+ aPressure, aOrientation, aObserver));
+ 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(NewRunnableMethod
+ <LayoutDeviceIntPoint, bool, nsIObserver*>
+ (widget, &nsIWidget::SynthesizeNativeTouchTap,
+ LayoutDeviceIntPoint(aScreenX, aScreenY), aLongTap, aObserver));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ClearNativeTouchSequence(nsIObserver* aObserver)
+{
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_DispatchToMainThread(NewRunnableMethod<nsIObserver*>
+ (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) {
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ return nsContentUtils::GetWidget(presShell, aOffset);
+ }
+ }
+
+ return nullptr;
+}
+
+nsIWidget*
+nsDOMWindowUtils::GetWidgetForElement(nsIDOMElement* aElement)
+{
+ if (!aElement)
+ return GetWidget();
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+ nsIDocument* doc = content->GetUncomposedDoc();
+ nsIPresShell* presShell = doc ? doc->GetShell() : nullptr;
+
+ if (presShell) {
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ frame = presShell->GetRootFrame();
+ }
+ if (frame)
+ return frame->GetNearestWidget();
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::Focus(nsIDOMElement* aElement)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ if (aElement)
+ fm->SetFocus(aElement, 0);
+ else
+ fm->ClearFocus(window);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GarbageCollect(nsICycleCollectorListener *aListener,
+ int32_t aExtraForgetSkippableCalls)
+{
+ PROFILER_LABEL("nsDOMWindowUtils", "GarbageCollect",
+ js::ProfileEntry::Category::GC);
+
+ nsJSContext::GarbageCollectNow(JS::gcreason::DOM_UTILS);
+ nsJSContext::CycleCollectNow(aListener, aExtraForgetSkippableCalls);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::CycleCollect(nsICycleCollectorListener *aListener,
+ int32_t aExtraForgetSkippableCalls)
+{
+ nsJSContext::CycleCollectNow(aListener, aExtraForgetSkippableCalls);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RunNextCollectorTimer()
+{
+ nsJSContext::RunNextCollectorTimer();
+
+ 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;
+ event.mTime = PR_IntervalNow();
+
+ 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,
+ nsIDOMElement** aReturn)
+{
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ Element* el =
+ doc->ElementFromPointHelper(aX, aY, aIgnoreRootScrollFrame, aFlushLayout);
+ nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(el);
+ retval.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,
+ nsIDOMNodeList** aReturn)
+{
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ return doc->NodesFromRectHelper(aX, aY, aTopSize, aRightSize, aBottomSize, aLeftSize,
+ aIgnoreRootScrollFrame, aFlushLayout, aReturn);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetTranslationNodes(nsIDOMNode* aRoot,
+ nsITranslationNodeList** aRetVal)
+{
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ nsCOMPtr<nsIContent> root = do_QueryInterface(aRoot);
+ NS_ENSURE_STATE(root);
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ if (root->OwnerDoc() != doc) {
+ return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
+ }
+
+ nsTHashtable<nsPtrHashKey<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->HasTextForTranslation()) {
+ translationNodesHash.PutEntry(content);
+
+ bool isBlockFrame = false;
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (frame) {
+ isBlockFrame = frame->IsFrameOfType(nsIFrame::eBlockFrame);
+ }
+
+ bool isTranslationRoot = isBlockFrame;
+ if (!isBlockFrame) {
+ // 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->AsDOMNode(), isTranslationRoot);
+ --limit;
+ break;
+ }
+ }
+ }
+
+ *aRetVal = list.forget().take();
+ return NS_OK;
+}
+
+static already_AddRefed<DataSourceSurface>
+CanvasToDataSourceSurface(nsIDOMHTMLCanvasElement* aCanvas)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aCanvas);
+ if (!node) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(node->IsElement(),
+ "An nsINode that implements nsIDOMHTMLCanvasElement should "
+ "be an element.");
+ nsLayoutUtils::SurfaceFromElementResult result =
+ nsLayoutUtils::SurfaceFromElement(node->AsElement());
+
+ MOZ_ASSERT(result.GetSourceSurface());
+ return result.GetSourceSurface()->GetDataSurface();
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::CompareCanvases(nsIDOMHTMLCanvasElement *aCanvas1,
+ nsIDOMHTMLCanvasElement *aCanvas2,
+ uint32_t* aMaxDifference,
+ uint32_t* retVal)
+{
+ if (aCanvas1 == nullptr ||
+ aCanvas2 == nullptr ||
+ retVal == nullptr)
+ return NS_ERROR_FAILURE;
+
+ RefPtr<DataSourceSurface> img1 = CanvasToDataSourceSurface(aCanvas1);
+ RefPtr<DataSourceSurface> img2 = CanvasToDataSourceSurface(aCanvas2);
+
+ DataSourceSurface::ScopedMap map1(img1, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap map2(img2, DataSourceSurface::READ);
+
+ if (img1 == nullptr || img2 == nullptr ||
+ !map1.IsMapped() || !map2.IsMapped() ||
+ img1->GetSize() != img2->GetSize() ||
+ map1.GetStride() != map2.GetStride()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int v;
+ IntSize size = img1->GetSize();
+ int32_t stride = map1.GetStride();
+
+ // we can optimize for the common all-pass case
+ if (stride == 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*stride;
+ unsigned char *p2 = map2.GetData() + j*stride;
+ v = memcmp(p1, p2, stride);
+
+ 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::ClearMozAfterPaintEvents()
+{
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext)
+ return NS_OK;
+ presContext->ClearMozAfterPaintEvents();
+ 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);
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+ presShell->DisableNonTestMouseEvents(aDisable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SuppressEventHandling(bool aSuppress)
+{
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ if (aSuppress) {
+ doc->SuppressEventHandling(nsIDocument::eEvents);
+ } else {
+ doc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, true);
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+getScrollXYAppUnits(nsWeakPtr aWindow, bool aFlushLayout, nsPoint& aScrollPos) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(aWindow);
+ nsCOMPtr<nsIDocument> doc = window ? window->GetExtantDoc() : nullptr;
+ NS_ENSURE_STATE(doc);
+
+ if (aFlushLayout) {
+ doc->FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsIPresShell *presShell = doc->GetShell();
+ if (presShell) {
+ 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::GetScrollbarSize(bool aFlushLayout, int32_t* aWidth,
+ int32_t* aHeight)
+{
+ *aWidth = 0;
+ *aHeight = 0;
+
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ if (aFlushLayout) {
+ doc->FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsIPresShell* presShell = doc->GetShell();
+ 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(nsIDOMElement *aElement,
+ nsIDOMClientRect** aResult)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ nsresult rv;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<DOMRect> rect = new DOMRect(window);
+ nsIFrame* frame = content->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::GetRootBounds(nsIDOMClientRect** aResult)
+{
+ nsIDocument* doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ nsRect bounds(0, 0, 0, 0);
+ nsIPresShell* presShell = doc->GetShell();
+ if (presShell) {
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ bounds = sf->GetScrollRange();
+ bounds.width += sf->GetScrollPortRect().width;
+ 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 != IMEState::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::GetFocusedInputType(char** aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ InputContext context = widget->GetInputContext();
+ *aType = ToNewCString(context.mHTMLInputType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetViewId(nsIDOMElement* aElement, nsViewID* aResult)
+{
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+ if (content && nsLayoutUtils::FindIDFor(content, aResult)) {
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetScreenPixelsPerCSSPixel(float* aScreenPixels)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+ *aScreenPixels = window->GetDevicePixelRatio(CallerType::System);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetFullZoom(float* aFullZoom)
+{
+ *aFullZoom = 1.0f;
+
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_OK;
+ }
+
+ *aFullZoom = presContext->DeviceContext()->GetFullZoom();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::DispatchDOMEventViaPresShell(nsIDOMNode* aTarget,
+ nsIDOMEvent* aEvent,
+ bool aTrusted,
+ bool* aRetVal)
+{
+ NS_ENSURE_STATE(aEvent);
+ aEvent->SetTrusted(aTrusted);
+ WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
+ NS_ENSURE_STATE(internalEvent);
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aTarget);
+ NS_ENSURE_STATE(content);
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ if (content->OwnerDoc()->GetWindow() != window) {
+ return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
+ }
+ nsCOMPtr<nsIDocument> targetDoc = content->GetUncomposedDoc();
+ NS_ENSURE_STATE(targetDoc);
+ RefPtr<nsIPresShell> targetShell = targetDoc->GetShell();
+ NS_ENSURE_STATE(targetShell);
+
+ targetDoc->FlushPendingNotifications(Flush_Layout);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ targetShell->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;
+ }
+ aEvent.mTime = PR_IntervalNow();
+}
+
+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);
+
+ nsCOMPtr<nsIPresShell> 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.
+ WidgetQueryContentEvent dummyEvent(true, eQueryContentState, widget);
+ dummyEvent.Init(options);
+ InitEvent(dummyEvent, &pt);
+ nsIFrame* popupFrame =
+ nsLayoutUtils::GetPopupFrameForEventCoordinates(presContext->GetRootPresContext(), &dummyEvent);
+
+ 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);
+
+ nsQueryContentEventResult* result = new nsQueryContentEventResult();
+ result->SetEventResult(widget, queryEvent);
+ 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)
+{
+ // 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("pasteTransferable")) {
+ msg = eContentCommandPasteTransferable;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetContentCommandEvent event(true, msg, widget);
+ 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_strdup(JS_GetClass(aObject.toObjectOrNull())->name);
+ MOZ_ASSERT(*aName, "NS_strdup should be infallible.");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetVisitedDependentComputedStyle(
+ nsIDOMElement *aElement, const nsAString& aPseudoElement,
+ const nsAString& aPropertyName, nsAString& aResult)
+{
+ aResult.Truncate();
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ nsCOMPtr<Element> element = do_QueryInterface(aElement);
+ NS_ENSURE_STATE(window && element);
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow = window->GetCurrentInnerWindow();
+ NS_ENSURE_STATE(window);
+
+ nsCOMPtr<nsIDOMCSSStyleDeclaration> decl;
+ {
+ ErrorResult rv;
+ decl = innerWindow->GetComputedStyle(*element, aPseudoElement, rv);
+ ENSURE_SUCCESS(rv, rv.StealNSResult());
+ }
+
+ static_cast<nsComputedDOMStyle*>(decl.get())->SetExposeVisitedStyle(true);
+ nsresult rv = decl->GetPropertyValue(aPropertyName, aResult);
+ static_cast<nsComputedDOMStyle*>(decl.get())->SetExposeVisitedStyle(false);
+
+ 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 = nsGlobalWindow::Cast(window)->IsInModalState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetDesktopModeViewport(bool aDesktopMode)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ window->SetDesktopModeViewport(aDesktopMode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetOuterWindowID(uint64_t *aWindowID)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ NS_ASSERTION(window->IsOuterWindow(), "How did that happen?");
+ *aWindowID = window->WindowID();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCurrentInnerWindowID(uint64_t *aWindowID)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
+
+ NS_ASSERTION(window->IsOuterWindow(), "How did that happen?");
+ nsGlobalWindow* inner =
+ nsGlobalWindow::Cast(window)->GetCurrentInnerWindowInternal();
+ if (!inner) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aWindowID = inner->WindowID();
+ 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;
+
+ LayerManager *mgr = widget->GetLayerManager(nsIWidget::LAYER_MANAGER_PERSISTENT);
+ if (!mgr)
+ return NS_ERROR_FAILURE;
+
+ mgr->GetBackendName(aType);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetLayerManagerRemote(bool* retval)
+{
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget)
+ return NS_ERROR_FAILURE;
+
+ LayerManager *mgr = widget->GetLayerManager();
+ if (!mgr)
+ return NS_ERROR_FAILURE;
+
+ *retval = !!mgr->AsShadowForwarder();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetSupportsHardwareH264Decoding(JS::MutableHandle<JS::Value> aPromise)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+ nsCOMPtr<nsIGlobalObject> parentObject =
+ do_QueryInterface(window->GetCurrentInnerWindow());
+ NS_ENSURE_STATE(parentObject);
+#ifdef MOZ_FMP4
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ NS_ENSURE_STATE(widget);
+ LayerManager *mgr = widget->GetLayerManager();
+ NS_ENSURE_STATE(mgr);
+ RefPtr<Promise> promise =
+ MP4Decoder::IsVideoAccelerated(mgr->AsShadowForwarder(), parentObject);
+ NS_ENSURE_STATE(promise);
+ aPromise.setObject(*promise->PromiseObj());
+#else
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(parentObject, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ promise->MaybeResolve(NS_LITERAL_STRING("No; Compiled without MP4 support."));
+ aPromise.setObject(*promise->PromiseObj());
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCurrentAudioBackend(nsAString& aBackend)
+{
+ CubebUtils::GetCurrentBackend(aBackend);
+ 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;
+
+ LayerManager *mgr = widget->GetLayerManager();
+ if (!mgr)
+ 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 = mgr->StartFrameTimeRecording(bufferSize);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::StopFrameTimeRecording(uint32_t startIndex,
+ uint32_t *frameCount,
+ float **frameIntervals)
+{
+ NS_ENSURE_ARG_POINTER(frameCount);
+ NS_ENSURE_ARG_POINTER(frameIntervals);
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget)
+ return NS_ERROR_FAILURE;
+
+ LayerManager *mgr = widget->GetLayerManager();
+ if (!mgr)
+ return NS_ERROR_FAILURE;
+
+ nsTArray<float> tmpFrameIntervals;
+ mgr->StopFrameTimeRecording(startIndex, tmpFrameIntervals);
+ *frameCount = tmpFrameIntervals.Length();
+
+ *frameIntervals = (float*)moz_xmalloc(*frameCount * sizeof(float));
+
+ /* copy over the frame intervals and paint times into the arrays we just allocated */
+ for (uint32_t i = 0; i < *frameCount; i++) {
+ (*frameIntervals)[i] = tmpFrameIntervals[i];
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::BeginTabSwitch()
+{
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget)
+ return NS_ERROR_FAILURE;
+
+ LayerManager *mgr = widget->GetLayerManager();
+ if (!mgr)
+ return NS_ERROR_FAILURE;
+
+ mgr->BeginTabSwitch();
+
+ return NS_OK;
+}
+
+static bool
+ComputeAnimationValue(nsCSSPropertyID aProperty,
+ Element* aElement,
+ const nsAString& aInput,
+ StyleAnimationValue& aOutput)
+{
+ nsIDocument* doc = aElement->GetUncomposedDoc();
+ nsIPresShell* shell = doc->GetShell();
+ if (!shell) {
+ return false;
+ }
+
+ RefPtr<nsStyleContext> styleContext =
+ nsComputedDOMStyle::GetStyleContextForElement(aElement, nullptr, shell);
+
+ if (!StyleAnimationValue::ComputeValue(aProperty, aElement, styleContext,
+ aInput, false, aOutput)) {
+ return false;
+ }
+ return true;
+}
+
+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<nsIDocument> doc = GetDocument();
+ if (doc) {
+ PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
+ if (tracker) {
+ tracker->TriggerPendingAnimationsNow();
+ }
+ }
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ nsRefreshDriver* driver = presContext->RefreshDriver();
+ driver->AdvanceTimeAndRefresh(aMilliseconds);
+
+ RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();
+ if (transaction && transaction->IPCOpen()) {
+ transaction->SendSetTestSampleTime(driver->MostRecentRefresh());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetLastTransactionId(uint64_t *aLastTransactionId)
+{
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsRefreshDriver* driver = presContext->GetRootPresContext()->RefreshDriver();
+ *aLastTransactionId = 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.
+ RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();
+ if (transaction && transaction->IPCOpen()) {
+ transaction->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(nsIDOMNode* aNode,
+ float aX, float aY)
+{
+ nsCOMPtr<Element> element = do_QueryInterface(aNode);
+ if (!element) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ FrameMetrics::ViewID viewId;
+ if (!nsLayoutUtils::FindIDFor(element, &viewId)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+ LayerManager* manager = widget->GetLayerManager();
+ if (!manager) {
+ return NS_ERROR_FAILURE;
+ }
+ ShadowLayerForwarder* forwarder = manager->AsShadowForwarder();
+ if (!forwarder || !forwarder->HasShadowManager()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ forwarder->GetShadowManager()->SendSetAsyncScrollOffset(viewId, aX, aY);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAsyncZoom(nsIDOMNode* aRootElement, float aValue)
+{
+ nsCOMPtr<Element> element = do_QueryInterface(aRootElement);
+ if (!element) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ FrameMetrics::ViewID viewId;
+ if (!nsLayoutUtils::FindIDFor(element, &viewId)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_FAILURE;
+ }
+ LayerManager* manager = widget->GetLayerManager();
+ if (!manager) {
+ return NS_ERROR_FAILURE;
+ }
+ ShadowLayerForwarder* forwarder = manager->AsShadowForwarder();
+ if (!forwarder || !forwarder->HasShadowManager()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ forwarder->GetShadowManager()->SendSetAsyncZoom(viewId, aValue);
+ return NS_OK;
+}
+
+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;
+ }
+ LayerManager* manager = widget->GetLayerManager();
+ if (!manager) {
+ *aOutResult = false;
+ return NS_OK;
+ }
+ ShadowLayerForwarder* forwarder = manager->AsShadowForwarder();
+ if (!forwarder || !forwarder->HasShadowManager()) {
+ *aOutResult = false;
+ return NS_OK;
+ }
+ forwarder->GetShadowManager()->SendFlushApzRepaints();
+ *aOutResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ZoomToFocusedInput()
+{
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_OK;
+ }
+ // If APZ is not enabled, this function is a no-op.
+ if (!widget->AsyncPanZoomEnabled()) {
+ return NS_OK;
+ }
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return NS_OK;
+ }
+
+ nsIContent* content = fm->GetFocusedContent();
+ if (!content) {
+ return NS_OK;
+ }
+
+ nsIPresShell* shell = APZCCallbackHelper::GetRootContentDocumentPresShellForContent(content);
+ if (!shell) {
+ return NS_OK;
+ }
+
+ nsIScrollableFrame* rootScrollFrame = shell->GetRootScrollFrameAsScrollable();
+ if (!rootScrollFrame) {
+ return NS_OK;
+ }
+
+ nsIDocument* document = shell->GetDocument();
+ if (!document) {
+ return NS_OK;
+ }
+
+ uint32_t presShellId;
+ FrameMetrics::ViewID viewId;
+ if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(document->GetDocumentElement(), &presShellId, &viewId)) {
+ uint32_t flags = layers::DISABLE_ZOOM_OUT;
+ if (!Preferences::GetBool("formhelper.autozoom")) {
+ flags |= layers::PAN_INTO_VIEW_ONLY;
+ } else {
+ flags |= layers::ONLY_ZOOM_TO_DEFAULT_SCALE;
+ }
+
+ CSSRect bounds = nsLayoutUtils::GetBoundingContentRect(content, rootScrollFrame);
+ if (bounds.IsEmpty()) {
+ // Do not zoom on empty bounds. Bail out.
+ return NS_OK;
+ }
+ bounds.Inflate(15.0f, 0.0f);
+ widget->ZoomToRect(presShellId, viewId, bounds, flags);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement,
+ const nsAString& aProperty,
+ const nsAString& aValue1,
+ const nsAString& aValue2,
+ double* aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCSSPropertyID property =
+ nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eIgnoreEnabledState);
+ if (property != eCSSProperty_UNKNOWN && nsCSSProps::IsShorthand(property)) {
+ property = eCSSProperty_UNKNOWN;
+ }
+
+ MOZ_ASSERT(property == eCSSProperty_UNKNOWN ||
+ !nsCSSProps::IsShorthand(property),
+ "should not have shorthand");
+
+ StyleAnimationValue v1, v2;
+ Element* element = content->AsElement();
+ if (property == eCSSProperty_UNKNOWN ||
+ !ComputeAnimationValue(property, element, aValue1, v1) ||
+ !ComputeAnimationValue(property, element, aValue2, v2)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsIPresShell* shell = element->GetUncomposedDoc()->GetShell();
+ RefPtr<nsStyleContext> styleContext = shell
+ ? nsComputedDOMStyle::GetStyleContextForElement(element, nullptr, shell)
+ : nullptr;
+ if (!StyleAnimationValue::ComputeDistance(property, v1, v2, styleContext,
+ *aResult)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDOMWindowUtils::RenderDocument(const nsRect& aRect,
+ uint32_t aFlags,
+ nscolor aBackgroundColor,
+ gfxContext* aThebesContext)
+{
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ // Get Primary Shell
+ nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ // Render Document
+ return presShell->RenderDocument(aRect, aFlags, aBackgroundColor, aThebesContext);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCursorType(int16_t *aCursor)
+{
+ NS_ENSURE_ARG_POINTER(aCursor);
+
+ nsIDocument* doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ bool isSameDoc = false;
+ do {
+ if (EventStateManager::sMouseOverDocument == doc) {
+ isSameDoc = true;
+ break;
+ }
+ } while ((doc = doc->GetParentDocument()));
+
+ if (!isSameDoc) {
+ *aCursor = eCursor_none;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ if (!widget)
+ return NS_ERROR_FAILURE;
+
+ // fetch cursor value from window's widget
+ *aCursor = widget->GetCursor();
+
+ 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::GetOuterWindowWithId(uint64_t aWindowID,
+ nsIDOMWindow** aWindow)
+{
+ // XXX This method is deprecated. See bug 865664.
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ nsContentUtils::GetDocumentFromCaller(),
+ nsContentUtils::eDOM_PROPERTIES,
+ "GetWindowWithOuterIdWarning");
+
+ *aWindow = nsGlobalWindow::GetOuterWindowWithId(aWindowID);
+ NS_IF_ADDREF(*aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetContainerElement(nsIDOMElement** aResult)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ nsCOMPtr<nsIDOMElement> element =
+ do_QueryInterface(window->GetFrameElementInternal());
+
+ element.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::WrapDOMFile(nsIFile *aFile,
+ nsISupports **aDOMFile)
+{
+ if (!aFile) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ nsPIDOMWindowInner* innerWindow = window->GetCurrentInnerWindow();
+ if (!innerWindow) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDOMBlob> blob = File::CreateFromFile(innerWindow, aFile);
+ blob.forget(aDOMFile);
+ return NS_OK;
+}
+
+#ifdef DEBUG
+static bool
+CheckLeafLayers(Layer* aLayer, const nsIntPoint& aOffset, nsIntRegion* aCoveredRegion)
+{
+ gfx::Matrix transform;
+ if (!aLayer->GetTransform().Is2D(&transform) ||
+ transform.HasNonIntegerTranslation())
+ return false;
+ transform.NudgeToIntegers();
+ IntPoint offset = aOffset + IntPoint::Truncate(transform._31, transform._32);
+
+ Layer* child = aLayer->GetFirstChild();
+ if (child) {
+ while (child) {
+ if (!CheckLeafLayers(child, offset, aCoveredRegion))
+ return false;
+ child = child->GetNextSibling();
+ }
+ } else {
+ nsIntRegion rgn = aLayer->GetVisibleRegion().ToUnknownRegion();
+ rgn.MoveBy(offset);
+ nsIntRegion tmp;
+ tmp.And(rgn, *aCoveredRegion);
+ if (!tmp.IsEmpty())
+ return false;
+ aCoveredRegion->Or(*aCoveredRegion, rgn);
+ }
+
+ return true;
+}
+#endif
+
+NS_IMETHODIMP
+nsDOMWindowUtils::LeafLayersPartitionWindow(bool* aResult)
+{
+ *aResult = true;
+#ifdef DEBUG
+ nsIWidget* widget = GetWidget();
+ if (!widget)
+ return NS_ERROR_FAILURE;
+ LayerManager* manager = widget->GetLayerManager();
+ if (!manager)
+ return NS_ERROR_FAILURE;
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext)
+ return NS_ERROR_FAILURE;
+ Layer* root = manager->GetRoot();
+ if (!root)
+ return NS_ERROR_FAILURE;
+
+ nsIntPoint offset(0, 0);
+ nsIntRegion coveredRegion;
+ if (!CheckLeafLayers(root, offset, &coveredRegion)) {
+ *aResult = false;
+ }
+ if (!coveredRegion.IsEqual(root->GetVisibleRegion().ToUnknownRegion())) {
+ *aResult = false;
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::CheckAndClearPaintedState(nsIDOMElement* aElement, bool* aResult)
+{
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIFrame* frame = content->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() == content) {
+ frame = parentFrame;
+ } else {
+ break;
+ }
+ }
+
+ *aResult = frame->CheckAndClearPaintedState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::IsPartOfOpaqueLayer(nsIDOMElement* aElement, bool* aResult)
+{
+ if (!aElement) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PaintedLayer* layer = FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(frame);
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = (layer->GetContentFlags() & Layer::CONTENT_OPAQUE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::NumberOfAssignedPaintedLayers(nsIDOMElement** aElements,
+ uint32_t aCount,
+ uint32_t* aResult)
+{
+ if (!aElements) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsTHashtable<nsPtrHashKey<PaintedLayer>> layers;
+ nsresult rv;
+ for (uint32_t i = 0; i < aCount; i++) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElements[i], &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PaintedLayer* layer = FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(frame);
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ layers.PutEntry(layer);
+ }
+
+ *aResult = layers.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::EnableDialogs()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsGlobalWindow::Cast(window)->EnableDialogs();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::DisableDialogs()
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsGlobalWindow::Cast(window)->DisableDialogs();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::AreDialogsEnabled(bool* aResult)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ *aResult = nsGlobalWindow::Cast(window)->AreDialogsEnabled();
+ 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());
+
+ IDBMutableFile* mutableFile = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
+ *_retval = mutableFile->GetFileId();
+ return NS_OK;
+ }
+
+ 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::HandleValue 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,
+ JS::Handle<JS::Value> aOptions,
+ int32_t* aRefCnt, int32_t* aDBRefCnt,
+ int32_t* aSliceRefCnt, JSContext* aCx,
+ bool* aResult)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCString origin;
+ nsresult rv =
+ quota::QuotaManager::GetInfoFromWindow(window, nullptr, nullptr, &origin,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ IDBOpenDBOptions options;
+ JS::Rooted<JS::Value> optionsVal(aCx, aOptions);
+ if (!options.Init(aCx, optionsVal)) {
+ return NS_ERROR_TYPE_ERR;
+ }
+
+ quota::PersistenceType persistenceType =
+ quota::PersistenceTypeFromStorage(options.mStorage);
+
+ RefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
+
+ if (mgr) {
+ rv = mgr->BlockAndGetFileReferences(persistenceType, origin, aDatabaseName,
+ aId, aRefCnt, aDBRefCnt, aSliceRefCnt,
+ aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ *aRefCnt = *aDBRefCnt = *aSliceRefCnt = -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::IsIncrementalGCEnabled(JSContext* cx, bool* aResult)
+{
+ *aResult = JS::IsIncrementalGCEnabled(cx);
+ 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);
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ *aPaintingSuppressed = presShell->IsPaintingSuppressed();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPlugins(JSContext* cx, JS::MutableHandle<JS::Value> aPlugins)
+{
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ nsTArray<nsIObjectLoadingContent*> plugins;
+ doc->GetPlugins(plugins);
+
+ JS::Rooted<JSObject*> jsPlugins(cx);
+ nsresult rv = nsTArrayToJSArray(cx, plugins, &jsPlugins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPlugins.setObject(*jsPlugins);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetScrollPositionClampingScrollPortSize(float aWidth, float aHeight)
+{
+ if (!(aWidth >= 0.0 && aHeight >= 0.0)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsLayoutUtils::SetScrollPositionClampingScrollPortSize(presShell, CSSSize(aWidth, aHeight));
+
+ return NS_OK;
+}
+
+nsresult
+nsDOMWindowUtils::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement)
+{
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ doc->RemoteFrameFullscreenChanged(aFrameElement);
+ return NS_OK;
+}
+
+nsresult
+nsDOMWindowUtils::RemoteFrameFullscreenReverted()
+{
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ doc->RemoteFrameFullscreenReverted();
+ return NS_OK;
+}
+
+static void
+PrepareForFullscreenChange(nsIPresShell* aPresShell, const nsSize& aSize,
+ nsSize* aOldSize = nullptr)
+{
+ if (!aPresShell) {
+ return;
+ }
+ if (nsRefreshDriver* rd = aPresShell->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()) {
+ if (nsViewManager* viewManager = aPresShell->GetViewManager()) {
+ if (aOldSize) {
+ viewManager->GetWindowDimensions(&aOldSize->width, &aOldSize->height);
+ }
+ viewManager->SetWindowDimensions(aSize.width, aSize.height);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::HandleFullscreenRequests(bool* aRetVal)
+{
+ PROFILER_MARKER("Enter fullscreen");
+ nsCOMPtr<nsIDocument> 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(GetPresShell(), screenRect.Size(), &oldSize);
+ OldWindowSize::Set(mWindow, oldSize);
+
+ *aRetVal = nsIDocument::HandlePendingFullscreenRequests(doc);
+ return NS_OK;
+}
+
+nsresult
+nsDOMWindowUtils::ExitFullscreen()
+{
+ PROFILER_MARKER("Exit fullscreen");
+ nsCOMPtr<nsIDocument> 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(GetPresShell(), oldSize);
+ nsIDocument::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;
+ }
+
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // The root frame for this content window
+ nsIFrame* rootFrame = presShell->FrameManager()->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, rootFrame);
+ nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(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, targetFrame);
+
+ nsresult rv =
+ static_cast<nsFrame*>(targetFrame)->
+ SelectByTypeAtPoint(GetPresContext(), relPoint, amount, amount,
+ nsFrame::SELECT_ACCUMULATE);
+ *_retval = !NS_FAILED(rv);
+ return NS_OK;
+}
+
+static nsIDocument::additionalSheetType
+convertSheetType(uint32_t aSheetType)
+{
+ switch(aSheetType) {
+ case nsDOMWindowUtils::AGENT_SHEET:
+ return nsIDocument::eAgentSheet;
+ case nsDOMWindowUtils::USER_SHEET:
+ return nsIDocument::eUserSheet;
+ case nsDOMWindowUtils::AUTHOR_SHEET:
+ return nsIDocument::eAuthorSheet;
+ default:
+ NS_ASSERTION(false, "wrong type");
+ // we must return something although this should never happen
+ return nsIDocument::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<nsIDocument> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ nsIDocument::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(nsIDOMStyleSheet *aSheet, uint32_t aSheetType)
+{
+ NS_ENSURE_ARG_POINTER(aSheet);
+ NS_ENSURE_ARG(aSheetType == AGENT_SHEET ||
+ aSheetType == USER_SHEET ||
+ aSheetType == AUTHOR_SHEET);
+
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ nsIDocument::additionalSheetType type = convertSheetType(aSheetType);
+ RefPtr<CSSStyleSheet> sheet = do_QueryObject(aSheet);
+ NS_ENSURE_TRUE(sheet, NS_ERROR_FAILURE);
+ if (sheet->GetOwningDocument()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ 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<nsIDocument> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ nsIDocument::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 = EventStateManager::IsHandlingUserInput();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetMillisSinceLastUserInput(double* aMillisSinceLastUserInput)
+{
+ TimeStamp lastInput = EventStateManager::LatestUserInputStart();
+ if (lastInput.IsNull()) {
+ *aMillisSinceLastUserInput = 0;
+ 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);
+ nsGlobalWindow::Cast(window)->AllowScriptsToClose();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetIsParentWindowMainWidgetVisible(bool* aIsVisible)
+{
+ // 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) {
+ if (TabChild *tabChild = TabChild::GetFrom(docShell)) {
+ if (!tabChild->SendIsParentWindowMainWidgetVisible(aIsVisible))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+ }
+
+ 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(nsIDOMNode* aNode, bool* aRetVal)
+{
+ *aRetVal = false;
+ nsCOMPtr<nsINode> n = do_QueryInterface(aNode);
+ nsINode* node = n;
+ while (node) {
+ if (node->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
+ nsCOMPtr<nsIFormControl> fc = do_QueryInterface(node);
+ if (fc && fc->IsDisabledForEvents(eVoidEvent)) {
+ *aRetVal = true;
+ break;
+ }
+ }
+ node = node->GetParentNode();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetPaintFlashing(bool aPaintFlashing)
+{
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ presContext->SetPaintFlashing(aPaintFlashing);
+ // Clear paint flashing colors
+ nsIPresShell* presShell = GetPresShell();
+ if (!aPaintFlashing && presShell) {
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (rootFrame) {
+ rootFrame->InvalidateFrameSubtree();
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetPaintFlashing(bool* aRetVal)
+{
+ *aRetVal = false;
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ *aRetVal = presContext->GetPaintFlashing();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::DispatchEventToChromeOnly(nsIDOMEventTarget* aTarget,
+ nsIDOMEvent* aEvent,
+ bool* aRetVal)
+{
+ *aRetVal = false;
+ NS_ENSURE_STATE(aTarget && aEvent);
+ aEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ aTarget->DispatchEvent(aEvent, aRetVal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RequestCompositorProperty(const nsAString& property,
+ float* aResult)
+{
+ if (nsIWidget* widget = GetWidget()) {
+ mozilla::layers::LayerManager* manager = widget->GetLayerManager();
+ if (manager) {
+ *aResult = manager->RequestProperty(property);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetOMTAStyle(nsIDOMElement* aElement,
+ const nsAString& aProperty,
+ const nsAString& aPseudoElement,
+ nsAString& aResult)
+{
+ nsCOMPtr<Element> element = do_QueryInterface(aElement);
+ if (!element) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsROCSSPrimitiveValue> cssValue = nullptr;
+ nsIFrame* frame = element->GetPrimaryFrame();
+ if (frame && !aPseudoElement.IsEmpty()) {
+ if (aPseudoElement.EqualsLiteral("::before")) {
+ frame = nsLayoutUtils::GetBeforeFrame(frame);
+ } else if (aPseudoElement.EqualsLiteral("::after")) {
+ frame = nsLayoutUtils::GetAfterFrame(frame);
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ if (frame && nsLayoutUtils::AreAsyncAnimationsEnabled()) {
+ if (aProperty.EqualsLiteral("opacity")) {
+ Layer* layer =
+ FrameLayerBuilder::GetDedicatedLayer(frame,
+ nsDisplayItem::TYPE_OPACITY);
+ if (layer) {
+ ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
+ if (forwarder && forwarder->HasShadowManager()) {
+ float value;
+ bool hadAnimatedOpacity;
+ forwarder->GetShadowManager()->SendGetAnimationOpacity(
+ layer->AsShadowableLayer()->GetShadow(),
+ &value, &hadAnimatedOpacity);
+
+ if (hadAnimatedOpacity) {
+ cssValue = new nsROCSSPrimitiveValue;
+ cssValue->SetNumber(value);
+ }
+ }
+ }
+ } else if (aProperty.EqualsLiteral("transform")) {
+ Layer* layer =
+ FrameLayerBuilder::GetDedicatedLayer(frame,
+ nsDisplayItem::TYPE_TRANSFORM);
+ if (layer) {
+ ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
+ if (forwarder && forwarder->HasShadowManager()) {
+ MaybeTransform transform;
+ forwarder->GetShadowManager()->SendGetAnimationTransform(
+ layer->AsShadowableLayer()->GetShadow(), &transform);
+ if (transform.type() == MaybeTransform::TMatrix4x4) {
+ Matrix4x4 matrix = transform.get_Matrix4x4();
+ cssValue = nsComputedDOMStyle::MatrixToCSSValue(matrix);
+ }
+ }
+ }
+ }
+ }
+
+ if (cssValue) {
+ nsString text;
+ ErrorResult rv;
+ cssValue->GetCssText(text, rv);
+ aResult.Assign(text);
+ return rv.StealNSResult();
+ } else {
+ aResult.Truncate();
+ }
+
+ 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;
+};
+
+NS_IMPL_ISUPPORTS(HandlingUserInputHelper, nsIJSRAIIHelper)
+
+HandlingUserInputHelper::HandlingUserInputHelper(bool aHandlingUserInput)
+ : mHandlingUserInput(aHandlingUserInput),
+ mDestructCalled(false)
+{
+ if (aHandlingUserInput) {
+ EventStateManager::StartHandlingUserInput();
+ }
+}
+
+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) {
+ EventStateManager::StopHandlingUserInput();
+ }
+
+ return NS_OK;
+}
+
+} // unnamed namespace
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetHandlingUserInput(bool aHandlingUserInput,
+ nsIJSRAIIHelper** aHelper)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ RefPtr<HandlingUserInputHelper> helper(
+ new HandlingUserInputHelper(aHandlingUserInput));
+ helper.forget(aHelper);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetContentAPZTestData(JSContext* aContext,
+ JS::MutableHandleValue aOutContentTestData)
+{
+ if (nsIWidget* widget = GetWidget()) {
+ RefPtr<LayerManager> lm = widget->GetLayerManager();
+ if (!lm) {
+ return NS_OK;
+ }
+ if (ClientLayerManager* clm = lm->AsClientLayerManager()) {
+ if (!clm->GetAPZTestData().ToJS(aOutContentTestData, aContext)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetCompositorAPZTestData(JSContext* aContext,
+ JS::MutableHandleValue aOutCompositorTestData)
+{
+ if (nsIWidget* widget = GetWidget()) {
+ RefPtr<LayerManager> lm = widget->GetLayerManager();
+ if (!lm) {
+ return NS_OK;
+ }
+ if (ClientLayerManager* clm = lm->AsClientLayerManager()) {
+ APZTestData compositorSideData;
+ clm->GetCompositorSideAPZTestData(&compositorSideData);
+ if (!compositorSideData.ToJS(aOutCompositorTestData, aContext)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::PostRestyleSelfEvent(nsIDOMElement* aElement)
+{
+ nsCOMPtr<Element> element = do_QueryInterface(aElement);
+ if (!element) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsLayoutUtils::PostRestyleEvent(element, eRestyle_Self, nsChangeHint(0));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetMediaSuspend(uint32_t* aSuspend)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ *aSuspend = window->GetMediaSuspend();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetMediaSuspend(uint32_t aSuspend)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ window->SetMediaSuspend(aSuspend);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetAudioMuted(bool* aMuted)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ *aMuted = window->GetAudioMuted();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAudioMuted(bool aMuted)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ window->SetAudioMuted(aMuted);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetAudioVolume(float* aVolume)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ *aVolume = window->GetAudioVolume();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAudioVolume(float aVolume)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ return window->SetAudioVolume(aVolume);
+}
+
+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::GetFrameUniformityTestData(JSContext* aContext,
+ JS::MutableHandleValue aOutFrameUniformity)
+{
+ nsIWidget* widget = GetWidget();
+ if (!widget) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<LayerManager> manager = widget->GetLayerManager();
+ if (!manager) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ FrameUniformityData outData;
+ manager->GetFrameUniformity(&outData);
+ outData.ToJS(aOutFrameUniformity, aContext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::XpconnectArgument(nsIDOMWindowUtils* aThis)
+{
+ // 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::GetElementsRestyled(uint64_t* aResult)
+{
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = presContext->ElementsRestyledCount();
+ 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::SetServiceWorkersTestingEnabled(bool aEnabled)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ window->SetServiceWorkersTestingEnabled(aEnabled);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetServiceWorkersTestingEnabled(bool *aEnabled)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ *aEnabled = window->GetServiceWorkersTestingEnabled();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::EnterChaosMode()
+{
+ ChaosMode::enterChaosMode();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::LeaveChaosMode()
+{
+ ChaosMode::leaveChaosMode();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ForceUseCounterFlush(nsIDOMNode *aNode)
+{
+ NS_ENSURE_ARG_POINTER(aNode);
+
+ if (nsCOMPtr<nsIDocument> doc = do_QueryInterface(aNode)) {
+ mozilla::css::ImageLoader* loader = doc->StyleImageLoader();
+ loader->FlushUseCounters();
+
+ static_cast<nsDocument*>(doc.get())->ReportUseCounters();
+ return NS_OK;
+ }
+
+ if (nsCOMPtr<nsIContent> content = do_QueryInterface(aNode)) {
+ if (HTMLImageElement* img = HTMLImageElement::FromContent(content)) {
+ img->FlushUseCounters();
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
+ bool* aRetVal)
+{
+ nsIPresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return presShell->HasRuleProcessorUsedByMultipleStyleSets(aSheetType,
+ aRetVal);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetNextPaintSyncId(int32_t aSyncId)
+{
+ if (nsIWidget* widget = GetWidget()) {
+ RefPtr<LayerManager> lm = widget->GetLayerManager();
+ if (lm && lm->AsClientLayerManager()) {
+ lm->AsClientLayerManager()->SetNextPaintSyncId(aSyncId);
+ return NS_OK;
+ }
+ }
+
+ NS_WARNING("Paint sync id could not be set on the ClientLayerManager");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::RespectDisplayPortSuppression(bool aEnabled)
+{
+ nsCOMPtr<nsIPresShell> shell(GetPresShell());
+ APZCCallbackHelper::RespectDisplayPortSuppression(aEnabled, shell);
+ 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;
+}
+
+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, nsIDOMNode** 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;
+}
diff --git a/dom/base/nsDOMWindowUtils.h b/dom/base/nsDOMWindowUtils.h
new file mode 100644
index 000000000..7f2ceb962
--- /dev/null
+++ b/dom/base/nsDOMWindowUtils.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 nsDOMWindowUtils_h_
+#define nsDOMWindowUtils_h_
+
+#include "nsWeakReference.h"
+
+#include "nsIDOMWindowUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+
+class nsGlobalWindow;
+class nsIPresShell;
+class nsIWidget;
+class nsPresContext;
+class nsIDocument;
+class nsView;
+struct nsPoint;
+
+namespace mozilla {
+ namespace layers {
+ class LayerTransactionChild;
+ } // 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(nsIDOMNode* aElement, bool aIsRoot)
+ {
+ mNodes.AppendElement(aElement);
+ mNodeIsRoot.AppendElement(aIsRoot);
+ mLength++;
+ }
+
+private:
+ ~nsTranslationNodeList() {}
+
+ nsTArray<nsCOMPtr<nsIDOMNode> > mNodes;
+ nsTArray<bool> mNodeIsRoot;
+ uint32_t mLength;
+};
+
+class nsDOMWindowUtils final : public nsIDOMWindowUtils,
+ public nsSupportsWeakReference
+{
+ typedef mozilla::widget::TextEventDispatcher
+ TextEventDispatcher;
+public:
+ explicit nsDOMWindowUtils(nsGlobalWindow *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(nsIDOMElement* aElement);
+
+ nsIPresShell* GetPresShell();
+ nsPresContext* GetPresContext();
+ nsIDocument* GetDocument();
+ mozilla::layers::LayerTransactionChild* GetLayerTransaction();
+
+ 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,
+ bool aToWindow,
+ bool *aPreventDefault,
+ bool aIsDOMEventSynthesized,
+ bool aIsWidgetEventSynthesized,
+ int32_t aButtons);
+
+ NS_IMETHOD SendPointerEventCommon(const nsAString& aType,
+ float aX,
+ float aY,
+ int32_t aButton,
+ int32_t aClickCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ float aPressure,
+ unsigned short aInputSourceArg,
+ int32_t aPointerId,
+ int32_t aWidth,
+ int32_t aHeight,
+ int32_t aTiltX,
+ int32_t aTiltY,
+ bool aIsPrimary,
+ bool aIsSynthesized,
+ uint8_t aOptionalArgCount,
+ bool aToWindow,
+ bool* aPreventDefault);
+
+ NS_IMETHOD SendTouchEventCommon(const nsAString& aType,
+ uint32_t* aIdentifiers,
+ int32_t* aXs,
+ int32_t* aYs,
+ uint32_t* aRxs,
+ uint32_t* aRys,
+ float* aRotationAngles,
+ float* aForces,
+ uint32_t aCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame,
+ bool aToWindow,
+ bool* aPreventDefault);
+};
+
+#endif
diff --git a/dom/base/nsDataDocumentContentPolicy.cpp b/dom/base/nsDataDocumentContentPolicy.cpp
new file mode 100644
index 000000000..0a728bac2
--- /dev/null
+++ b/dom/base/nsDataDocumentContentPolicy.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/. */
+
+/*
+ * Content policy implementation that prevents all loads of images,
+ * subframes, etc from documents loaded as data (eg documents loaded
+ * via XMLHttpRequest).
+ */
+
+#include "nsContentUtils.h"
+#include "nsDataDocumentContentPolicy.h"
+#include "nsNetUtil.h"
+#include "nsIProtocolHandler.h"
+#include "nsScriptSecurityManager.h"
+#include "nsIDocument.h"
+#include "nsINode.h"
+#include "nsIDOMWindow.h"
+#include "nsIURI.h"
+
+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(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestingLocation,
+ nsISupports *aRequestingContext,
+ const nsACString &aMimeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+ "We should only see external content policy types here.");
+
+ *aDecision = nsIContentPolicy::ACCEPT;
+ // Look for the document. In most cases, aRequestingContext is a node.
+ nsCOMPtr<nsIDocument> doc;
+ nsCOMPtr<nsINode> node = do_QueryInterface(aRequestingContext);
+ if (node) {
+ doc = node->OwnerDoc();
+ } else {
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aRequestingContext)) {
+ doc = window->GetDoc();
+ }
+ }
+
+ // DTDs are always OK to load
+ if (!doc || aContentType == nsIContentPolicy::TYPE_DTD) {
+ return NS_OK;
+ }
+
+ // Nothing else is OK to load for data documents
+ if (doc->IsLoadedAsData()) {
+ // ...but let static (print/print preview) documents to load fonts.
+ if (!doc->IsStaticDocument() || aContentType != nsIContentPolicy::TYPE_FONT) {
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+ return NS_OK;
+ }
+ }
+
+ nsIDocument* 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();
+ RefPtr<nsIURI> principalURI;
+ nsresult rv =
+ requestingPrincipal->GetURI(getter_AddRefs(principalURI));
+ if (NS_SUCCEEDED(rv) && principalURI) {
+ nsScriptSecurityManager::ReportError(
+ nullptr, NS_LITERAL_STRING("ExternalDataError"), principalURI,
+ aContentLocation);
+ }
+ }
+ } else if ((aContentType == nsIContentPolicy::TYPE_IMAGE ||
+ aContentType == nsIContentPolicy::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 (aContentType == nsIContentPolicy::TYPE_OBJECT ||
+ aContentType == nsIContentPolicy::TYPE_DOCUMENT ||
+ aContentType == nsIContentPolicy::TYPE_SUBDOCUMENT ||
+ aContentType == nsIContentPolicy::TYPE_SCRIPT ||
+ aContentType == nsIContentPolicy::TYPE_XSLT ||
+ aContentType == nsIContentPolicy::TYPE_FETCH ||
+ aContentType == nsIContentPolicy::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(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestingLocation,
+ nsISupports *aRequestingContext,
+ const nsACString &aMimeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ return ShouldLoad(aContentType, aContentLocation, aRequestingLocation,
+ aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal,
+ aDecision);
+}
diff --git a/dom/base/nsDataDocumentContentPolicy.h b/dom/base/nsDataDocumentContentPolicy.h
new file mode 100644
index 000000000..eb6a46dea
--- /dev/null
+++ b/dom/base/nsDataDocumentContentPolicy.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/. */
+
+/*
+ * 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()
+ {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+
+ nsDataDocumentContentPolicy()
+ {}
+};
+
+
+#endif /* nsDataDocumentContentPolicy_h__ */
diff --git a/dom/base/nsDeprecatedOperationList.h b/dom/base/nsDeprecatedOperationList.h
new file mode 100644
index 000000000..8fb381d9d
--- /dev/null
+++ b/dom/base/nsDeprecatedOperationList.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/. */
+// IWYU pragma: private, include "nsIDocument.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(GetAttributeNode)
+DEPRECATED_OPERATION(SetAttributeNode)
+DEPRECATED_OPERATION(GetAttributeNodeNS)
+DEPRECATED_OPERATION(SetAttributeNodeNS)
+DEPRECATED_OPERATION(RemoveAttributeNode)
+DEPRECATED_OPERATION(CreateAttribute)
+DEPRECATED_OPERATION(CreateAttributeNS)
+DEPRECATED_OPERATION(NodeValue)
+DEPRECATED_OPERATION(TextContent)
+DEPRECATED_OPERATION(EnablePrivilege)
+DEPRECATED_OPERATION(DOMExceptionCode)
+DEPRECATED_OPERATION(NoExposedProps)
+DEPRECATED_OPERATION(MutationEvent)
+DEPRECATED_OPERATION(Components)
+DEPRECATED_OPERATION(PrefixedVisibilityAPI)
+DEPRECATED_OPERATION(NodeIteratorDetach)
+DEPRECATED_OPERATION(LenientThis)
+DEPRECATED_OPERATION(GetPreventDefault)
+DEPRECATED_OPERATION(GetSetUserData)
+DEPRECATED_OPERATION(MozGetAsFile)
+DEPRECATED_OPERATION(UseOfCaptureEvents)
+DEPRECATED_OPERATION(UseOfReleaseEvents)
+DEPRECATED_OPERATION(UseOfDOM3LoadMethod)
+DEPRECATED_OPERATION(ChromeUseOfDOM3LoadMethod)
+DEPRECATED_OPERATION(ShowModalDialog)
+DEPRECATED_OPERATION(Window_Content)
+DEPRECATED_OPERATION(SyncXMLHttpRequest)
+DEPRECATED_OPERATION(DataContainerEvent)
+DEPRECATED_OPERATION(Window_Controllers)
+DEPRECATED_OPERATION(ImportXULIntoContent)
+DEPRECATED_OPERATION(PannerNodeDoppler)
+DEPRECATED_OPERATION(NavigatorGetUserMedia)
+DEPRECATED_OPERATION(WebrtcDeprecatedPrefix)
+DEPRECATED_OPERATION(RTCPeerConnectionGetStreams)
+DEPRECATED_OPERATION(AppCache)
+DEPRECATED_OPERATION(PrefixedImageSmoothingEnabled)
+DEPRECATED_OPERATION(PrefixedFullscreenAPI)
+DEPRECATED_OPERATION(LenientSetter)
+DEPRECATED_OPERATION(FileLastModifiedDate)
+DEPRECATED_OPERATION(ImageBitmapRenderingContext_TransferImageBitmap)
diff --git a/dom/base/nsDocElementCreatedNotificationRunner.h b/dom/base/nsDocElementCreatedNotificationRunner.h
new file mode 100644
index 000000000..1e53c3dd0
--- /dev/null
+++ b/dom/base/nsDocElementCreatedNotificationRunner.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDocument.h"
+
+class nsDocElementCreatedNotificationRunner : public mozilla::Runnable
+{
+public:
+ explicit nsDocElementCreatedNotificationRunner(nsIDocument* aDoc)
+ : mDoc(aDoc)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsContentSink::NotifyDocElementCreated(mDoc);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocument> mDoc;
+};
+
+#endif /* nsDocElementCreatedNotificationRunner_h */
diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
new file mode 100644
index 000000000..8e6920a0e
--- /dev/null
+++ b/dom/base/nsDocument.cpp
@@ -0,0 +1,12834 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsDocument.h"
+#include "nsIDocumentInlines.h"
+#include "mozilla/AnimationComparator.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Likely.h"
+#include <algorithm>
+
+#include "mozilla/Logging.h"
+#include "plstr.h"
+#include "mozilla/Sprintf.h"
+
+#include "mozilla/Telemetry.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsITextControlFrame.h"
+#include "nsNumberControlFrame.h"
+#include "nsUnicharUtils.h"
+#include "nsContentList.h"
+#include "nsCSSPseudoElements.h"
+#include "nsIObserver.h"
+#include "nsIBaseWindow.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/css/ImageLoader.h"
+#include "nsDocShell.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsCOMArray.h"
+#include "nsQueryObject.h"
+#include "nsDOMClassInfo.h"
+#include "mozilla/Services.h"
+#include "nsScreen.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "nsIDOMNodeFilter.h"
+
+#include "nsIDOMStyleSheet.h"
+#include "mozilla/dom/Attr.h"
+#include "nsIDOMDOMImplementation.h"
+#include "nsIDOMDocumentXBL.h"
+#include "mozilla/dom/Element.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/dom/CDATASection.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+#include "nsDOMString.h"
+#include "nsNodeUtils.h"
+#include "nsLayoutUtils.h" // for GetFrameForPoint
+#include "nsIFrame.h"
+#include "nsITabChild.h"
+
+#include "nsRange.h"
+#include "nsIDOMText.h"
+#include "nsIDOMComment.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/NodeIterator.h"
+#include "mozilla/dom/TreeWalker.h"
+
+#include "nsIServiceManager.h"
+#include "mozilla/dom/workers/ServiceWorkerManager.h"
+#include "imgLoader.h"
+
+#include "nsCanvasFrame.h"
+#include "nsContentCID.h"
+#include "nsError.h"
+#include "nsPresShell.h"
+#include "nsPresContext.h"
+#include "nsIJSON.h"
+#include "nsThreadUtils.h"
+#include "nsNodeInfoManager.h"
+#include "nsIFileChannel.h"
+#include "nsIMultiPartChannel.h"
+#include "nsIRefreshURI.h"
+#include "nsIWebNavigation.h"
+#include "nsIScriptError.h"
+#include "nsISimpleEnumerator.h"
+#include "nsStyleSheetService.h"
+
+#include "nsNetUtil.h" // for NS_NewURI
+#include "nsIInputStreamChannel.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+
+#include "nsIScriptSecurityManager.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsNullPrincipal.h"
+
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMElement.h"
+#include "nsFocusManager.h"
+
+// for radio group stuff
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIRadioVisitor.h"
+#include "nsIFormControl.h"
+
+#include "nsBidiUtils.h"
+
+#include "nsIParserService.h"
+#include "nsContentCreatorFunctions.h"
+
+#include "nsIScriptContext.h"
+#include "nsBindingManager.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsHTMLDocument.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsIRequest.h"
+#include "nsHostObjectProtocolHandler.h"
+
+#include "nsCharsetSource.h"
+#include "nsIParser.h"
+#include "nsIContentSink.h"
+
+#include "nsDateTimeFormatCID.h"
+#include "nsIDateTimeFormat.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "nsDOMCID.h"
+
+#include "jsapi.h"
+#include "nsIXPConnect.h"
+#include "xpcpublic.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsIContentPolicy.h"
+#include "nsContentPolicyUtils.h"
+#include "nsICategoryManager.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIDocumentLoader.h"
+#include "nsIContentViewer.h"
+#include "nsIXMLContentSink.h"
+#include "nsIXULDocument.h"
+#include "nsIPrompt.h"
+#include "nsIPropertyBag2.h"
+#include "mozilla/dom/PageTransitionEvent.h"
+#include "mozilla/dom/StyleRuleChangeEvent.h"
+#include "mozilla/dom/StyleSheetChangeEvent.h"
+#include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
+#include "nsJSUtils.h"
+#include "nsFrameLoader.h"
+#include "nsEscape.h"
+#include "nsObjectLoadingContent.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/MediaSource.h"
+#include "mozilla/dom/FlyWebService.h"
+
+#include "mozAutoDocUpdate.h"
+#include "nsGlobalWindow.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsDOMNavigationTiming.h"
+
+#include "nsSMILAnimationController.h"
+#include "imgIContainer.h"
+#include "nsSVGUtils.h"
+
+#include "nsRefreshDriver.h"
+
+// FOR CSP (autogenerated by xpidl)
+#include "nsIContentSecurityPolicy.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsHTMLCSSStyleSheet.h"
+#include "SVGAttrAnimationRuleProcessor.h"
+#include "mozilla/dom/DOMImplementation.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/Comment.h"
+#include "nsTextNode.h"
+#include "mozilla/dom/Link.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/dom/TouchEvent.h"
+
+#include "mozilla/Preferences.h"
+
+#include "imgILoader.h"
+#include "imgRequestProxy.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsSandboxFlags.h"
+#include "nsIAddonPolicyService.h"
+#include "mozilla/dom/AnimatableBinding.h"
+#include "mozilla/dom/AnonymousContent.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/WebComponentsBinding.h"
+#include "mozilla/dom/CustomElementRegistryBinding.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "nsFrame.h"
+#include "nsDOMCaretPosition.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsViewportInfo.h"
+#include "mozilla/StaticPtr.h"
+#include "nsITextControlElement.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsIEditor.h"
+#include "nsIDOMCSSStyleRule.h"
+#include "mozilla/css/Rule.h"
+#include "nsIDOMLocation.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "mozilla/dom/XPathEvaluator.h"
+#include "mozilla/dom/XPathNSResolverBinding.h"
+#include "mozilla/dom/XPathResult.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDocumentActivity.h"
+#include "nsIStructuredCloneContainer.h"
+#include "nsIMutableArray.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "nsWindowMemoryReporter.h"
+#include "mozilla/dom/Location.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/BoxObject.h"
+#include "gfxPrefs.h"
+#include "nsISupportsPrimitives.h"
+#include "mozilla/StyleSetHandle.h"
+#include "mozilla/StyleSetHandleInlines.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/TabGroup.h"
+
+#include "mozilla/DocLoadingTimelineMarker.h"
+
+#include "nsISpeculativeConnect.h"
+
+#include "mozilla/MediaManager.h"
+#ifdef MOZ_WEBRTC
+#include "IPeerConnection.h"
+#endif // MOZ_WEBRTC
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+typedef nsTArray<Link*> LinkArray;
+
+static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
+static LazyLogModule gCspPRLog("CSP");
+
+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;
+}
+
+#define NAME_NOT_VALID ((nsSimpleContentList*)1)
+
+nsIdentifierMapEntry::~nsIdentifierMapEntry()
+{
+}
+
+void
+nsIdentifierMapEntry::Traverse(nsCycleCollectionTraversalCallback* aCallback)
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mIdentifierMap mNameContentList");
+ aCallback->NoteXPCOMChild(static_cast<nsIDOMNodeList*>(mNameContentList));
+
+ if (mImageElement) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mIdentifierMap mImageElement element");
+ nsIContent* imageElement = mImageElement;
+ aCallback->NoteXPCOMChild(imageElement);
+ }
+}
+
+bool
+nsIdentifierMapEntry::IsEmpty()
+{
+ return mIdContentList.IsEmpty() && !mNameContentList &&
+ !mChangeCallbacks && !mImageElement;
+}
+
+Element*
+nsIdentifierMapEntry::GetIdElement()
+{
+ return mIdContentList.SafeElementAt(0);
+}
+
+Element*
+nsIdentifierMapEntry::GetImageIdElement()
+{
+ return mImageElement ? mImageElement.get() : GetIdElement();
+}
+
+void
+nsIdentifierMapEntry::AppendAllIdContent(nsCOMArray<nsIContent>* aElements)
+{
+ for (size_t i = 0; i < mIdContentList.Length(); ++i) {
+ aElements->AppendObject(mIdContentList[i]);
+ }
+}
+
+void
+nsIdentifierMapEntry::AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback,
+ void* aData, bool aForImage)
+{
+ if (!mChangeCallbacks) {
+ mChangeCallbacks = new nsTHashtable<ChangeCallbackEntry>;
+ }
+
+ ChangeCallback cc = { aCallback, aData, aForImage };
+ mChangeCallbacks->PutEntry(cc);
+}
+
+void
+nsIdentifierMapEntry::RemoveContentChangeCallback(nsIDocument::IDTargetObserver aCallback,
+ void* aData, bool aForImage)
+{
+ if (!mChangeCallbacks)
+ return;
+ ChangeCallback cc = { aCallback, aData, aForImage };
+ mChangeCallbacks->RemoveEntry(cc);
+ if (mChangeCallbacks->Count() == 0) {
+ mChangeCallbacks = nullptr;
+ }
+}
+
+void
+nsIdentifierMapEntry::FireChangeCallbacks(Element* aOldElement,
+ Element* aNewElement,
+ bool aImageOnly)
+{
+ if (!mChangeCallbacks)
+ return;
+
+ for (auto iter = mChangeCallbacks->ConstIter(); !iter.Done(); iter.Next()) {
+ nsIdentifierMapEntry::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();
+ }
+ }
+}
+
+namespace {
+
+struct PositionComparator
+{
+ Element* const mElement;
+ explicit PositionComparator(Element* const aElement) : mElement(aElement) {}
+
+ int operator()(void* aElement) const {
+ Element* curElement = static_cast<Element*>(aElement);
+ if (mElement == curElement) {
+ return 0;
+ }
+ if (nsContentUtils::PositionIsBefore(mElement, curElement)) {
+ return -1;
+ }
+ return 1;
+ }
+};
+
+} // namespace
+
+bool
+nsIdentifierMapEntry::AddIdElement(Element* aElement)
+{
+ NS_PRECONDITION(aElement, "Must have element");
+ NS_PRECONDITION(!mIdContentList.Contains(nullptr),
+ "Why is null in our list?");
+
+#ifdef DEBUG
+ Element* currentElement = mIdContentList.SafeElementAt(0);
+#endif
+
+ // Common case
+ if (mIdContentList.IsEmpty()) {
+ if (!mIdContentList.AppendElement(aElement))
+ return false;
+ NS_ASSERTION(currentElement == nullptr, "How did that happen?");
+ FireChangeCallbacks(nullptr, aElement);
+ return true;
+ }
+
+ // We seem to have multiple content nodes for the same id, or XUL is messing
+ // with us. Search for the right place to insert the content.
+
+ size_t idx;
+ if (BinarySearchIf(mIdContentList, 0, mIdContentList.Length(),
+ PositionComparator(aElement), &idx)) {
+ // Already in the list, so already in the right spot. Get out of here.
+ // XXXbz this only happens because XUL does all sorts of random
+ // UpdateIdTableEntry calls. Hate, hate, hate!
+ return true;
+ }
+
+ if (!mIdContentList.InsertElementAt(idx, aElement)) {
+ return false;
+ }
+
+ if (idx == 0) {
+ Element* oldElement = mIdContentList.SafeElementAt(1);
+ NS_ASSERTION(currentElement == oldElement, "How did that happen?");
+ FireChangeCallbacks(oldElement, aElement);
+ }
+ return true;
+}
+
+void
+nsIdentifierMapEntry::RemoveIdElement(Element* aElement)
+{
+ NS_PRECONDITION(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
+nsIdentifierMapEntry::SetImageElement(Element* aElement)
+{
+ Element* oldElement = GetImageIdElement();
+ mImageElement = aElement;
+ Element* newElement = GetImageIdElement();
+ if (oldElement != newElement) {
+ FireChangeCallbacks(oldElement, newElement, true);
+ }
+}
+
+void
+nsIdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement)
+{
+ if (!mNameContentList) {
+ mNameContentList = new nsSimpleContentList(aNode);
+ }
+
+ mNameContentList->AppendElement(aElement);
+}
+
+void
+nsIdentifierMapEntry::RemoveNameElement(Element* aElement)
+{
+ if (mNameContentList) {
+ mNameContentList->RemoveElement(aElement);
+ }
+}
+
+bool
+nsIdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty()
+{
+ Element* idElement = GetIdElement();
+ return idElement &&
+ nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
+}
+
+size_t
+nsIdentifierMapEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return nsStringHashKey::SizeOfExcludingThis(aMallocSizeOf);
+}
+
+// Helper structs for the content->subdoc map
+
+class SubDocMapEntry : public PLDHashEntryHdr
+{
+public:
+ // Both of these are strong references
+ Element *mKey; // must be first, to look like PLDHashEntryStub
+ nsIDocument *mSubDocument;
+};
+
+
+/**
+ * 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;
+ nsCOMArray<nsIFormControl> mRadioButtons;
+ uint32_t mRequiredRadioCount;
+ bool mGroupSuffersFromValueMissing;
+};
+
+
+nsDOMStyleSheetList::nsDOMStyleSheetList(nsIDocument *aDocument)
+{
+ mLength = -1;
+ // Not reference counted to avoid circular references.
+ // The document will tell us when its going away.
+ mDocument = aDocument;
+ mDocument->AddObserver(this);
+}
+
+nsDOMStyleSheetList::~nsDOMStyleSheetList()
+{
+ if (mDocument) {
+ mDocument->RemoveObserver(this);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsDOMStyleSheetList, StyleSheetList,
+ nsIDocumentObserver,
+ nsIMutationObserver)
+
+uint32_t
+nsDOMStyleSheetList::Length()
+{
+ if (!mDocument) {
+ return 0;
+ }
+
+ // XXX Find the number and then cache it. We'll use the
+ // observer notification to figure out if new ones have
+ // been added or removed.
+ if (-1 == mLength) {
+ mLength = mDocument->GetNumberOfStyleSheets();
+ }
+ return mLength;
+}
+
+StyleSheet*
+nsDOMStyleSheetList::IndexedGetter(uint32_t aIndex, bool& aFound)
+{
+ if (!mDocument || aIndex >= (uint32_t)mDocument->GetNumberOfStyleSheets()) {
+ aFound = false;
+ return nullptr;
+ }
+ aFound = true;
+ return mDocument->GetStyleSheetAt(aIndex);
+}
+
+void
+nsDOMStyleSheetList::NodeWillBeDestroyed(const nsINode *aNode)
+{
+ mDocument = nullptr;
+}
+
+void
+nsDOMStyleSheetList::StyleSheetAdded(StyleSheet* aStyleSheet,
+ bool aDocumentSheet)
+{
+ if (aDocumentSheet && -1 != mLength) {
+ mLength++;
+ }
+}
+
+void
+nsDOMStyleSheetList::StyleSheetRemoved(StyleSheet* aStyleSheet,
+ bool aDocumentSheet)
+{
+ if (aDocumentSheet && -1 != mLength) {
+ mLength--;
+ }
+}
+
+// nsOnloadBlocker implementation
+NS_IMPL_ISUPPORTS(nsOnloadBlocker, nsIRequest)
+
+NS_IMETHODIMP
+nsOnloadBlocker::GetName(nsACString &aResult)
+{
+ aResult.AssignLiteral("about:document-onload-blocker");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOnloadBlocker::IsPending(bool *_retval)
+{
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOnloadBlocker::GetStatus(nsresult *status)
+{
+ *status = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOnloadBlocker::Cancel(nsresult status)
+{
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsOnloadBlocker::Suspend(void)
+{
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsOnloadBlocker::Resume(void)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOnloadBlocker::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOnloadBlocker::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOnloadBlocker::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = nsIRequest::LOAD_NORMAL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ return NS_OK;
+}
+
+// ==================================================================
+
+nsExternalResourceMap::nsExternalResourceMap()
+ : mHaveShutDown(false)
+{
+}
+
+nsIDocument*
+nsExternalResourceMap::RequestResource(nsIURI* aURI,
+ nsINode* aRequestingNode,
+ nsDocument* 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.
+ NS_PRECONDITION(aURI, "Must have a URI");
+ NS_PRECONDITION(aRequestingNode, "Must have a node");
+ *aPendingLoad = nullptr;
+ if (mHaveShutDown) {
+ return nullptr;
+ }
+
+ // First, make sure we strip the ref from aURI.
+ nsCOMPtr<nsIURI> clone;
+ nsresult rv = aURI->CloneIgnoringRef(getter_AddRefs(clone));
+ if (NS_FAILED(rv) || !clone) {
+ return nullptr;
+ }
+
+ ExternalResource* resource;
+ mMap.Get(clone, &resource);
+ if (resource) {
+ return resource->mDocument;
+ }
+
+ RefPtr<PendingLoad> load;
+ mPendingLoads.Get(clone, getter_AddRefs(load));
+ if (load) {
+ load.forget(aPendingLoad);
+ return nullptr;
+ }
+
+ load = new PendingLoad(aDisplayDocument);
+
+ mPendingLoads.Put(clone, load);
+
+ if (NS_FAILED(load->StartLoad(clone, aRequestingNode))) {
+ // Make sure we don't thrash things by trying this load again, since
+ // chances are it failed for good reasons (security check, etc).
+ AddExternalResource(clone, nullptr, nullptr, aDisplayDocument);
+ } else {
+ load.forget(aPendingLoad);
+ }
+
+ return nullptr;
+}
+
+void
+nsExternalResourceMap::EnumerateResources(nsIDocument::nsSubDocEnumFunc aCallback,
+ void* aData)
+{
+ for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
+ nsExternalResourceMap::ExternalResource* resource = iter.UserData();
+ if (resource->mDocument && !aCallback(resource->mDocument, aData)) {
+ break;
+ }
+ }
+}
+
+void
+nsExternalResourceMap::Traverse(nsCycleCollectionTraversalCallback* aCallback) const
+{
+ // mPendingLoads will get cleared out as the requests complete, so
+ // no need to worry about those here.
+ for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
+ nsExternalResourceMap::ExternalResource* resource = iter.UserData();
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback,
+ "mExternalResourceMap.mMap entry"
+ "->mDocument");
+ aCallback->NoteXPCOMChild(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
+nsExternalResourceMap::HideViewers()
+{
+ for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIContentViewer> viewer = iter.UserData()->mViewer;
+ if (viewer) {
+ viewer->Hide();
+ }
+ }
+}
+
+void
+nsExternalResourceMap::ShowViewers()
+{
+ for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIContentViewer> viewer = iter.UserData()->mViewer;
+ if (viewer) {
+ viewer->Show();
+ }
+ }
+}
+
+void
+TransferZoomLevels(nsIDocument* aFromDoc,
+ nsIDocument* aToDoc)
+{
+ MOZ_ASSERT(aFromDoc && aToDoc,
+ "transferring zoom levels from/to null doc");
+
+ nsIPresShell* fromShell = aFromDoc->GetShell();
+ if (!fromShell)
+ return;
+
+ nsPresContext* fromCtxt = fromShell->GetPresContext();
+ if (!fromCtxt)
+ return;
+
+ nsIPresShell* toShell = aToDoc->GetShell();
+ if (!toShell)
+ return;
+
+ nsPresContext* toCtxt = toShell->GetPresContext();
+ if (!toCtxt)
+ return;
+
+ toCtxt->SetFullZoom(fromCtxt->GetFullZoom());
+ toCtxt->SetBaseMinFontSize(fromCtxt->BaseMinFontSize());
+ toCtxt->SetTextZoom(fromCtxt->TextZoom());
+ toCtxt->SetOverrideDPPX(fromCtxt->GetOverrideDPPX());
+}
+
+void
+TransferShowingState(nsIDocument* aFromDoc, nsIDocument* aToDoc)
+{
+ MOZ_ASSERT(aFromDoc && aToDoc,
+ "transferring showing state from/to null doc");
+
+ if (aFromDoc->IsShowing()) {
+ aToDoc->OnPageShow(true, nullptr);
+ }
+}
+
+nsresult
+nsExternalResourceMap::AddExternalResource(nsIURI* aURI,
+ nsIContentViewer* aViewer,
+ nsILoadGroup* aLoadGroup,
+ nsIDocument* aDisplayDocument)
+{
+ NS_PRECONDITION(aURI, "Unexpected call");
+ NS_PRECONDITION((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup),
+ "Must have both or neither");
+
+ RefPtr<PendingLoad> load;
+ mPendingLoads.Get(aURI, getter_AddRefs(load));
+ mPendingLoads.Remove(aURI);
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIDocument> doc;
+ if (aViewer) {
+ doc = aViewer->GetDocument();
+ NS_ASSERTION(doc, "Must have a document");
+
+ nsCOMPtr<nsIXULDocument> xulDoc = do_QueryInterface(doc);
+ if (xulDoc) {
+ // We don't handle XUL stuff here yet.
+ rv = NS_ERROR_NOT_AVAILABLE;
+ } else {
+ 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));
+ if (NS_SUCCEEDED(rv)) {
+ rv = aViewer->Open(nullptr, nullptr);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ doc = nullptr;
+ aViewer = nullptr;
+ aLoadGroup = nullptr;
+ }
+ }
+
+ ExternalResource* newResource = new ExternalResource();
+ mMap.Put(aURI, newResource);
+
+ newResource->mDocument = doc;
+ newResource->mViewer = aViewer;
+ newResource->mLoadGroup = aLoadGroup;
+ if (doc) {
+ TransferZoomLevels(aDisplayDocument, doc);
+ TransferShowingState(aDisplayDocument, doc);
+ }
+
+ const nsTArray< nsCOMPtr<nsIObserver> > & obs = load->Observers();
+ for (uint32_t i = 0; i < obs.Length(); ++i) {
+ obs[i]->Observe(doc, "external-resource-document-created", nullptr);
+ }
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsExternalResourceMap::PendingLoad,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+nsExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ nsExternalResourceMap& 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, aContext);
+}
+
+nsresult
+nsExternalResourceMap::PendingLoad::SetupViewer(nsIRequest* aRequest,
+ nsIContentViewer** aViewer,
+ nsILoadGroup** aLoadGroup)
+{
+ NS_PRECONDITION(!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);
+ nsXPIDLCString contractId;
+ nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", type.get(),
+ getter_Copies(contractId));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ do_GetService(contractId);
+ 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
+nsExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ NS_PRECONDITION(mTargetListener, "Shouldn't be getting called!");
+ if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) {
+ return NS_BINDING_ABORTED;
+ }
+ return mTargetListener->OnDataAvailable(aRequest, aContext, aStream, aOffset,
+ aCount);
+}
+
+NS_IMETHODIMP
+nsExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ // mTargetListener might be null if SetupViewer or AddExternalResource failed
+ if (mTargetListener) {
+ nsCOMPtr<nsIStreamListener> listener;
+ mTargetListener.swap(listener);
+ return listener->OnStopRequest(aRequest, aContext, aStatus);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsExternalResourceMap::PendingLoad::StartLoad(nsIURI* aURI,
+ nsINode* aRequestingNode)
+{
+ NS_PRECONDITION(aURI, "Must have a URI");
+ NS_PRECONDITION(aRequestingNode, "Must have a node");
+
+ 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_DATA_INHERITS,
+ nsIContentPolicy::TYPE_OTHER,
+ loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mURI = aURI;
+
+ return channel->AsyncOpen2(this);
+}
+
+NS_IMPL_ISUPPORTS(nsExternalResourceMap::LoadgroupCallbacks,
+ nsIInterfaceRequestor)
+
+#define IMPL_SHIM(_i) \
+ NS_IMPL_ISUPPORTS(nsExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i)
+
+IMPL_SHIM(nsILoadContext)
+IMPL_SHIM(nsIProgressEventSink)
+IMPL_SHIM(nsIChannelEventSink)
+IMPL_SHIM(nsISecurityEventSink)
+IMPL_SHIM(nsIApplicationCacheContainer)
+
+#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
+nsExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID & aIID,
+ void **aSink)
+{
+ if (mCallbacks &&
+ (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) || IID_IS(nsIAuthPrompt2) ||
+ IID_IS(nsITabChild))) {
+ return mCallbacks->GetInterface(aIID, aSink);
+ }
+
+ *aSink = nullptr;
+
+ TRY_SHIM(nsILoadContext);
+ TRY_SHIM(nsIProgressEventSink);
+ TRY_SHIM(nsIChannelEventSink);
+ TRY_SHIM(nsISecurityEventSink);
+ TRY_SHIM(nsIApplicationCacheContainer);
+
+ return NS_NOINTERFACE;
+}
+
+#undef TRY_SHIM
+#undef IID_IS
+
+nsExternalResourceMap::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 nsDOMStyleSheetSetList final : public DOMStringList
+{
+public:
+ explicit nsDOMStyleSheetSetList(nsIDocument* aDocument);
+
+ void Disconnect()
+ {
+ mDocument = nullptr;
+ }
+
+ virtual void EnsureFresh() override;
+
+protected:
+ nsIDocument* mDocument; // Our document; weak ref. It'll let us know if it
+ // dies.
+};
+
+nsDOMStyleSheetSetList::nsDOMStyleSheetSetList(nsIDocument* aDocument)
+ : mDocument(aDocument)
+{
+ NS_ASSERTION(mDocument, "Must have document!");
+}
+
+void
+nsDOMStyleSheetSetList::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
+ }
+
+ int32_t count = mDocument->GetNumberOfStyleSheets();
+ nsAutoString title;
+ for (int32_t index = 0; index < count; index++) {
+ StyleSheet* sheet = mDocument->GetStyleSheetAt(index);
+ NS_ASSERTION(sheet, "Null sheet in sheet list!");
+ // XXXheycam ServoStyleSheets don't expose their title yet.
+ if (sheet->IsServo()) {
+ NS_ERROR("stylo: ServoStyleSets don't expose their title yet");
+ continue;
+ }
+ sheet->AsGecko()->GetTitle(title);
+ if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
+ return;
+ }
+ }
+}
+
+// ==================================================================
+nsIDocument::SelectorCache::SelectorCache()
+ : nsExpirationTracker<SelectorCacheKey, 4>(1000, "nsIDocument::SelectorCache")
+{ }
+
+// CacheList takes ownership of aSelectorList.
+void nsIDocument::SelectorCache::CacheList(const nsAString& aSelector,
+ nsCSSSelectorList* aSelectorList)
+{
+ SelectorCacheKey* key = new SelectorCacheKey(aSelector);
+ mTable.Put(key->mKey, aSelectorList);
+ AddObject(key);
+}
+
+class nsIDocument::SelectorCacheKeyDeleter final : public Runnable
+{
+public:
+ explicit SelectorCacheKeyDeleter(SelectorCacheKey* aToDelete)
+ : mSelector(aToDelete)
+ {
+ MOZ_COUNT_CTOR(SelectorCacheKeyDeleter);
+ }
+
+protected:
+ ~SelectorCacheKeyDeleter()
+ {
+ MOZ_COUNT_DTOR(SelectorCacheKeyDeleter);
+ }
+
+public:
+ NS_IMETHOD Run() override
+ {
+ return NS_OK;
+ }
+
+private:
+ nsAutoPtr<SelectorCacheKey> mSelector;
+};
+
+void nsIDocument::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector)
+{
+ RemoveObject(aSelector);
+ mTable.Remove(aSelector->mKey);
+ nsCOMPtr<nsIRunnable> runnable = new SelectorCacheKeyDeleter(aSelector);
+ NS_DispatchToCurrentThread(runnable);
+}
+
+
+struct nsIDocument::FrameRequest
+{
+ FrameRequest(FrameRequestCallback& aCallback,
+ int32_t aHandle) :
+ mCallback(&aCallback),
+ mHandle(aHandle)
+ {}
+
+ // Conversion operator so that we can append these to a
+ // FrameRequestCallbackList
+ operator const RefPtr<FrameRequestCallback>& () const {
+ return mCallback;
+ }
+
+ // 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;
+};
+
+static already_AddRefed<mozilla::dom::NodeInfo> nullNodeInfo;
+
+// ==================================================================
+// =
+// ==================================================================
+nsIDocument::nsIDocument()
+ : nsINode(nullNodeInfo),
+ mReferrerPolicySet(false),
+ mReferrerPolicy(mozilla::net::RP_Default),
+ mBlockAllMixedContent(false),
+ mBlockAllMixedContentPreloads(false),
+ mUpgradeInsecureRequests(false),
+ mUpgradeInsecurePreloads(false),
+ mCharacterSet(NS_LITERAL_CSTRING("ISO-8859-1")),
+ mNodeInfoManager(nullptr),
+ mCompatMode(eCompatibility_FullStandards),
+ mVisibilityState(dom::VisibilityState::Hidden),
+ mIsInitialDocumentInWindow(false),
+ mMayStartLayout(true),
+ 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),
+ mIsBeingUsedAsImage(false),
+ mHasLinksToUpdate(false),
+ mFontFaceSetDirty(true),
+ mGetUserFontSetCalled(false),
+ mPostedFlushUserFontSet(false),
+ mDidFireDOMContentLoaded(true),
+ mHasScrollLinkedEffect(false),
+ mFrameRequestCallbacksScheduled(false),
+ mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
+ mPartID(0),
+ mUserHasInteracted(false)
+{
+ SetIsInDocument();
+
+ PR_INIT_CLIST(&mDOMMediaQueryLists);
+}
+
+// NOTE! nsDocument::operator new() zeroes out all members, so don't
+// bother initializing members to 0.
+
+nsDocument::nsDocument(const char* aContentType)
+ : nsIDocument()
+ , mViewportType(Unknown)
+{
+ SetContentTypeInternal(nsDependentCString(aContentType));
+
+ if (gDocumentLeakPRLog)
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p created", this));
+
+ // Start out mLastStyleSheetSet as null, per spec
+ SetDOMStringToNull(mLastStyleSheetSet);
+
+ // void state used to differentiate an empty source from an unselected source
+ mPreloadPictureFoundSource.SetIsVoid(true);
+
+ mEverInForeground = false;
+}
+
+void
+nsDocument::ClearAllBoxObjects()
+{
+ if (mBoxObjectTable) {
+ for (auto iter = mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) {
+ nsPIBoxObject* boxObject = iter.UserData();
+ if (boxObject) {
+ boxObject->Clear();
+ }
+ }
+ delete mBoxObjectTable;
+ mBoxObjectTable = nullptr;
+ }
+}
+
+nsIDocument::~nsIDocument()
+{
+ MOZ_ASSERT(PR_CLIST_IS_EMPTY(&mDOMMediaQueryLists),
+ "must not have media query lists left");
+
+ if (mNodeInfoManager) {
+ mNodeInfoManager->DropDocumentReference();
+ }
+
+ if (mDocGroup) {
+ mDocGroup->RemoveDocument(this);
+ }
+
+ UnlinkOriginalDocumentIfStatic();
+}
+
+bool
+nsDocument::IsAboutPage()
+{
+ nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
+ nsCOMPtr<nsIURI> uri;
+ principal->GetURI(getter_AddRefs(uri));
+ bool isAboutScheme = true;
+ if (uri) {
+ uri->SchemeIs("about", &isAboutScheme);
+ }
+ return isAboutScheme;
+}
+
+nsDocument::~nsDocument()
+{
+ if (gDocumentLeakPRLog)
+ MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
+ ("DOCUMENT %p destroyed", this));
+
+ NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
+
+ if (IsTopLevelContentDocument()) {
+ //don't report for about: pages
+ if (!IsAboutPage()) {
+ // Record the page load
+ uint32_t pageLoaded = 1;
+ Accumulate(Telemetry::MIXED_CONTENT_UNBLOCK_COUNTER, pageLoaded);
+ // Record the mixed content status of the docshell in Telemetry
+ enum {
+ NO_MIXED_CONTENT = 0, // There is no Mixed Content on the page
+ MIXED_DISPLAY_CONTENT = 1, // The page attempted to load Mixed Display Content
+ MIXED_ACTIVE_CONTENT = 2, // The page attempted to load Mixed Active Content
+ MIXED_DISPLAY_AND_ACTIVE_CONTENT = 3 // The page attempted to load Mixed Display & Mixed Active Content
+ };
+
+ bool mixedActiveLoaded = GetHasMixedActiveContentLoaded();
+ bool mixedActiveBlocked = GetHasMixedActiveContentBlocked();
+
+ bool mixedDisplayLoaded = GetHasMixedDisplayContentLoaded();
+ bool mixedDisplayBlocked = GetHasMixedDisplayContentBlocked();
+
+ bool hasMixedDisplay = (mixedDisplayBlocked || mixedDisplayLoaded);
+ bool hasMixedActive = (mixedActiveBlocked || mixedActiveLoaded);
+
+ uint32_t mixedContentLevel = NO_MIXED_CONTENT;
+ if (hasMixedDisplay && hasMixedActive) {
+ mixedContentLevel = MIXED_DISPLAY_AND_ACTIVE_CONTENT;
+ } else if (hasMixedActive){
+ mixedContentLevel = MIXED_ACTIVE_CONTENT;
+ } else if (hasMixedDisplay) {
+ mixedContentLevel = MIXED_DISPLAY_CONTENT;
+ }
+ Accumulate(Telemetry::MIXED_CONTENT_PAGE_LOAD, mixedContentLevel);
+
+ // record mixed object subrequest telemetry
+ if (mHasMixedContentObjectSubrequest) {
+ /* mixed object subrequest loaded on page*/
+ Accumulate(Telemetry::MIXED_CONTENT_OBJECT_SUBREQUEST, 1);
+ } else {
+ /* no mixed object subrequests loaded on page*/
+ Accumulate(Telemetry::MIXED_CONTENT_OBJECT_SUBREQUEST, 0);
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ ReportUseCounters();
+
+ 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;
+
+ // Destroy link map now so we don't waste time removing
+ // links one by one
+ DestroyElementMaps();
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ for (uint32_t indx = mChildren.ChildCount(); indx-- != 0; ) {
+ mChildren.ChildAt(indx)->UnbindFromTree();
+ mChildren.RemoveChildAt(indx);
+ }
+ mFirstChild = nullptr;
+ mCachedRootElement = nullptr;
+
+ // Let the stylesheets know we're going away
+ for (StyleSheet* sheet : mStyleSheets) {
+ sheet->SetOwningDocument(nullptr);
+ }
+ for (auto& sheets : mAdditionalSheets) {
+ for (StyleSheet* sheet : sheets) {
+ sheet->SetOwningDocument(nullptr);
+ }
+ }
+ if (mAttrStyleSheet) {
+ mAttrStyleSheet->SetOwningDocument(nullptr);
+ }
+ // We don't own the mOnDemandBuiltInUASheets, so we don't need to reset them.
+
+ 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();
+ }
+
+ delete mHeaderData;
+
+ ClearAllBoxObjects();
+
+ mPendingTitleChangeEvent.Revoke();
+
+ mPlugins.Clear();
+}
+
+NS_INTERFACE_TABLE_HEAD(nsDocument)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE_BEGIN
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsDocument, nsISupports, nsINode)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsINode)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDocument)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMDocument)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMNode)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMDocumentXBL)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIScriptObjectPrincipal)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMEventTarget)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, mozilla::dom::EventTarget)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsISupportsWeakReference)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIRadioGroupContainer)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIMutationObserver)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIApplicationCacheContainer)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIObserver)
+ NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMXPathEvaluator)
+ NS_INTERFACE_TABLE_END
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsDocument)
+NS_INTERFACE_MAP_END
+
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocument)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsDocument::Release()
+{
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ NS_ASSERT_OWNINGTHREAD(nsDocument);
+ nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(nsDocument)::Upcast(this);
+ bool shouldDelete = false;
+ nsrefcnt count = mRefCnt.decr(base, &shouldDelete);
+ NS_LOG_RELEASE(this, count, "nsDocument");
+ if (count == 0) {
+ if (mStackRefCnt && !mNeedsReleaseAfterStackRefCntRelease) {
+ mNeedsReleaseAfterStackRefCntRelease = true;
+ NS_ADDREF_THIS();
+ return mRefCnt.get();
+ }
+ mRefCnt.incr(base);
+ nsNodeUtils::LastRelease(this);
+ mRefCnt.decr(base);
+ if (shouldDelete) {
+ mRefCnt.stabilizeForDeletion();
+ DeleteCycleCollectable();
+ }
+ }
+ return count;
+}
+
+NS_IMETHODIMP_(void)
+nsDocument::DeleteCycleCollectable()
+{
+ delete this;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDocument)
+ 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(nsDocument)
+ return Element::CanSkipInCC(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDocument)
+ return Element::CanSkipThis(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+static const char* kNSURIs[] = {
+ "([none])",
+ "(xmlns)",
+ "(xml)",
+ "(xhtml)",
+ "(XLink)",
+ "(XSLT)",
+ "(XBL)",
+ "(MathML)",
+ "(RDF)",
+ "(XUL)"
+};
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
+ 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();
+ if (nsid < ArrayLength(kNSURIs)) {
+ SprintfLiteral(name, "nsDocument %s %s %s",
+ loadedAsData.get(), kNSURIs[nsid], uri.get());
+ }
+ else {
+ SprintfLiteral(name, "nsDocument %s %s",
+ loadedAsData.get(), uri.get());
+ }
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ }
+ else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsDocument, tmp->mRefCnt.get())
+ }
+
+ // Always need to traverse script objects, so do that before we check
+ // if we're uncollectable.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+
+ if (!nsINode::Traverse(tmp, cb)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+
+ if (tmp->mMaybeEndOutermostXBLUpdateRunner) {
+ // The cached runnable keeps a reference to the document object..
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mMaybeEndOutermostXBLUpdateRunner.mObj");
+ cb.NoteXPCOMChild(ToSupports(tmp));
+ }
+
+ for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done();
+ iter.Next()) {
+ iter.Get()->Traverse(&cb);
+ }
+
+ tmp->mExternalResourceMap.Traverse(&cb);
+
+ // Traverse the mChildren nsAttrAndChildArray.
+ for (int32_t indx = int32_t(tmp->mChildren.ChildCount()); indx > 0; --indx) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildren[i]");
+ cb.NoteXPCOMChild(tmp->mChildren.ChildAt(indx - 1));
+ }
+
+ // Traverse all nsIDocument pointer members.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
+
+ // Traverse all nsDocument nsCOMPtrs.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImportManager)
+
+ for (auto iter = tmp->mRadioGroups.Iter(); !iter.Done(); iter.Next()) {
+ nsRadioGroupStruct* radioGroup = iter.UserData();
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+ cb, "mRadioGroups entry->mSelectedRadioButton");
+ cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton));
+
+ uint32_t i, count = radioGroup->mRadioButtons.Count();
+ for (i = 0; i < count; ++i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+ cb, "mRadioGroups entry->mRadioButtons[i]");
+ cb.NoteXPCOMChild(radioGroup->mRadioButtons[i]);
+ }
+ }
+
+ // The boxobject for an element will only exist as long as it's in the
+ // document, so we'll traverse the table here instead of from the element.
+ if (tmp->mBoxObjectTable) {
+ for (auto iter = tmp->mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mBoxObjectTable entry");
+ cb.NoteXPCOMChild(iter.UserData());
+ }
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleAttrStyleSheet)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXPathEvaluator)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLayoutHistoryState)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFirstBaseNodeWithHref)
+ 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(mStateObjectCached)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
+
+ // Traverse all our nsCOMArrays.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnDemandBuiltInUASheets)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntersectionObservers)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubImportLinks)
+
+ for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]");
+ cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback);
+ }
+
+ // 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(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 (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
+ l != &tmp->mDOMMediaQueryLists; l = PR_NEXT_LINK(l)) {
+ MediaQueryList *mql = static_cast<MediaQueryList*>(l);
+ if (mql->HasListeners()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
+ cb.NoteXPCOMChild(mql);
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDocument)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
+ tmp->mInUnlinkOrDeletion = true;
+
+ // Clear out our external resources
+ tmp->mExternalResourceMap.Shutdown();
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ nsINode::Unlink(tmp);
+
+ // Unlink the mChildren nsAttrAndChildArray.
+ for (int32_t indx = int32_t(tmp->mChildren.ChildCount()) - 1;
+ indx >= 0; --indx) {
+ tmp->mChildren.ChildAt(indx)->UnbindFromTree();
+ tmp->mChildren.RemoveChildAt(indx);
+ }
+ tmp->mFirstChild = nullptr;
+
+ tmp->UnlinkOriginalDocumentIfStatic();
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mXPathEvaluator)
+ tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFirstBaseNodeWithHref)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMaybeEndOutermostXBLUpdateRunner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMasterDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubImportLinks)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
+
+ tmp->mParentDocument = nullptr;
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
+
+ tmp->ClearAllBoxObjects();
+
+ if (tmp->mListenerManager) {
+ tmp->mListenerManager->Disconnect();
+ tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
+ tmp->mListenerManager = nullptr;
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets)
+
+ if (tmp->mStyleSheetSetList) {
+ tmp->mStyleSheetSetList->Disconnect();
+ tmp->mStyleSheetSetList = nullptr;
+ }
+
+ delete tmp->mSubDocuments;
+ tmp->mSubDocuments = nullptr;
+
+ tmp->mFrameRequestCallbacks.Clear();
+ MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled,
+ "How did we get here without our presshell going away "
+ "first?");
+
+ tmp->mRadioGroups.Clear();
+
+ // nsDocument 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->mIdentifierMap.Clear();
+ 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 (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
+ l != &tmp->mDOMMediaQueryLists; ) {
+ PRCList *next = PR_NEXT_LINK(l);
+ MediaQueryList *mql = static_cast<MediaQueryList*>(l);
+ mql->RemoveAllListeners();
+ l = next;
+ }
+
+ tmp->mInUnlinkOrDeletion = false;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+nsresult
+nsDocument::Init()
+{
+ if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ // Force initialization.
+ nsINode::nsSlots* slots = Slots();
+
+ // Prepend self as mutation-observer whether we need it or not (some
+ // subclasses currently do, other don't). This is because the code in
+ // nsNodeUtils always notifies the first observer first, expecting the
+ // first observer to be the document.
+ NS_ENSURE_TRUE(slots->mMutationObservers.PrependElementUnlessExists(static_cast<nsIMutationObserver*>(this)),
+ NS_ERROR_OUT_OF_MEMORY);
+
+
+ mOnloadBlocker = new nsOnloadBlocker();
+ mCSSLoader = new mozilla::css::Loader(this);
+ // Assume we're not quirky, until we know otherwise
+ mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards);
+
+ mStyleImageLoader = new mozilla::css::ImageLoader(this);
+
+ mNodeInfoManager = new nsNodeInfoManager();
+ nsresult rv = mNodeInfoManager->Init(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mNodeInfo keeps NodeInfoManager alive!
+ mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo();
+ NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY);
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+ "Bad NodeType in aNodeInfo");
+
+ NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!");
+
+ // 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 nsScriptLoader(this);
+
+ mozilla::HoldJSObjects(this);
+
+ return NS_OK;
+}
+
+void
+nsIDocument::DeleteAllProperties()
+{
+ for (uint32_t i = 0; i < GetPropertyTableCount(); ++i) {
+ PropertyTable(i)->DeleteAllProperties();
+ }
+}
+
+void
+nsIDocument::DeleteAllPropertiesFor(nsINode* aNode)
+{
+ for (uint32_t i = 0; i < GetPropertyTableCount(); ++i) {
+ PropertyTable(i)->DeleteAllPropertiesFor(aNode);
+ }
+}
+
+nsPropertyTable*
+nsIDocument::GetExtraPropertyTable(uint16_t aCategory)
+{
+ NS_ASSERTION(aCategory > 0, "Category 0 should have already been handled");
+ while (aCategory >= mExtraPropertyTables.Length() + 1) {
+ mExtraPropertyTables.AppendElement(new nsPropertyTable());
+ }
+ return mExtraPropertyTables[aCategory - 1];
+}
+
+bool
+nsIDocument::IsVisibleConsideringAncestors() const
+{
+ const nsIDocument *parent = this;
+ do {
+ if (!parent->IsVisible()) {
+ return false;
+ }
+ } while ((parent = parent->GetParentDocument()));
+
+ return true;
+ }
+
+void
+nsDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIPrincipal> principal;
+ if (aChannel) {
+ // Note: this code is duplicated in XULDocument::StartDocumentLoad and
+ // nsScriptSecurityManager::GetChannelResultPrincipal.
+ // Note: this should match nsDocShell::OnLoadingSite
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+
+ nsIScriptSecurityManager *securityManager =
+ nsContentUtils::GetSecurityManager();
+ if (securityManager) {
+ securityManager->GetChannelResultPrincipal(aChannel,
+ getter_AddRefs(principal));
+ }
+ }
+
+ ResetToURI(uri, aLoadGroup, principal);
+
+ // 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;
+
+ nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
+ if (bag) {
+ nsCOMPtr<nsIURI> baseURI;
+ bag->GetPropertyAsInterface(NS_LITERAL_STRING("baseURI"),
+ NS_GET_IID(nsIURI), getter_AddRefs(baseURI));
+ if (baseURI) {
+ mDocumentBaseURI = baseURI;
+ mChromeXHRDocBaseURI = nullptr;
+ }
+ }
+
+ mChannel = aChannel;
+}
+
+void
+nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
+ nsIPrincipal* aPrincipal)
+{
+ NS_PRECONDITION(aURI, "Null URI passed to ResetToURI");
+
+ if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
+ PR_LogPrint("DOCUMENT %p ResetToURI %s", this,
+ aURI->GetSpecOrDefault().get());
+ }
+
+ mSecurityInfo = nullptr;
+
+ mDocumentLoadGroup = nullptr;
+
+ // Delete references to sub-documents and kill the subdocument map,
+ // if any. It holds strong references
+ delete mSubDocuments;
+ mSubDocuments = nullptr;
+
+ // Destroy link map now so we don't waste time removing
+ // links one by one
+ DestroyElementMaps();
+
+ bool oldVal = mInUnlinkOrDeletion;
+ mInUnlinkOrDeletion = true;
+ uint32_t count = mChildren.ChildCount();
+ { // Scope for update
+ MOZ_AUTO_DOC_UPDATE(this, UPDATE_CONTENT_MODEL, true);
+ for (int32_t i = int32_t(count) - 1; i >= 0; i--) {
+ nsCOMPtr<nsIContent> content = mChildren.ChildAt(i);
+
+ nsIContent* previousSibling = content->GetPreviousSibling();
+
+ if (nsINode::GetFirstChild() == content) {
+ mFirstChild = content->GetNextSibling();
+ }
+ mChildren.RemoveChildAt(i);
+ nsNodeUtils::ContentRemoved(this, content, i, previousSibling);
+ content->UnbindFromTree();
+ }
+ mCachedRootElement = nullptr;
+ }
+ mInUnlinkOrDeletion = oldVal;
+
+ // 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.
+ SetPrincipal(nullptr);
+
+ // Clear the original URI so SetDocumentURI sets it.
+ mOriginalURI = nullptr;
+
+ SetDocumentURI(aURI);
+ mChromeXHRDocURI = nullptr;
+ // If mDocumentBaseURI is null, nsIDocument::GetBaseURI() returns
+ // mDocumentURI.
+ mDocumentBaseURI = nullptr;
+ mChromeXHRDocBaseURI = nullptr;
+
+ 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?
+ }
+
+ mLastModified.Truncate();
+ // XXXbz I guess we're assuming that the caller will either pass in
+ // a channel with a useful type or call SetContentType?
+ SetContentTypeInternal(EmptyCString());
+ mContentLanguage.Truncate();
+ mBaseTarget.Truncate();
+ mReferrer.Truncate();
+
+ mXMLDeclarationBits = 0;
+
+ // Now get our new principal
+ if (aPrincipal) {
+ SetPrincipal(aPrincipal);
+ } 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->
+ GetLoadContextCodebasePrincipal(mDocumentURI, loadContext,
+ getter_AddRefs(principal));
+ if (NS_SUCCEEDED(rv)) {
+ SetPrincipal(principal);
+ }
+ }
+ }
+
+ // Refresh the principal on the compartment.
+ if (nsPIDOMWindowInner* win = GetInnerWindow()) {
+ nsGlobalWindow::Cast(win)->RefreshCompartmentPrincipal();
+ }
+}
+
+void
+nsDocument::RemoveDocStyleSheetsFromStyleSets()
+{
+ // The stylesheets should forget us
+ for (StyleSheet* sheet : Reversed(mStyleSheets)) {
+ sheet->SetOwningDocument(nullptr);
+
+ if (sheet->IsApplicable()) {
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ shell->StyleSet()->RemoveDocStyleSheet(sheet);
+ }
+ }
+ // XXX Tell observers?
+ }
+}
+
+void
+nsDocument::RemoveStyleSheetsFromStyleSets(
+ const nsTArray<RefPtr<StyleSheet>>& aSheets,
+ SheetType aType)
+{
+ // The stylesheets should forget us
+ for (StyleSheet* sheet : Reversed(aSheets)) {
+ sheet->SetOwningDocument(nullptr);
+
+ if (sheet->IsApplicable()) {
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ shell->StyleSet()->RemoveStyleSheet(aType, sheet);
+ }
+ }
+ // XXX Tell observers?
+ }
+}
+
+void
+nsDocument::ResetStylesheetsToURI(nsIURI* aURI)
+{
+ MOZ_ASSERT(aURI);
+
+ mozAutoDocUpdate upd(this, UPDATE_STYLE, true);
+ if (mStyleSetFilled) {
+ // Skip removing style sheets from the style set if we know we haven't
+ // filled the style set. (This allows us to avoid calling
+ // GetStyleBackendType() too early.)
+ RemoveDocStyleSheetsFromStyleSets();
+ RemoveStyleSheetsFromStyleSets(mOnDemandBuiltInUASheets, SheetType::Agent);
+ RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet], SheetType::Agent);
+ RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet], SheetType::User);
+ RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet], SheetType::Doc);
+
+ if (GetStyleBackendType() == StyleBackendType::Gecko) {
+ nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance();
+ if (sheetService) {
+ RemoveStyleSheetsFromStyleSets(*sheetService->AuthorStyleSheets(), SheetType::Doc);
+ }
+ } else {
+ NS_ERROR("stylo: nsStyleSheetService doesn't handle ServoStyleSheets yet");
+ }
+
+ mStyleSetFilled = false;
+ }
+
+ // Release all the sheets
+ mStyleSheets.Clear();
+ mOnDemandBuiltInUASheets.Clear();
+ for (auto& sheets : mAdditionalSheets) {
+ sheets.Clear();
+ }
+
+ // NOTE: We don't release the catalog sheets. It doesn't really matter
+ // now, but it could in the future -- in which case not releasing them
+ // is probably the right thing to do.
+
+ // 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 (!mSVGAttrAnimationRuleProcessor) {
+ mSVGAttrAnimationRuleProcessor =
+ new mozilla::SVGAttrAnimationRuleProcessor();
+ }
+
+ // Now set up our style sets
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ FillStyleSet(shell->StyleSet());
+ }
+}
+
+static void
+AppendSheetsToStyleSet(StyleSetHandle aStyleSet,
+ const nsTArray<RefPtr<StyleSheet>>& aSheets,
+ SheetType aType)
+{
+ for (StyleSheet* sheet : Reversed(aSheets)) {
+ aStyleSet->AppendStyleSheet(aType, sheet);
+ }
+}
+
+
+void
+nsDocument::FillStyleSet(StyleSetHandle aStyleSet)
+{
+ NS_PRECONDITION(aStyleSet, "Must have a style set");
+ NS_PRECONDITION(aStyleSet->SheetCount(SheetType::Doc) == 0,
+ "Style set already has document sheets?");
+
+ MOZ_ASSERT(!mStyleSetFilled);
+
+ for (StyleSheet* sheet : Reversed(mStyleSheets)) {
+ if (sheet->IsApplicable()) {
+ aStyleSet->AddDocStyleSheet(sheet, this);
+ }
+ }
+
+ if (aStyleSet->IsGecko()) {
+ nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance();
+ if (sheetService) {
+ for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
+ aStyleSet->AppendStyleSheet(SheetType::Doc, sheet);
+ }
+ }
+
+ // Iterate backwards to maintain order
+ for (StyleSheet* sheet : Reversed(mOnDemandBuiltInUASheets)) {
+ if (sheet->IsApplicable()) {
+ aStyleSet->PrependStyleSheet(SheetType::Agent, sheet);
+ }
+ }
+ } else {
+ NS_WARNING("stylo: Not yet checking nsStyleSheetService for Servo-backed "
+ "documents. See bug 1290224");
+ }
+
+ AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet],
+ SheetType::Agent);
+ AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eUserSheet],
+ SheetType::User);
+ AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet],
+ SheetType::Doc);
+
+ mStyleSetFilled = true;
+}
+
+static void
+WarnIfSandboxIneffective(nsIDocShell* aDocShell,
+ uint32_t aSandboxFlags,
+ nsIChannel* aChannel)
+{
+ // 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)) {
+ nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
+ aDocShell->GetSameTypeParent(getter_AddRefs(parentAsItem));
+ nsCOMPtr<nsIDocShell> parentDocShell = do_QueryInterface(parentAsItem);
+ if (!parentDocShell) {
+ return;
+ }
+
+ // Don't warn if our parent is not the top-level document.
+ nsCOMPtr<nsIDocShellTreeItem> grandParentAsItem;
+ parentDocShell->GetSameTypeParent(getter_AddRefs(grandParentAsItem));
+ if (grandParentAsItem) {
+ return;
+ }
+
+ nsCOMPtr<nsIChannel> parentChannel;
+ parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel));
+ if (!parentChannel) {
+ return;
+ }
+ nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> parentDocument = parentDocShell->GetDocument();
+ nsCOMPtr<nsIURI> iframeUri;
+ parentChannel->GetURI(getter_AddRefs(iframeUri));
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Iframe Sandbox"),
+ parentDocument,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ "BothAllowScriptsAndSameOriginPresent",
+ nullptr, 0, iframeUri);
+ }
+}
+
+nsresult
+nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener **aDocListener,
+ bool aReset, nsIContentSink* aSink)
+{
+ if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ PR_LogPrint("DOCUMENT %p StartDocumentLoad %s",
+ this, uri ? uri->GetSpecOrDefault().get() : "");
+ }
+
+ MOZ_ASSERT(NodePrincipal()->GetAppId() != nsIScriptSecurityManager::UNKNOWN_APP_ID,
+ "Document should never have UNKNOWN_APP_ID");
+
+ MOZ_ASSERT(GetReadyStateEnum() == nsIDocument::READYSTATE_UNINITIALIZED,
+ "Bad readyState");
+ SetReadyStateInternal(READYSTATE_LOADING);
+
+ if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
+ mLoadedAsData = 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;
+
+ if (aReset) {
+ Reset(aChannel, aLoadGroup);
+ }
+
+ nsAutoCString contentType;
+ nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
+ if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString(
+ NS_LITERAL_STRING("contentType"), 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);
+ SetContentTypeInternal(Substring(start, semicolon));
+ }
+
+ RetrieveRelevantHeaders(aChannel);
+
+ mChannel = aChannel;
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
+ if (inStrmChan) {
+ bool isSrcdocChannel;
+ inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
+ if (isSrcdocChannel) {
+ mIsSrcdocDocument = true;
+ }
+ }
+
+ // 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 from docshell
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (docShell && !(loadInfo && loadInfo->GetLoadErrorPage())) {
+ nsresult rv = docShell->GetSandboxFlags(&mSandboxFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel());
+ }
+
+ // The CSP directive upgrade-insecure-requests not only applies to the
+ // toplevel document, but also to nested documents. Let's propagate that
+ // flag from the parent to the nested document.
+ nsCOMPtr<nsIDocShellTreeItem> treeItem = this->GetDocShell();
+ if (treeItem) {
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
+ treeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
+ if (sameTypeParent) {
+ nsIDocument* doc = sameTypeParent->GetDocument();
+ mBlockAllMixedContent = doc->GetBlockAllMixedContent(false);
+ // if the parent document makes use of block-all-mixed-content
+ // then subdocument preloads should always be blocked.
+ mBlockAllMixedContentPreloads =
+ mBlockAllMixedContent || doc->GetBlockAllMixedContent(true);
+
+ mUpgradeInsecureRequests = doc->GetUpgradeInsecureRequests(false);
+ // if the parent document makes use of upgrade-insecure-requests
+ // then subdocument preloads should always be upgraded.
+ mUpgradeInsecurePreloads =
+ mUpgradeInsecureRequests || doc->GetUpgradeInsecureRequests(true);
+ }
+ }
+
+ // If this is not a data document, set CSP.
+ if (!mLoadedAsData) {
+ nsresult rv = InitCSP(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+void
+nsDocument::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
+nsDocument::ApplySettingsFromCSP(bool aSpeculative)
+{
+ nsresult rv = NS_OK;
+ if (!aSpeculative) {
+ // 1) apply settings from regular CSP
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (csp) {
+ // Set up any Referrer Policy specified by CSP
+ bool hasReferrerPolicy = false;
+ uint32_t referrerPolicy = mozilla::net::RP_Default;
+ rv = csp->GetReferrerPolicy(&referrerPolicy, &hasReferrerPolicy);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (hasReferrerPolicy) {
+ mReferrerPolicy = static_cast<ReferrerPolicy>(referrerPolicy);
+ mReferrerPolicySet = true;
+ }
+
+ // Set up 'block-all-mixed-content' if not already inherited
+ // from the parent context or set by any other CSP.
+ if (!mBlockAllMixedContent) {
+ rv = csp->GetBlockAllMixedContent(&mBlockAllMixedContent);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ 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) {
+ rv = csp->GetUpgradeInsecureRequests(&mUpgradeInsecureRequests);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ if (!mUpgradeInsecurePreloads) {
+ mUpgradeInsecurePreloads = mUpgradeInsecureRequests;
+ }
+ }
+ return;
+ }
+
+ // 2) apply settings from speculative csp
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
+ rv = NodePrincipal()->GetPreloadCsp(getter_AddRefs(preloadCsp));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (preloadCsp) {
+ if (!mBlockAllMixedContentPreloads) {
+ rv = preloadCsp->GetBlockAllMixedContent(&mBlockAllMixedContentPreloads);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ if (!mUpgradeInsecurePreloads) {
+ rv = preloadCsp->GetUpgradeInsecureRequests(&mUpgradeInsecurePreloads);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+}
+
+nsresult
+nsDocument::InitCSP(nsIChannel* aChannel)
+{
+ MOZ_ASSERT(!mScriptGlobalObject,
+ "CSP must be initialized before mScriptGlobalObject is set!");
+ if (!CSPService::sCSPEnabled) {
+ MOZ_LOG(gCspPRLog, LogLevel::Debug,
+ ("CSP is disabled, skipping CSP init for document %p", this));
+ return NS_OK;
+ }
+
+ nsAutoCString tCspHeaderValue, tCspROHeaderValue;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel;
+ nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (httpChannel) {
+ httpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("content-security-policy"),
+ tCspHeaderValue);
+
+ httpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("content-security-policy-report-only"),
+ tCspROHeaderValue);
+ }
+ NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
+ NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
+
+ // Check if this is a document from a WebExtension.
+ nsString addonId;
+ nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
+ principal->GetAddonId(addonId);
+ bool applyAddonCSP = !addonId.IsEmpty();
+
+ // Check if this is a signed content to apply default CSP.
+ bool applySignedContentCSP = false;
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (loadInfo && loadInfo->GetVerifySignedContent()) {
+ applySignedContentCSP = true;
+ }
+
+ // If there's no CSP to apply, go ahead and return early
+ if (!applyAddonCSP &&
+ !applySignedContentCSP &&
+ 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));
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ rv = principal->EnsureCSP(this, getter_AddRefs(csp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ----- if the doc is an addon, apply its CSP.
+ if (applyAddonCSP) {
+ nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
+
+ nsAutoString addonCSP;
+ rv = aps->GetBaseCSP(addonCSP);
+ if (NS_SUCCEEDED(rv)) {
+ csp->AppendPolicy(addonCSP, false, false);
+ }
+
+ rv = aps->GetAddonCSP(addonId, addonCSP);
+ if (NS_SUCCEEDED(rv)) {
+ csp->AppendPolicy(addonCSP, false, false);
+ }
+ }
+
+ // ----- if the doc is a signed content, apply the default CSP.
+ // Note that when the content signing becomes a standard, we might have
+ // to restrict this enforcement to "remote content" only.
+ if (applySignedContentCSP) {
+ nsAdoptingString signedContentCSP =
+ Preferences::GetString("security.signed_content.CSP.default");
+ csp->AppendPolicy(signedContentCSP, false, false);
+ }
+
+ // ----- if there's a full-strength CSP header, apply it.
+ if (!cspHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // ----- if there's a report-only CSP header, apply it.
+ if (!cspROHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(csp, 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 = csp->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 = nsNullPrincipal::CreateWithInheritedAttributes(principal);
+ principal->SetCsp(csp);
+ SetPrincipal(principal);
+ }
+
+ // ----- Enforce frame-ancestor policy on any applied policies
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell) {
+ bool safeAncestry = false;
+
+ // PermitsAncestry sends violation reports when necessary
+ rv = csp->PermitsAncestry(docShell, &safeAncestry);
+
+ if (NS_FAILED(rv) || !safeAncestry) {
+ MOZ_LOG(gCspPRLog, LogLevel::Debug,
+ ("CSP doesn't like frame's ancestry, not loading."));
+ // stop! ERROR page!
+ aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
+ }
+ }
+ ApplySettingsFromCSP(false);
+ return NS_OK;
+}
+
+void
+nsDocument::StopDocumentLoad()
+{
+ if (mParser) {
+ mParserAborted = true;
+ mParser->Terminate();
+ }
+}
+
+void
+nsDocument::SetDocumentURI(nsIURI* aURI)
+{
+ nsCOMPtr<nsIURI> oldBase = GetDocBaseURI();
+ mDocumentURI = NS_TryToMakeImmutable(aURI);
+ nsIURI* newBase = GetDocBaseURI();
+
+ 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) {
+ RefreshLinkHrefs();
+ }
+}
+
+void
+nsDocument::SetChromeXHRDocURI(nsIURI* aURI)
+{
+ mChromeXHRDocURI = aURI;
+}
+
+void
+nsDocument::SetChromeXHRDocBaseURI(nsIURI* aURI)
+{
+ mChromeXHRDocBaseURI = aURI;
+}
+
+NS_IMETHODIMP
+nsDocument::GetLastModified(nsAString& aLastModified)
+{
+ nsIDocument::GetLastModified(aLastModified);
+ return NS_OK;
+}
+
+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
+nsIDocument::GetLastModified(nsAString& aLastModified) const
+{
+ if (!mLastModified.IsEmpty()) {
+ aLastModified.Assign(mLastModified);
+ } else {
+ GetFormattedTimeString(PR_Now(), aLastModified);
+ }
+}
+
+void
+nsDocument::AddToNameTable(Element *aElement, nsIAtom* aName)
+{
+ MOZ_ASSERT(nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
+ "Only put elements that need to be exposed as document['name'] in "
+ "the named table.");
+
+ nsIdentifierMapEntry *entry =
+ mIdentifierMap.PutEntry(nsDependentAtomString(aName));
+
+ // Null for out-of-memory
+ if (entry) {
+ if (!entry->HasNameElement() &&
+ !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ ++mExpandoAndGeneration.generation;
+ }
+ entry->AddNameElement(this, aElement);
+ }
+}
+
+void
+nsDocument::RemoveFromNameTable(Element *aElement, nsIAtom* aName)
+{
+ // Speed up document teardown
+ if (mIdentifierMap.Count() == 0)
+ return;
+
+ nsIdentifierMapEntry *entry =
+ mIdentifierMap.GetEntry(nsDependentAtomString(aName));
+ if (!entry) // Could be false if the element was anonymous, hence never added
+ return;
+
+ entry->RemoveNameElement(aElement);
+ if (!entry->HasNameElement() &&
+ !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ ++mExpandoAndGeneration.generation;
+ }
+}
+
+void
+nsDocument::AddToIdTable(Element *aElement, nsIAtom* aId)
+{
+ nsIdentifierMapEntry *entry =
+ mIdentifierMap.PutEntry(nsDependentAtomString(aId));
+
+ if (entry) { /* True except on OOM */
+ if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
+ !entry->HasNameElement() &&
+ !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ ++mExpandoAndGeneration.generation;
+ }
+ entry->AddIdElement(aElement);
+ }
+}
+
+void
+nsDocument::RemoveFromIdTable(Element *aElement, nsIAtom* aId)
+{
+ NS_ASSERTION(aId, "huhwhatnow?");
+
+ // Speed up document teardown
+ if (mIdentifierMap.Count() == 0) {
+ return;
+ }
+
+ nsIdentifierMapEntry *entry =
+ mIdentifierMap.GetEntry(nsDependentAtomString(aId));
+ if (!entry) // Can be null for XML elements with changing ids.
+ return;
+
+ entry->RemoveIdElement(aElement);
+ if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
+ !entry->HasNameElement() &&
+ !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+ ++mExpandoAndGeneration.generation;
+ }
+ if (entry->IsEmpty()) {
+ mIdentifierMap.RemoveEntry(entry);
+ }
+}
+
+nsIPrincipal*
+nsDocument::GetPrincipal()
+{
+ return NodePrincipal();
+}
+
+extern bool sDisablePrefetchHTTPSPref;
+
+void
+nsDocument::SetPrincipal(nsIPrincipal *aNewPrincipal)
+{
+ if (aNewPrincipal && mAllowDNSPrefetch && sDisablePrefetchHTTPSPref) {
+ nsCOMPtr<nsIURI> uri;
+ aNewPrincipal->GetURI(getter_AddRefs(uri));
+ bool isHTTPS;
+ if (!uri || NS_FAILED(uri->SchemeIs("https", &isHTTPS)) ||
+ isHTTPS) {
+ mAllowDNSPrefetch = false;
+ }
+ }
+ mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal);
+
+#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
+}
+
+mozilla::dom::DocGroup*
+nsIDocument::GetDocGroup()
+{
+#ifdef DEBUG
+ // Sanity check that we have an up-to-date and accurate docgroup
+ if (mDocGroup) {
+ nsAutoCString docGroupKey;
+ mozilla::dom::DocGroup::GetKey(NodePrincipal(), docGroupKey);
+ MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
+ // XXX: Check that the TabGroup is correct as well!
+ }
+#endif
+
+ return mDocGroup;
+}
+
+NS_IMETHODIMP
+nsDocument::GetApplicationCache(nsIApplicationCache **aApplicationCache)
+{
+ NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::SetApplicationCache(nsIApplicationCache *aApplicationCache)
+{
+ mApplicationCache = aApplicationCache;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetContentType(nsAString& aContentType)
+{
+ CopyUTF8toUTF16(GetContentTypeInternal(), aContentType);
+
+ return NS_OK;
+}
+
+void
+nsDocument::SetContentType(const nsAString& aContentType)
+{
+ SetContentTypeInternal(NS_ConvertUTF16toUTF8(aContentType));
+}
+
+nsresult
+nsDocument::GetAllowPlugins(bool * aAllowPlugins)
+{
+ // First, we ask our docshell if it allows plugins.
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+
+ if (docShell) {
+ docShell->GetAllowPlugins(aAllowPlugins);
+
+ // If the docshell allows plugins, we check whether
+ // we are sandboxed and plugins should not be allowed.
+ if (*aAllowPlugins)
+ *aAllowPlugins = !(mSandboxFlags & SANDBOXED_PLUGINS);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsDocument::IsElementAnimateEnabled(JSContext* /*unused*/, JSObject* /*unused*/)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsCallerChrome() ||
+ Preferences::GetBool("dom.animations-api.core.enabled") ||
+ Preferences::GetBool("dom.animations-api.element-animate.enabled");
+}
+
+bool
+nsDocument::IsWebAnimationsEnabled(JSContext* /*unused*/, JSObject* /*unused*/)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return nsContentUtils::IsCallerChrome() ||
+ Preferences::GetBool("dom.animations-api.core.enabled");
+}
+
+DocumentTimeline*
+nsDocument::Timeline()
+{
+ if (!mDocumentTimeline) {
+ mDocumentTimeline = new DocumentTimeline(this, TimeDuration(0));
+ }
+
+ return mDocumentTimeline;
+}
+
+void
+nsDocument::GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations)
+{
+ // Hold a strong ref for the root element since Element::GetAnimations() calls
+ // FlushPendingNotifications() which may destroy the element.
+ RefPtr<Element> root = GetRootElement();
+ if (!root) {
+ return;
+ }
+ AnimationFilter filter;
+ filter.mSubtree = true;
+ root->GetAnimations(filter, aAnimations);
+}
+
+SVGSVGElement*
+nsIDocument::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. */
+NS_IMETHODIMP
+nsDocument::HasFocus(bool* aResult)
+{
+ ErrorResult rv;
+ *aResult = nsIDocument::HasFocus(rv);
+ return rv.StealNSResult();
+}
+
+bool
+nsIDocument::HasFocus(ErrorResult& rv) const
+{
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ rv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return false;
+ }
+
+ // Is there a focused DOMWindow?
+ nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
+ fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
+ if (!focusedWindow) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(focusedWindow);
+
+ // Are we an ancestor of the focused DOMWindow?
+ for (nsIDocument* currentDoc = piWindow->GetDoc(); currentDoc;
+ currentDoc = currentDoc->GetParentDocument()) {
+ if (currentDoc == this) {
+ // Yes, we are an ancestor
+ return true;
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsDocument::GetReferrer(nsAString& aReferrer)
+{
+ nsIDocument::GetReferrer(aReferrer);
+ return NS_OK;
+}
+
+void
+nsIDocument::GetReferrer(nsAString& aReferrer) const
+{
+ if (mIsSrcdocDocument && mParentDocument)
+ mParentDocument->GetReferrer(aReferrer);
+ else
+ CopyUTF8toUTF16(mReferrer, aReferrer);
+}
+
+nsresult
+nsIDocument::GetSrcdocData(nsAString &aSrcdocData)
+{
+ if (mIsSrcdocDocument) {
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(mChannel);
+ if (inStrmChan) {
+ return inStrmChan->GetSrcdocData(aSrcdocData);
+ }
+ }
+ aSrcdocData = NullString();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetActiveElement(nsIDOMElement **aElement)
+{
+ nsCOMPtr<nsIDOMElement> el(do_QueryInterface(nsIDocument::GetActiveElement()));
+ el.forget(aElement);
+ return NS_OK;
+}
+
+Element*
+nsIDocument::GetActiveElement()
+{
+ // Get the focused element.
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow()) {
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent =
+ nsFocusManager::GetFocusedDescendant(window, false,
+ getter_AddRefs(focusedWindow));
+ // be safe and make sure the element is from this document
+ if (focusedContent && focusedContent->OwnerDoc() == this) {
+ if (focusedContent->ChromeOnlyAccess()) {
+ focusedContent = focusedContent->FindFirstNonChromeOnlyAccessContent();
+ }
+ if (focusedContent) {
+ return focusedContent->AsElement();
+ }
+ }
+ }
+
+ // No focused element anywhere in this document. Try to get the BODY.
+ RefPtr<nsHTMLDocument> htmlDoc = AsHTMLDocument();
+ if (htmlDoc) {
+ // Because of IE compatibility, return null when html document doesn't have
+ // a body.
+ return htmlDoc->GetBody();
+ }
+
+ // If we couldn't get a BODY, return the root element.
+ return GetDocumentElement();
+}
+
+NS_IMETHODIMP
+nsDocument::GetCurrentScript(nsIDOMElement **aElement)
+{
+ nsCOMPtr<nsIDOMElement> el(do_QueryInterface(nsIDocument::GetCurrentScript()));
+ el.forget(aElement);
+ return NS_OK;
+}
+
+Element*
+nsIDocument::GetCurrentScript()
+{
+ nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
+ return el;
+}
+
+NS_IMETHODIMP
+nsDocument::ElementFromPoint(float aX, float aY, nsIDOMElement** aReturn)
+{
+ Element* el = nsIDocument::ElementFromPoint(aX, aY);
+ nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(el);
+ retval.forget(aReturn);
+ return NS_OK;
+}
+
+Element*
+nsIDocument::ElementFromPoint(float aX, float aY)
+{
+ return ElementFromPointHelper(aX, aY, false, true);
+}
+
+void
+nsIDocument::ElementsFromPoint(float aX, float aY,
+ nsTArray<RefPtr<Element>>& aElements)
+{
+ ElementsFromPointHelper(aX, aY, nsIDocument::FLUSH_LAYOUT, aElements);
+}
+
+Element*
+nsDocument::ElementFromPointHelper(float aX, float aY,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout)
+{
+ AutoTArray<RefPtr<Element>, 1> elementArray;
+ ElementsFromPointHelper(aX, aY,
+ ((aIgnoreRootScrollFrame ? nsIDocument::IGNORE_ROOT_SCROLL_FRAME : 0) |
+ (aFlushLayout ? nsIDocument::FLUSH_LAYOUT : 0) |
+ nsIDocument::IS_ELEMENT_FROM_POINT),
+ elementArray);
+ if (elementArray.IsEmpty()) {
+ return nullptr;
+ }
+ return elementArray[0];
+}
+
+void
+nsDocument::ElementsFromPointHelper(float aX, float aY,
+ uint32_t aFlags,
+ nsTArray<RefPtr<mozilla::dom::Element>>& aElements)
+{
+ // As per the the spec, we return null if either coord is negative
+ if (!(aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) && (aX < 0 || aY < 0)) {
+ return;
+ }
+
+ nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
+ nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
+ nsPoint pt(x, y);
+
+ // Make sure the layout information we get is up-to-date, and
+ // ensure we get a root frame (for everything but XUL)
+ if (aFlags & nsIDocument::FLUSH_LAYOUT) {
+ FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsIPresShell *ps = GetShell();
+ if (!ps) {
+ return;
+ }
+ nsIFrame *rootFrame = ps->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
+ }
+
+ nsTArray<nsIFrame*> outFrames;
+ // Emulate what GetFrameAtPoint does, since we want all the frames under our
+ // point.
+ nsLayoutUtils::GetFramesForArea(rootFrame, nsRect(pt, nsSize(1, 1)), outFrames,
+ nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC |
+ ((aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0));
+
+ // Dunno when this would ever happen, as we should at least have a root frame under us?
+ if (outFrames.IsEmpty()) {
+ return;
+ }
+
+ // Used to filter out repeated elements in sequence.
+ nsIContent* lastAdded = nullptr;
+
+ for (uint32_t i = 0; i < outFrames.Length(); i++) {
+ nsIContent* node = GetContentInThisDocument(outFrames[i]);
+
+ if (!node || !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.
+ if (!(aFlags & nsIDocument::IS_ELEMENT_FROM_POINT)) {
+ continue;
+ }
+ node = node->GetParent();
+ }
+ if (node && node != lastAdded) {
+ aElements.AppendElement(node->AsElement());
+ lastAdded = node;
+ // If this helper is called via ElementFromPoint, just return the first
+ // element we find.
+ if (aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) {
+ return;
+ }
+ }
+ }
+}
+
+nsresult
+nsDocument::NodesFromRectHelper(float aX, float aY,
+ float aTopSize, float aRightSize,
+ float aBottomSize, float aLeftSize,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout,
+ nsIDOMNodeList** aReturn)
+{
+ NS_ENSURE_ARG_POINTER(aReturn);
+
+ nsSimpleContentList* elements = new nsSimpleContentList(this);
+ NS_ADDREF(elements);
+ *aReturn = elements;
+
+ // Following the same behavior of elementFromPoint,
+ // we don't return anything if either coord is negative
+ if (!aIgnoreRootScrollFrame && (aX < 0 || aY < 0))
+ return NS_OK;
+
+ 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);
+
+ // Make sure the layout information we get is up-to-date, and
+ // ensure we get a root frame (for everything but XUL)
+ if (aFlushLayout) {
+ FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsIPresShell *ps = GetShell();
+ NS_ENSURE_STATE(ps);
+ nsIFrame *rootFrame = ps->GetRootFrame();
+
+ // XUL docs, unlike HTML, have no frame tree until everything's done loading
+ if (!rootFrame)
+ return NS_OK; // return nothing to premature XUL callers as a reminder to wait
+
+ AutoTArray<nsIFrame*,8> outFrames;
+ nsLayoutUtils::GetFramesForArea(rootFrame, rect, outFrames,
+ nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC |
+ (aIgnoreRootScrollFrame ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0));
+
+ // Used to filter out repeated elements in sequence.
+ nsIContent* lastAdded = nullptr;
+
+ for (uint32_t i = 0; i < outFrames.Length(); i++) {
+ nsIContent* node = GetContentInThisDocument(outFrames[i]);
+
+ if (node && !node->IsElement() && !node->IsNodeOfType(nsINode::eTEXT)) {
+ // We have a node that isn't an element or a text node,
+ // use its parent content instead.
+ node = node->GetParent();
+ }
+ if (node && node != lastAdded) {
+ elements->AppendElement(node);
+ lastAdded = node;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetElementsByClassName(const nsAString& aClasses,
+ nsIDOMNodeList** aReturn)
+{
+ *aReturn = nsIDocument::GetElementsByClassName(aClasses).take();
+ return NS_OK;
+}
+
+already_AddRefed<nsContentList>
+nsIDocument::GetElementsByClassName(const nsAString& aClasses)
+{
+ return nsContentUtils::GetElementsByClassName(this, aClasses);
+}
+
+NS_IMETHODIMP
+nsDocument::ReleaseCapture()
+{
+ nsIDocument::ReleaseCapture();
+ return NS_OK;
+}
+
+void
+nsIDocument::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 = nsIPresShell::GetCapturingContent();
+ if (node && nsContentUtils::CanCallerAccess(node)) {
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+ }
+}
+
+already_AddRefed<nsIURI>
+nsIDocument::GetBaseURI(bool aTryUseXHRDocBaseURI) const
+{
+ nsCOMPtr<nsIURI> uri;
+ if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
+ uri = mChromeXHRDocBaseURI;
+ } else {
+ uri = GetDocBaseURI();
+ }
+
+ return uri.forget();
+}
+
+void
+nsDocument::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;
+ }
+ }
+
+ if (aURI) {
+ mDocumentBaseURI = NS_TryToMakeImmutable(aURI);
+ } else {
+ mDocumentBaseURI = nullptr;
+ }
+ RefreshLinkHrefs();
+}
+
+void
+nsDocument::GetBaseTarget(nsAString &aBaseTarget)
+{
+ aBaseTarget = mBaseTarget;
+}
+
+void
+nsDocument::SetDocumentCharacterSet(const nsACString& aCharSetID)
+{
+ // XXX it would be a good idea to assert the sanity of the argument,
+ // but before we figure out what to do about non-Encoding Standard
+ // encodings in the charset menu and in mailnews, assertions are futile.
+ if (!mCharacterSet.Equals(aCharSetID)) {
+ if (mMasterDocument && !aCharSetID.EqualsLiteral("UTF-8")) {
+ // Imports are always UTF-8
+ return;
+ }
+ mCharacterSet = aCharSetID;
+
+ int32_t n = mCharSetObservers.Length();
+
+ for (int32_t i = 0; i < n; i++) {
+ nsIObserver* observer = mCharSetObservers.ElementAt(i);
+
+ observer->Observe(static_cast<nsIDocument *>(this), "charset",
+ NS_ConvertASCIItoUTF16(aCharSetID).get());
+ }
+ }
+}
+
+nsresult
+nsDocument::AddCharSetObserver(nsIObserver* aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+
+ NS_ENSURE_TRUE(mCharSetObservers.AppendElement(aObserver), NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+void
+nsDocument::RemoveCharSetObserver(nsIObserver* aObserver)
+{
+ mCharSetObservers.RemoveElement(aObserver);
+}
+
+void
+nsIDocument::GetSandboxFlagsAsString(nsAString& aFlags)
+{
+ nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags);
+}
+
+void
+nsDocument::GetHeaderData(nsIAtom* aHeaderField, nsAString& aData) const
+{
+ aData.Truncate();
+ const nsDocHeaderData* data = mHeaderData;
+ while (data) {
+ if (data->mField == aHeaderField) {
+ aData = data->mData;
+
+ break;
+ }
+ data = data->mNext;
+ }
+}
+
+void
+nsDocument::SetHeaderData(nsIAtom* aHeaderField, const nsAString& aData)
+{
+ if (!aHeaderField) {
+ NS_ERROR("null headerField");
+ return;
+ }
+
+ if (!mHeaderData) {
+ if (!aData.IsEmpty()) { // don't bother storing empty string
+ mHeaderData = new nsDocHeaderData(aHeaderField, aData);
+ }
+ }
+ else {
+ nsDocHeaderData* data = mHeaderData;
+ nsDocHeaderData** 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
+ *lastPtr = data->mNext;
+ data->mNext = nullptr;
+ delete data;
+ }
+ found = true;
+
+ break;
+ }
+ lastPtr = &(data->mNext);
+ data = *lastPtr;
+ } while (data);
+
+ if (!aData.IsEmpty() && !found) {
+ // didn't find, append
+ *lastPtr = new nsDocHeaderData(aHeaderField, aData);
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::headerContentLanguage) {
+ CopyUTF16toUTF8(aData, mContentLanguage);
+ }
+
+ if (aHeaderField == nsGkAtoms::headerDefaultStyle) {
+ // 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(aData, true);
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::refresh) {
+ // We get into this code before we have a script global yet, so get to
+ // our container via mDocumentContainer.
+ nsCOMPtr<nsIRefreshURI> refresher(mDocumentContainer);
+ if (refresher) {
+ // 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.
+ refresher->SetupRefreshURIFromHeader(mDocumentURI, NodePrincipal(),
+ NS_ConvertUTF16toUTF8(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::viewport ||
+ aHeaderField == nsGkAtoms::handheldFriendly ||
+ aHeaderField == nsGkAtoms::viewport_minimum_scale ||
+ aHeaderField == nsGkAtoms::viewport_maximum_scale ||
+ aHeaderField == nsGkAtoms::viewport_initial_scale ||
+ aHeaderField == nsGkAtoms::viewport_height ||
+ aHeaderField == nsGkAtoms::viewport_width ||
+ aHeaderField == nsGkAtoms::viewport_user_scalable) {
+ mViewportType = Unknown;
+ }
+
+ // Referrer policy spec says to ignore any empty referrer policies.
+ if (aHeaderField == nsGkAtoms::referrer && !aData.IsEmpty()) {
+ ReferrerPolicy policy = mozilla::net::ReferrerPolicyFromString(aData);
+ // If policy is not the empty string, then set element's node document's
+ // referrer policy to policy
+ if (policy != mozilla::net::RP_Unset) {
+ // Referrer policy spec (section 6.1) says that we always use the newest
+ // referrer policy we find
+ mReferrerPolicy = policy;
+ mReferrerPolicySet = true;
+ }
+ }
+
+ if (aHeaderField == nsGkAtoms::headerReferrerPolicy && !aData.IsEmpty()) {
+ ReferrerPolicy policy = nsContentUtils::GetReferrerPolicyFromHeader(aData);
+ if (policy != mozilla::net::RP_Unset) {
+ mReferrerPolicy = policy;
+ mReferrerPolicySet = true;
+ }
+ }
+
+}
+void
+nsDocument::TryChannelCharset(nsIChannel *aChannel,
+ int32_t& aCharsetSource,
+ nsACString& aCharset,
+ nsHtml5TreeOpExecutor* aExecutor)
+{
+ if (aChannel) {
+ nsAutoCString charsetVal;
+ nsresult rv = aChannel->GetContentCharset(charsetVal);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString preferred;
+ if(EncodingUtils::FindEncodingForLabel(charsetVal, preferred)) {
+ aCharset = preferred;
+ aCharsetSource = kCharsetFromChannel;
+ return;
+ } else if (aExecutor && !charsetVal.IsEmpty()) {
+ aExecutor->ComplainAboutBogusProtocolCharset(this);
+ }
+ }
+ }
+}
+
+already_AddRefed<nsIPresShell>
+nsDocument::CreateShell(nsPresContext* aContext, nsViewManager* aViewManager,
+ StyleSetHandle aStyleSet)
+{
+ // Don't add anything here. Add it to |doCreateShell| instead.
+ // This exists so that subclasses can pass other values for the 4th
+ // parameter some of the time.
+ return doCreateShell(aContext, aViewManager, aStyleSet);
+}
+
+already_AddRefed<nsIPresShell>
+nsDocument::doCreateShell(nsPresContext* aContext,
+ nsViewManager* aViewManager, StyleSetHandle aStyleSet)
+{
+ NS_ASSERTION(!mPresShell, "We have a presshell already!");
+
+ NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
+
+ FillStyleSet(aStyleSet);
+
+ RefPtr<PresShell> shell = new PresShell;
+ shell->Init(this, aContext, aViewManager, aStyleSet);
+
+ // Note: we don't hold a ref to the shell (it holds a ref to us)
+ mPresShell = shell;
+
+ // Make sure to never paint if we belong to an invisible DocShell.
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ if (docShell && docShell->IsInvisible())
+ shell->SetNeverPainting(true);
+
+ mExternalResourceMap.ShowViewers();
+
+ UpdateFrameRequestCallbackSchedulingState();
+
+ // Now that we have a shell, we might have @font-face rules.
+ RebuildUserFontSet();
+
+ return shell.forget();
+}
+
+void
+nsIDocument::UpdateFrameRequestCallbackSchedulingState(nsIPresShell* aOldShell)
+{
+ // If the condition for shouldBeScheduled changes to depend on some other
+ // variable, add UpdateFrameRequestCallbackSchedulingState() calls to the
+ // places where that variable can change.
+ bool shouldBeScheduled =
+ mPresShell && IsEventHandlingEnabled() && !AnimationsPaused() &&
+ !mFrameRequestCallbacks.IsEmpty();
+ if (shouldBeScheduled == mFrameRequestCallbacksScheduled) {
+ // nothing to do
+ return;
+ }
+
+ nsIPresShell* presShell = aOldShell ? aOldShell : mPresShell;
+ MOZ_RELEASE_ASSERT(presShell);
+
+ nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver();
+ if (shouldBeScheduled) {
+ rd->ScheduleFrameRequestCallbacks(this);
+ } else {
+ rd->RevokeFrameRequestCallbacks(this);
+ }
+
+ mFrameRequestCallbacksScheduled = shouldBeScheduled;
+}
+
+void
+nsIDocument::TakeFrameRequestCallbacks(FrameRequestCallbackList& aCallbacks)
+{
+ aCallbacks.AppendElements(mFrameRequestCallbacks);
+ mFrameRequestCallbacks.Clear();
+ // 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
+nsIDocument::ShouldThrottleFrameRequests()
+{
+ 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) {
+ return false; // Can't do anything smarter.
+ }
+
+ nsIFrame* frame = mPresShell->GetRootFrame();
+ if (!frame) {
+ return false; // Can't do anything smarter.
+ }
+
+ nsIFrame* displayRootFrame = nsLayoutUtils::GetDisplayRootFrame(frame);
+ if (!displayRootFrame) {
+ return false; // Can't do anything smarter.
+ }
+
+ if (!displayRootFrame->DidPaintPresShell(mPresShell)) {
+ // We didn't get painted during the last paint, so we're not visible.
+ // Throttle. Note that because we have to paint this document 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 since it would happen outside the viewport on APZ
+ // platforms and is unlikely to be human-perceivable on non-APZ platforms.
+ return true;
+ }
+
+ // We got painted during the last paint, so run at full speed.
+ return false;
+}
+
+void
+nsDocument::DeleteShell()
+{
+ mExternalResourceMap.HideViewers();
+ if (nsPresContext* presContext = mPresShell->GetPresContext()) {
+ presContext->RefreshDriver()->CancelPendingEvents(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.
+ RebuildUserFontSet();
+
+ nsIPresShell* oldShell = mPresShell;
+ mPresShell = nullptr;
+ UpdateFrameRequestCallbackSchedulingState(oldShell);
+ mStyleSetFilled = false;
+}
+
+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
+nsDocument::SetSubDocumentFor(Element* aElement, nsIDocument* 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;
+}
+
+nsIDocument*
+nsDocument::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*
+nsDocument::FindContentForSubDocument(nsIDocument *aDocument) const
+{
+ NS_ENSURE_TRUE(aDocument, nullptr);
+
+ if (!mSubDocuments) {
+ return nullptr;
+ }
+
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ if (entry->mSubDocument == aDocument) {
+ return entry->mKey;
+ }
+ }
+ return nullptr;
+}
+
+bool
+nsDocument::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~eDOCUMENT);
+}
+
+Element*
+nsIDocument::GetRootElement() const
+{
+ return (mCachedRootElement && mCachedRootElement->GetParentNode() == this) ?
+ mCachedRootElement : GetRootElementInternal();
+}
+
+Element*
+nsDocument::GetRootElementInternal() const
+{
+ // Loop backwards because any non-elements, such as doctypes and PIs
+ // are likely to appear before the root element.
+ uint32_t i;
+ for (i = mChildren.ChildCount(); i > 0; --i) {
+ nsIContent* child = mChildren.ChildAt(i - 1);
+ if (child->IsElement()) {
+ const_cast<nsDocument*>(this)->mCachedRootElement = child->AsElement();
+ return child->AsElement();
+ }
+ }
+
+ const_cast<nsDocument*>(this)->mCachedRootElement = nullptr;
+ return nullptr;
+}
+
+nsIContent *
+nsDocument::GetChildAt(uint32_t aIndex) const
+{
+ return mChildren.GetSafeChildAt(aIndex);
+}
+
+int32_t
+nsDocument::IndexOf(const nsINode* aPossibleChild) const
+{
+ return mChildren.IndexOfChild(aPossibleChild);
+}
+
+uint32_t
+nsDocument::GetChildCount() const
+{
+ return mChildren.ChildCount();
+}
+
+nsIContent * const *
+nsDocument::GetChildArray(uint32_t* aChildCount) const
+{
+ return mChildren.GetChildArray(aChildCount);
+}
+
+
+nsresult
+nsDocument::InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify)
+{
+ if (aKid->IsElement() && GetRootElement()) {
+ NS_WARNING("Inserting root element when we already have one");
+ return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
+ }
+
+ return doInsertChildAt(aKid, aIndex, aNotify, mChildren);
+}
+
+void
+nsDocument::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+ nsCOMPtr<nsIContent> oldKid = GetChildAt(aIndex);
+ if (!oldKid) {
+ return;
+ }
+
+ if (oldKid->IsElement()) {
+ // Destroy the link map up front before we mess with the child list.
+ DestroyElementMaps();
+ }
+
+ doRemoveChildAt(aIndex, aNotify, oldKid, mChildren);
+ mCachedRootElement = nullptr;
+}
+
+void
+nsDocument::EnsureOnDemandBuiltInUASheet(StyleSheet* aSheet)
+{
+ if (mOnDemandBuiltInUASheets.Contains(aSheet)) {
+ return;
+ }
+ BeginUpdate(UPDATE_STYLE);
+ AddOnDemandBuiltInUASheet(aSheet);
+ EndUpdate(UPDATE_STYLE);
+}
+
+void
+nsDocument::AddOnDemandBuiltInUASheet(StyleSheet* aSheet)
+{
+ MOZ_ASSERT(!mOnDemandBuiltInUASheets.Contains(aSheet));
+
+ // Prepend here so that we store the sheets in mOnDemandBuiltInUASheets in
+ // the same order that they should end up in the style set.
+ mOnDemandBuiltInUASheets.InsertElementAt(0, aSheet);
+
+ if (aSheet->IsApplicable()) {
+ // This is like |AddStyleSheetToStyleSets|, but for an agent sheet.
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ // Note that prepending here is necessary to make sure that html.css etc.
+ // do not override Firefox OS/Mobile's content.css sheet. Maybe we should
+ // have an insertion point to match the order of
+ // nsDocumentViewer::CreateStyleSet though?
+ shell->StyleSet()->PrependStyleSheet(SheetType::Agent, aSheet);
+ }
+ }
+
+ NotifyStyleSheetAdded(aSheet, false);
+}
+
+int32_t
+nsDocument::GetNumberOfStyleSheets() const
+{
+ return mStyleSheets.Length();
+}
+
+StyleSheet*
+nsDocument::GetStyleSheetAt(int32_t aIndex) const
+{
+ return mStyleSheets.SafeElementAt(aIndex, nullptr);
+}
+
+int32_t
+nsDocument::GetIndexOfStyleSheet(const StyleSheet* aSheet) const
+{
+ return mStyleSheets.IndexOf(aSheet);
+}
+
+void
+nsDocument::AddStyleSheetToStyleSets(StyleSheet* aSheet)
+{
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ shell->StyleSet()->AddDocStyleSheet(aSheet, this);
+ }
+}
+
+#define DO_STYLESHEET_NOTIFICATION(className, type, memberName, argName) \
+ do { \
+ className##Init init; \
+ init.mBubbles = true; \
+ init.mCancelable = true; \
+ /* XXXheycam ServoStyleSheet doesn't implement DOM interfaces yet */ \
+ if (aSheet->IsServo()) { \
+ NS_ERROR("stylo: can't dispatch events for ServoStyleSheets yet"); \
+ } \
+ init.mStylesheet = aSheet->IsGecko() ? aSheet->AsGecko() : nullptr; \
+ init.memberName = argName; \
+ \
+ RefPtr<className> event = \
+ className::Constructor(this, NS_LITERAL_STRING(type), init); \
+ event->SetTrusted(true); \
+ event->SetTarget(this); \
+ RefPtr<AsyncEventDispatcher> asyncDispatcher = \
+ new AsyncEventDispatcher(this, event); \
+ asyncDispatcher->mOnlyChromeDispatch = true; \
+ asyncDispatcher->PostDOMEvent(); \
+ } while (0);
+
+void
+nsDocument::NotifyStyleSheetAdded(StyleSheet* aSheet, bool aDocumentSheet)
+{
+ NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetAdded, (aSheet, aDocumentSheet));
+
+ if (StyleSheetChangeEventsEnabled()) {
+ DO_STYLESHEET_NOTIFICATION(StyleSheetChangeEvent,
+ "StyleSheetAdded",
+ mDocumentSheet,
+ aDocumentSheet);
+ }
+}
+
+void
+nsDocument::NotifyStyleSheetRemoved(StyleSheet* aSheet, bool aDocumentSheet)
+{
+ NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetRemoved, (aSheet, aDocumentSheet));
+
+ if (StyleSheetChangeEventsEnabled()) {
+ DO_STYLESHEET_NOTIFICATION(StyleSheetChangeEvent,
+ "StyleSheetRemoved",
+ mDocumentSheet,
+ aDocumentSheet);
+ }
+}
+
+void
+nsDocument::AddStyleSheet(StyleSheet* aSheet)
+{
+ NS_PRECONDITION(aSheet, "null arg");
+ mStyleSheets.AppendElement(aSheet);
+ aSheet->SetOwningDocument(this);
+
+ if (aSheet->IsApplicable()) {
+ AddStyleSheetToStyleSets(aSheet);
+ }
+
+ NotifyStyleSheetAdded(aSheet, true);
+}
+
+void
+nsDocument::RemoveStyleSheetFromStyleSets(StyleSheet* aSheet)
+{
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ shell->StyleSet()->RemoveDocStyleSheet(aSheet);
+ }
+}
+
+void
+nsDocument::RemoveStyleSheet(StyleSheet* aSheet)
+{
+ NS_PRECONDITION(aSheet, "null arg");
+ RefPtr<StyleSheet> sheet = aSheet; // hold ref so it won't die too soon
+
+ if (!mStyleSheets.RemoveElement(aSheet)) {
+ NS_ASSERTION(mInUnlinkOrDeletion, "stylesheet not found");
+ return;
+ }
+
+ if (!mIsGoingAway) {
+ if (aSheet->IsApplicable()) {
+ RemoveStyleSheetFromStyleSets(aSheet);
+ }
+
+ NotifyStyleSheetRemoved(aSheet, true);
+ }
+
+ aSheet->SetOwningDocument(nullptr);
+}
+
+void
+nsDocument::UpdateStyleSheets(nsTArray<RefPtr<StyleSheet>>& aOldSheets,
+ nsTArray<RefPtr<StyleSheet>>& aNewSheets)
+{
+ BeginUpdate(UPDATE_STYLE);
+
+ // XXX Need to set the sheet on the ownernode, if any
+ NS_PRECONDITION(aOldSheets.Length() == aNewSheets.Length(),
+ "The lists must be the same length!");
+ int32_t count = aOldSheets.Length();
+
+ RefPtr<StyleSheet> oldSheet;
+ int32_t i;
+ for (i = 0; i < count; ++i) {
+ oldSheet = aOldSheets[i];
+
+ // First remove the old sheet.
+ NS_ASSERTION(oldSheet, "None of the old sheets should be null");
+ int32_t oldIndex = mStyleSheets.IndexOf(oldSheet);
+ RemoveStyleSheet(oldSheet); // This does the right notifications
+
+ // Now put the new one in its place. If it's null, just ignore it.
+ StyleSheet* newSheet = aNewSheets[i];
+ if (newSheet) {
+ mStyleSheets.InsertElementAt(oldIndex, newSheet);
+ newSheet->SetOwningDocument(this);
+ if (newSheet->IsApplicable()) {
+ AddStyleSheetToStyleSets(newSheet);
+ }
+
+ NotifyStyleSheetAdded(newSheet, true);
+ }
+ }
+
+ EndUpdate(UPDATE_STYLE);
+}
+
+void
+nsDocument::InsertStyleSheetAt(StyleSheet* aSheet, int32_t aIndex)
+{
+ NS_PRECONDITION(aSheet, "null ptr");
+
+ mStyleSheets.InsertElementAt(aIndex, aSheet);
+
+ aSheet->SetOwningDocument(this);
+
+ if (aSheet->IsApplicable()) {
+ AddStyleSheetToStyleSets(aSheet);
+ }
+
+ NotifyStyleSheetAdded(aSheet, true);
+}
+
+
+void
+nsDocument::SetStyleSheetApplicableState(StyleSheet* aSheet,
+ bool aApplicable)
+{
+ NS_PRECONDITION(aSheet, "null arg");
+
+ // If we're actually in the document style sheet list
+ if (mStyleSheets.IndexOf(aSheet) != mStyleSheets.NoIndex) {
+ if (aApplicable) {
+ AddStyleSheetToStyleSets(aSheet);
+ } else {
+ RemoveStyleSheetFromStyleSets(aSheet);
+ }
+ }
+
+ // We have to always notify, since this will be called for sheets
+ // that are children of sheets in our style set, as well as some
+ // sheets for HTMLEditor.
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetApplicableStateChanged, (aSheet));
+
+ if (StyleSheetChangeEventsEnabled()) {
+ DO_STYLESHEET_NOTIFICATION(StyleSheetApplicableStateChangeEvent,
+ "StyleSheetApplicableStateChanged",
+ mApplicable,
+ aApplicable);
+ }
+
+ if (!mSSApplicableStateNotificationPending) {
+ nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(this,
+ &nsDocument::NotifyStyleSheetApplicableStateChanged);
+ mSSApplicableStateNotificationPending =
+ NS_SUCCEEDED(NS_DispatchToCurrentThread(notification));
+ }
+}
+
+void
+nsDocument::NotifyStyleSheetApplicableStateChanged()
+{
+ mSSApplicableStateNotificationPending = false;
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(static_cast<nsIDocument*>(this),
+ "style-sheet-applicable-state-changed",
+ nullptr);
+ }
+}
+
+static SheetType
+ConvertAdditionalSheetType(nsIDocument::additionalSheetType aType)
+{
+ switch(aType) {
+ case nsIDocument::eAgentSheet:
+ return SheetType::Agent;
+ case nsIDocument::eUserSheet:
+ return SheetType::User;
+ case nsIDocument::eAuthorSheet:
+ return SheetType::Doc;
+ default:
+ MOZ_ASSERT(false, "wrong type");
+ // we must return something although this should never happen
+ return SheetType::Count;
+ }
+}
+
+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
+nsDocument::LoadAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* aSheetURI)
+{
+ NS_PRECONDITION(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(GetStyleBackendType());
+
+ css::SheetParsingMode parsingMode;
+ switch (aType) {
+ case nsIDocument::eAgentSheet:
+ parsingMode = css::eAgentSheetFeatures;
+ break;
+
+ case nsIDocument::eUserSheet:
+ parsingMode = css::eUserSheetFeatures;
+ break;
+
+ case nsIDocument::eAuthorSheet:
+ parsingMode = css::eAuthorSheetFeatures;
+ break;
+
+ default:
+ MOZ_CRASH("impossible value for aType");
+ }
+
+ RefPtr<StyleSheet> sheet;
+ nsresult rv = loader->LoadSheetSync(aSheetURI, parsingMode, true, &sheet);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sheet->SetOwningDocument(this);
+ MOZ_ASSERT(sheet->IsApplicable());
+
+ return AddAdditionalStyleSheet(aType, sheet);
+}
+
+nsresult
+nsDocument::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);
+
+ BeginUpdate(UPDATE_STYLE);
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ SheetType type = ConvertAdditionalSheetType(aType);
+ shell->StyleSet()->AppendStyleSheet(type, aSheet);
+ }
+
+ // Passing false, so documet.styleSheets.length will not be affected by
+ // these additional sheets.
+ NotifyStyleSheetAdded(aSheet, false);
+ EndUpdate(UPDATE_STYLE);
+ return NS_OK;
+}
+
+void
+nsDocument::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 = sheets[i];
+ sheets.RemoveElementAt(i);
+
+ BeginUpdate(UPDATE_STYLE);
+ if (!mIsGoingAway) {
+ MOZ_ASSERT(sheetRef->IsApplicable());
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ SheetType type = ConvertAdditionalSheetType(aType);
+ shell->StyleSet()->RemoveStyleSheet(type, sheetRef);
+ }
+ }
+
+ // Passing false, so documet.styleSheets.length will not be affected by
+ // these additional sheets.
+ NotifyStyleSheetRemoved(sheetRef, false);
+ EndUpdate(UPDATE_STYLE);
+
+ sheetRef->SetOwningDocument(nullptr);
+ }
+}
+
+StyleSheet*
+nsDocument::GetFirstAdditionalAuthorSheet()
+{
+ return mAdditionalSheets[eAuthorSheet].SafeElementAt(0);
+}
+
+nsIGlobalObject*
+nsDocument::GetScopeObject() const
+{
+ nsCOMPtr<nsIGlobalObject> scope(do_QueryReferent(mScopeObject));
+ return scope;
+}
+
+void
+nsDocument::SetScopeObject(nsIGlobalObject* aGlobal)
+{
+ mScopeObject = do_GetWeakReference(aGlobal);
+ if (aGlobal) {
+ mHasHadScriptHandlingObject = true;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ if (window) {
+ // We want to get the tabgroup unconditionally, such that we can make
+ // certain that it is cached in the inner window early enough.
+ mozilla::dom::TabGroup* tabgroup = window->TabGroup();
+ // 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;
+ mozilla::dom::DocGroup::GetKey(NodePrincipal(), docGroupKey);
+ if (!mDocGroup) {
+ mDocGroup = tabgroup->AddDocument(docGroupKey, this);
+ MOZ_ASSERT(mDocGroup);
+ }
+ }
+ }
+}
+
+static void
+CheckIfContainsEMEContent(nsISupports* aSupports, void* aContainsEME)
+{
+ nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aSupports));
+ if (domMediaElem) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(domMediaElem));
+ MOZ_ASSERT(content, "aSupports is not a content");
+ HTMLMediaElement* mediaElem = static_cast<HTMLMediaElement*>(content.get());
+ bool* contains = static_cast<bool*>(aContainsEME);
+ if (mediaElem->GetMediaKeys()) {
+ *contains = true;
+ }
+ }
+}
+
+bool
+nsDocument::ContainsEMEContent()
+{
+ bool containsEME = false;
+ EnumerateActivityObservers(CheckIfContainsEMEContent,
+ static_cast<void*>(&containsEME));
+ return containsEME;
+}
+
+static void
+CheckIfContainsMSEContent(nsISupports* aSupports, void* aContainsMSE)
+{
+ nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aSupports));
+ if (domMediaElem) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(domMediaElem));
+ MOZ_ASSERT(content, "aSupports is not a content");
+ HTMLMediaElement* mediaElem = static_cast<HTMLMediaElement*>(content.get());
+ bool* contains = static_cast<bool*>(aContainsMSE);
+ RefPtr<MediaSource> ms = mediaElem->GetMozMediaSourceObject();
+ if (ms) {
+ *contains = true;
+ }
+ }
+}
+
+bool
+nsDocument::ContainsMSEContent()
+{
+ bool containsMSE = false;
+ EnumerateActivityObservers(CheckIfContainsMSEContent,
+ static_cast<void*>(&containsMSE));
+ return containsMSE;
+}
+
+static void
+NotifyActivityChanged(nsISupports *aSupports, void *aUnused)
+{
+ nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aSupports));
+ if (domMediaElem) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(domMediaElem));
+ MOZ_ASSERT(content, "aSupports is not a content");
+ HTMLMediaElement* mediaElem = static_cast<HTMLMediaElement*>(content.get());
+ 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();
+ }
+}
+
+void
+nsIDocument::SetContainer(nsDocShell* aContainer)
+{
+ if (aContainer) {
+ mDocumentContainer = aContainer;
+ } else {
+ mDocumentContainer = WeakPtr<nsDocShell>();
+ }
+
+ EnumerateActivityObservers(NotifyActivityChanged, nullptr);
+ if (!aContainer) {
+ return;
+ }
+
+ // Get the Docshell
+ if (aContainer->ItemType() == nsIDocShellTreeItem::typeContent) {
+ // check if same type root
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
+ aContainer->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
+ NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!");
+
+ if (sameTypeRoot == aContainer) {
+ static_cast<nsDocument*>(this)->SetIsTopLevelContentDocument(true);
+ }
+
+ static_cast<nsDocument*>(this)->SetIsContentDocument(true);
+ }
+}
+
+nsISupports*
+nsIDocument::GetContainer() const
+{
+ return static_cast<nsIDocShell*>(mDocumentContainer);
+}
+
+void
+nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
+{
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aScriptGlobalObject));
+
+ NS_ASSERTION(!win || win->IsInnerWindow(),
+ "Script global object must be an inner window!");
+ }
+#endif
+ MOZ_ASSERT(aScriptGlobalObject || !mAnimationController ||
+ mAnimationController->IsPausedByType(
+ nsSMILTimeContainer::PAUSE_PAGEHIDE |
+ nsSMILTimeContainer::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);
+ }
+ }
+
+ using mozilla::dom::workers::ServiceWorkerManager;
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ ErrorResult error;
+ if (swm->IsControlled(this, error)) {
+ imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
+ if (loader) {
+ 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;
+ }
+ swm->MaybeStopControlling(this);
+ }
+
+ // Remove ourself from the list of clients. We only register
+ // content principal documents in this list.
+ if (!nsContentUtils::IsSystemPrincipal(GetPrincipal()) &&
+ !GetPrincipal()->GetIsNullPrincipal()) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, "service-worker-get-client");
+ }
+ }
+
+ } else if (!mScriptGlobalObject && aScriptGlobalObject &&
+ mDocumentContainer && GetChannel() &&
+ !nsContentUtils::IsSystemPrincipal(GetPrincipal()) &&
+ !GetPrincipal()->GetIsNullPrincipal()) {
+ // This document is being activated. Register it in the list of
+ // clients. We only do this for content principal documents
+ // since we can never observe system or null principals.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "service-worker-get-client", /* ownsWeak */ false);
+ }
+ }
+
+ // 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;
+#ifdef DEBUG
+ if (!mWillReparent) {
+ // We really shouldn't have a wrapper here but if we do we need to make sure
+ // it has the correct parent.
+ JSObject *obj = GetWrapperPreserveColor();
+ if (obj) {
+ JSObject *newScope = aScriptGlobalObject->GetGlobalJSObject();
+ NS_ASSERTION(js::GetGlobalForObjectCrossCompartment(obj) == newScope,
+ "Wrong scope, this is really bad!");
+ }
+ }
+#endif
+
+ 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;
+ }
+ }
+ }
+
+ // 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 occured and were stored
+ // in the queue prior to this point.
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ NodePrincipal()->GetCsp(getter_AddRefs(csp));
+ if (csp) {
+ static_cast<nsCSPContext*>(csp.get())->flushConsoleMessages();
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(GetChannel());
+ if (internalChannel) {
+ nsCOMArray<nsISecurityConsoleMessage> messages;
+ internalChannel->TakeAllSecurityMessages(messages);
+ 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.
+ dom::VisibilityState oldState = mVisibilityState;
+ mVisibilityState = GetVisibilityState();
+ // 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 nsDocument::SetScriptGlobalObject() call.
+ // MediaDecoder decides whether starting decoding is decided based on
+ // document's visibility. When the MediaDecoder is created,
+ // nsDocument::SetScriptGlobalObject() is not yet called and document is
+ // hidden state. Therefore the MediaDecoder decides that decoding is
+ // not yet necessary. But soon after nsDocument::SetScriptGlobalObject()
+ // call, the document becomes not hidden. At the time, MediaDecoder needs
+ // to know it and needs to start updating decoding.
+ if (oldState != mVisibilityState) {
+ EnumerateActivityObservers(NotifyActivityChanged, nullptr);
+ }
+
+ // The global in the template contents owner document should be the same.
+ if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
+ mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
+ }
+
+ if (!mMaybeServiceWorkerControlled && mDocumentContainer && mScriptGlobalObject && GetChannel()) {
+ nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+ uint32_t loadType;
+ docShell->GetLoadType(&loadType);
+
+ // If we are shift-reloaded, don't associate with a ServiceWorker.
+ if (IsForceReloadType(loadType)) {
+ NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
+ return;
+ }
+
+ nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
+ if (swm) {
+ // If this document is being resurrected from the bfcache, then we may
+ // already have a document ID. In that case reuse the same ID. Otherwise
+ // get our document ID from the docshell.
+ nsString documentId(GetId());
+ if (documentId.IsEmpty()) {
+ static_cast<nsDocShell*>(docShell.get())->GetInterceptedDocumentId(documentId);
+ }
+
+ swm->MaybeStartControlling(this, documentId);
+ mMaybeServiceWorkerControlled = true;
+ }
+ }
+}
+
+nsIScriptGlobalObject*
+nsDocument::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
+nsDocument::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject)
+{
+ NS_ASSERTION(!mScriptGlobalObject ||
+ mScriptGlobalObject == aScriptObject,
+ "Wrong script object!");
+ if (aScriptObject) {
+ SetScopeObject(aScriptObject);
+ mHasHadDefaultView = false;
+ }
+}
+
+bool
+nsDocument::IsTopLevelContentDocument()
+{
+ return mIsTopLevelContentDocument;
+}
+
+void
+nsDocument::SetIsTopLevelContentDocument(bool aIsTopLevelContentDocument)
+{
+ mIsTopLevelContentDocument = aIsTopLevelContentDocument;
+}
+
+bool
+nsDocument::IsContentDocument() const
+{
+ return mIsContentDocument;
+}
+
+void
+nsDocument::SetIsContentDocument(bool aIsContentDocument)
+{
+ mIsContentDocument = aIsContentDocument;
+}
+
+nsPIDOMWindowOuter*
+nsDocument::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;
+}
+
+nsScriptLoader*
+nsDocument::ScriptLoader()
+{
+ return mScriptLoader;
+}
+
+bool
+nsDocument::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
+nsDocument::AddObserver(nsIDocumentObserver* aObserver)
+{
+ NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
+ "Observer already in the list");
+ mObservers.AppendElement(aObserver);
+ AddMutationObserver(aObserver);
+}
+
+bool
+nsDocument::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
+nsDocument::MaybeEndOutermostXBLUpdate()
+{
+ // Only call BindingManager()->EndOutermostUpdate() when
+ // we're not in an update and it is safe to run scripts.
+ if (mUpdateNestLevel == 0 && mInXBLUpdate) {
+ if (nsContentUtils::IsSafeToRunScript()) {
+ mInXBLUpdate = false;
+ BindingManager()->EndOutermostUpdate();
+ } else if (!mInDestructor) {
+ if (!mMaybeEndOutermostXBLUpdateRunner) {
+ mMaybeEndOutermostXBLUpdateRunner =
+ NewRunnableMethod(this, &nsDocument::MaybeEndOutermostXBLUpdate);
+ }
+ nsContentUtils::AddScriptRunner(mMaybeEndOutermostXBLUpdateRunner);
+ }
+ }
+}
+
+void
+nsDocument::BeginUpdate(nsUpdateType aUpdateType)
+{
+ if (mUpdateNestLevel == 0 && !mInXBLUpdate) {
+ mInXBLUpdate = true;
+ BindingManager()->BeginOutermostUpdate();
+ }
+
+ ++mUpdateNestLevel;
+ nsContentUtils::AddScriptBlocker();
+ NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this, aUpdateType));
+}
+
+void
+nsDocument::EndUpdate(nsUpdateType aUpdateType)
+{
+ NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this, aUpdateType));
+
+ nsContentUtils::RemoveScriptBlocker();
+
+ --mUpdateNestLevel;
+
+ // This set of updates may have created XBL bindings. Let the
+ // binding manager know we're done.
+ MaybeEndOutermostXBLUpdate();
+
+ MaybeInitializeFinalizeFrameLoaders();
+}
+
+void
+nsDocument::BeginLoad()
+{
+ // 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
+nsDocument::ReportEmptyGetElementByIdArg()
+{
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"), this,
+ nsContentUtils::eDOM_PROPERTIES,
+ "EmptyGetElementByIdParam");
+}
+
+Element*
+nsDocument::GetElementById(const nsAString& aElementId)
+{
+ if (!CheckGetElementByIdArg(aElementId)) {
+ return nullptr;
+ }
+
+ nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aElementId);
+ return entry ? entry->GetIdElement() : nullptr;
+}
+
+const nsTArray<Element*>*
+nsDocument::GetAllElementsForId(const nsAString& aElementId) const
+{
+ if (aElementId.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aElementId);
+ return entry ? &entry->GetIdElements() : nullptr;
+}
+
+NS_IMETHODIMP
+nsDocument::GetElementById(const nsAString& aId, nsIDOMElement** aReturn)
+{
+ Element *content = GetElementById(aId);
+ if (content) {
+ return CallQueryInterface(content, aReturn);
+ }
+
+ *aReturn = nullptr;
+
+ return NS_OK;
+}
+
+Element*
+nsDocument::AddIDTargetObserver(nsIAtom* aID, IDTargetObserver aObserver,
+ void* aData, bool aForImage)
+{
+ nsDependentAtomString id(aID);
+
+ if (!CheckGetElementByIdArg(id))
+ return nullptr;
+
+ nsIdentifierMapEntry *entry = mIdentifierMap.PutEntry(id);
+ NS_ENSURE_TRUE(entry, nullptr);
+
+ entry->AddContentChangeCallback(aObserver, aData, aForImage);
+ return aForImage ? entry->GetImageIdElement() : entry->GetIdElement();
+}
+
+void
+nsDocument::RemoveIDTargetObserver(nsIAtom* aID, IDTargetObserver aObserver,
+ void* aData, bool aForImage)
+{
+ nsDependentAtomString id(aID);
+
+ if (!CheckGetElementByIdArg(id))
+ return;
+
+ nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(id);
+ if (!entry) {
+ return;
+ }
+
+ entry->RemoveContentChangeCallback(aObserver, aData, aForImage);
+}
+
+NS_IMETHODIMP
+nsDocument::MozSetImageElement(const nsAString& aImageElementId,
+ nsIDOMElement* aImageElement)
+{
+ nsCOMPtr<Element> el = do_QueryInterface(aImageElement);
+ MozSetImageElement(aImageElementId, el);
+ return NS_OK;
+}
+
+void
+nsDocument::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;
+
+ nsIdentifierMapEntry *entry = mIdentifierMap.PutEntry(aImageElementId);
+ if (entry) {
+ entry->SetImageElement(aElement);
+ if (entry->IsEmpty()) {
+ mIdentifierMap.RemoveEntry(entry);
+ }
+ }
+}
+
+Element*
+nsDocument::LookupImageElement(const nsAString& aId)
+{
+ if (aId.IsEmpty())
+ return nullptr;
+
+ nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aId);
+ return entry ? entry->GetImageIdElement() : nullptr;
+}
+
+void
+nsDocument::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(nsIDocument::GetDocumentURI());
+ }
+
+ // Dispatch observer notification to notify observers document is interactive.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal *principal = GetPrincipal();
+ os->NotifyObservers(static_cast<nsIDocument*>(this),
+ nsContentUtils::IsSystemPrincipal(principal) ?
+ "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, static_cast<nsIDocument*>(this),
+ NS_LITERAL_STRING("DOMContentLoaded"),
+ true, false);
+
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ nsIDocShell* docShell = this->GetDocShell();
+
+ if (timelines && timelines->HasConsumer(docShell)) {
+ timelines->AddMarkerForDocShell(docShell,
+ MakeUnique<DocLoadingTimelineMarker>("document::DOMContentLoaded"));
+ }
+
+ if (mTiming) {
+ mTiming->NotifyDOMContentLoadedEnd(nsIDocument::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<EventTarget> target_frame;
+
+ if (mParentDocument) {
+ target_frame = mParentDocument->FindContentForSubDocument(this);
+ }
+
+ if (target_frame) {
+ nsCOMPtr<nsIDocument> parent = mParentDocument;
+ do {
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(parent);
+
+ nsCOMPtr<nsIDOMEvent> event;
+ if (domDoc) {
+ domDoc->CreateEvent(NS_LITERAL_STRING("Events"),
+ getter_AddRefs(event));
+
+ }
+
+ if (event) {
+ event->InitEvent(NS_LITERAL_STRING("DOMFrameContentLoaded"), 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;
+
+ nsIPresShell *shell = parent->GetShell();
+ if (shell) {
+ RefPtr<nsPresContext> context = shell->GetPresContext();
+
+ if (context) {
+ EventDispatcher::Dispatch(parent, context, innerEvent, event,
+ &status);
+ }
+ }
+ }
+ }
+
+ parent = parent->GetParentDocument();
+ } while (parent);
+ }
+
+ // If the document has a manifest attribute, fire a MozApplicationManifest
+ // event.
+ Element* root = GetRootElement();
+ if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::manifest)) {
+ nsContentUtils::DispatchChromeEvent(this, static_cast<nsIDocument*>(this),
+ NS_LITERAL_STRING("MozApplicationManifest"),
+ true, true);
+ }
+
+ if (mMaybeServiceWorkerControlled) {
+ using mozilla::dom::workers::ServiceWorkerManager;
+ RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+ if (swm) {
+ swm->MaybeCheckNavigationUpdate(this);
+ }
+ }
+
+ UnblockOnload(true);
+}
+
+void
+nsDocument::EndLoad()
+{
+ // 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;
+ }
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
+
+ UnblockDOMContentLoaded();
+}
+
+void
+nsDocument::UnblockDOMContentLoaded()
+{
+ MOZ_ASSERT(mBlockDOMContentLoaded);
+ if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) {
+ return;
+ }
+ mDidFireDOMContentLoaded = true;
+
+ MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
+ if (!mSynchronousDOMContentLoaded) {
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this, &nsDocument::DispatchContentLoadedEvents);
+ NS_DispatchToCurrentThread(ev);
+ } else {
+ DispatchContentLoadedEvents();
+ }
+}
+
+void
+nsDocument::ContentStateChanged(nsIContent* aContent, EventStates aStateMask)
+{
+ NS_PRECONDITION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a scriptblocker");
+ NS_DOCUMENT_NOTIFY_OBSERVERS(ContentStateChanged,
+ (this, aContent, aStateMask));
+}
+
+void
+nsDocument::DocumentStatesChanged(EventStates aStateMask)
+{
+ // Invalidate our cached state.
+ mGotDocumentState &= ~aStateMask;
+ mDocumentState &= ~aStateMask;
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(DocumentStatesChanged, (this, aStateMask));
+}
+
+void
+nsDocument::StyleRuleChanged(StyleSheet* aSheet,
+ css::Rule* aStyleRule)
+{
+ NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleChanged, (aSheet));
+
+ if (StyleSheetChangeEventsEnabled()) {
+ DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
+ "StyleRuleChanged",
+ mRule,
+ aStyleRule ? aStyleRule->GetDOMRule() : nullptr);
+ }
+}
+
+void
+nsDocument::StyleRuleAdded(StyleSheet* aSheet,
+ css::Rule* aStyleRule)
+{
+ NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleAdded, (aSheet));
+
+ if (StyleSheetChangeEventsEnabled()) {
+ DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
+ "StyleRuleAdded",
+ mRule,
+ aStyleRule ? aStyleRule->GetDOMRule()
+ : nullptr);
+ }
+}
+
+void
+nsDocument::StyleRuleRemoved(StyleSheet* aSheet,
+ css::Rule* aStyleRule)
+{
+ NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleRemoved, (aSheet));
+
+ if (StyleSheetChangeEventsEnabled()) {
+ DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
+ "StyleRuleRemoved",
+ mRule,
+ aStyleRule ? aStyleRule->GetDOMRule()
+ : nullptr);
+ }
+}
+
+#undef DO_STYLESHEET_NOTIFICATION
+
+already_AddRefed<AnonymousContent>
+nsIDocument::InsertAnonymousContent(Element& aElement, ErrorResult& aRv)
+{
+ nsIPresShell* shell = GetShell();
+ if (!shell || !shell->GetCanvasFrame()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ nsCOMPtr<Element> container = shell->GetCanvasFrame()
+ ->GetCustomContentContainer();
+ if (!container) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ // Clone the node to avoid returning a direct reference
+ nsCOMPtr<nsINode> clonedElement = aElement.CloneNode(true, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Insert the element into the container
+ nsresult rv;
+ rv = container->AppendChildTo(clonedElement->AsContent(), true);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ RefPtr<AnonymousContent> anonymousContent =
+ new AnonymousContent(clonedElement->AsElement());
+ mAnonymousContents.AppendElement(anonymousContent);
+
+ shell->GetCanvasFrame()->ShowCustomContentContainer();
+
+ return anonymousContent.forget();
+}
+
+void
+nsIDocument::RemoveAnonymousContent(AnonymousContent& aContent,
+ ErrorResult& aRv)
+{
+ nsIPresShell* shell = GetShell();
+ if (!shell || !shell->GetCanvasFrame()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ nsCOMPtr<Element> container = shell->GetCanvasFrame()
+ ->GetCustomContentContainer();
+ if (!container) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // Iterate over mAnonymousContents to find and remove the given node.
+ for (size_t i = 0, len = mAnonymousContents.Length(); i < len; ++i) {
+ if (mAnonymousContents[i] == &aContent) {
+ // Get the node from the customContent
+ nsCOMPtr<Element> node = aContent.GetContentNode();
+
+ // Remove the entry in mAnonymousContents
+ mAnonymousContents.RemoveElementAt(i);
+
+ // Remove the node from its container
+ container->RemoveChild(*node, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ break;
+ }
+ }
+ if (mAnonymousContents.IsEmpty()) {
+ shell->GetCanvasFrame()->HideCustomContentContainer();
+ }
+}
+
+Element*
+nsIDocument::GetAnonRootIfInAnonymousContentContainer(nsINode* aNode) const
+{
+ if (!aNode->IsInNativeAnonymousSubtree()) {
+ return nullptr;
+ }
+
+ nsIPresShell* shell = GetShell();
+ if (!shell || !shell->GetCanvasFrame()) {
+ return nullptr;
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+ nsCOMPtr<Element> customContainer = shell->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 child->IsElement() ? child->AsElement() : nullptr;
+ }
+ child = parent;
+ parent = child->GetParentNode();
+ }
+ return nullptr;
+}
+
+//
+// nsIDOMDocument interface
+//
+DocumentType*
+nsIDocument::GetDoctype() const
+{
+ for (nsIContent* child = GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE) {
+ return static_cast<DocumentType*>(child);
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDocument::GetDoctype(nsIDOMDocumentType** aDoctype)
+{
+ MOZ_ASSERT(aDoctype);
+ nsCOMPtr<nsIDOMDocumentType> doctype = nsIDocument::GetDoctype();
+ doctype.forget(aDoctype);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetImplementation(nsIDOMDOMImplementation** aImplementation)
+{
+ ErrorResult rv;
+ *aImplementation = GetImplementation(rv);
+ if (rv.Failed()) {
+ MOZ_ASSERT(!*aImplementation);
+ return rv.StealNSResult();
+ }
+ NS_ADDREF(*aImplementation);
+ return NS_OK;
+}
+
+DOMImplementation*
+nsDocument::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;
+}
+
+NS_IMETHODIMP
+nsDocument::GetDocumentElement(nsIDOMElement** aDocumentElement)
+{
+ NS_ENSURE_ARG_POINTER(aDocumentElement);
+
+ Element* root = GetRootElement();
+ if (root) {
+ return CallQueryInterface(root, aDocumentElement);
+ }
+
+ *aDocumentElement = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::CreateElement(const nsAString& aTagName,
+ nsIDOMElement** aReturn)
+{
+ *aReturn = nullptr;
+ ErrorResult rv;
+ ElementCreationOptionsOrString options;
+
+ options.SetAsString();
+ nsCOMPtr<Element> element = CreateElement(aTagName, options, rv);
+ NS_ENSURE_FALSE(rv.Failed(), rv.StealNSResult());
+ return CallQueryInterface(element, aReturn);
+}
+
+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<mozilla::dom::CustomElementRegistry>
+nsDocument::GetCustomElementRegistry()
+{
+ nsAutoString contentType;
+ GetContentType(contentType);
+ if (!IsHTMLDocument() &&
+ !contentType.EqualsLiteral("application/xhtml+xml")) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window(
+ do_QueryInterface(mScriptGlobalObject ? mScriptGlobalObject
+ : GetScopeObject()));
+ if (!window) {
+ return nullptr;
+ }
+
+ RefPtr<CustomElementRegistry> registry = window->CustomElements();
+ if (!registry) {
+ return nullptr;
+ }
+
+ return registry.forget();
+}
+
+already_AddRefed<Element>
+nsDocument::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;
+ if (aOptions.IsElementCreationOptions()) {
+ // Throw NotFoundError if 'is' is not-null and definition is null
+ is = CheckCustomElementName(aOptions.GetAsElementCreationOptions(),
+ needsLowercase ? lcTagName : aTagName, mDefaultElementType, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<Element> elem = CreateElem(
+ needsLowercase ? lcTagName : aTagName, nullptr, mDefaultElementType, is);
+
+ return elem.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ nsIDOMElement** aReturn)
+{
+ *aReturn = nullptr;
+ ElementCreationOptionsOrString options;
+ options.SetAsString();
+
+ ErrorResult rv;
+ nsCOMPtr<Element> element =
+ CreateElementNS(aNamespaceURI, aQualifiedName, options, rv);
+ NS_ENSURE_FALSE(rv.Failed(), rv.StealNSResult());
+ return CallQueryInterface(element, aReturn);
+}
+
+already_AddRefed<Element>
+nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ const ElementCreationOptionsOrString& aOptions,
+ ErrorResult& rv)
+{
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI,
+ aQualifiedName,
+ mNodeInfoManager,
+ nsIDOMNode::ELEMENT_NODE,
+ getter_AddRefs(nodeInfo));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ const nsString* is = nullptr;
+ if (aOptions.IsElementCreationOptions()) {
+ // Throw NotFoundError if 'is' is not-null and definition is null
+ is = CheckCustomElementName(aOptions.GetAsElementCreationOptions(),
+ aQualifiedName, nodeInfo->NamespaceID(), rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<Element> element;
+ rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(),
+ NOT_FROM_PARSER, is);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ return element.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateTextNode(const nsAString& aData, nsIDOMText** aReturn)
+{
+ *aReturn = nsIDocument::CreateTextNode(aData).take();
+ return NS_OK;
+}
+
+already_AddRefed<nsTextNode>
+nsIDocument::CreateTextNode(const nsAString& aData) const
+{
+ RefPtr<nsTextNode> text = new nsTextNode(mNodeInfoManager);
+ // Don't notify; this node is still being created.
+ text->SetText(aData, false);
+ return text.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateDocumentFragment(nsIDOMDocumentFragment** aReturn)
+{
+ *aReturn = nsIDocument::CreateDocumentFragment().take();
+ return NS_OK;
+}
+
+already_AddRefed<DocumentFragment>
+nsIDocument::CreateDocumentFragment() const
+{
+ RefPtr<DocumentFragment> frag = new DocumentFragment(mNodeInfoManager);
+ return frag.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateComment(const nsAString& aData, nsIDOMComment** aReturn)
+{
+ *aReturn = nsIDocument::CreateComment(aData).take();
+ return NS_OK;
+}
+
+// Unfortunately, bareword "Comment" is ambiguous with some Mac system headers.
+already_AddRefed<dom::Comment>
+nsIDocument::CreateComment(const nsAString& aData) const
+{
+ RefPtr<dom::Comment> comment = new dom::Comment(mNodeInfoManager);
+
+ // Don't notify; this node is still being created.
+ comment->SetText(aData, false);
+ return comment.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateCDATASection(const nsAString& aData,
+ nsIDOMCDATASection** aReturn)
+{
+ NS_ENSURE_ARG_POINTER(aReturn);
+ ErrorResult rv;
+ *aReturn = nsIDocument::CreateCDATASection(aData, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<CDATASection>
+nsIDocument::CreateCDATASection(const nsAString& aData,
+ ErrorResult& rv)
+{
+ if (IsHTMLDocument()) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (FindInReadable(NS_LITERAL_STRING("]]>"), aData)) {
+ rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+ return nullptr;
+ }
+
+ RefPtr<CDATASection> cdata = new CDATASection(mNodeInfoManager);
+
+ // Don't notify; this node is still being created.
+ cdata->SetText(aData, false);
+
+ return cdata.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateProcessingInstruction(const nsAString& aTarget,
+ const nsAString& aData,
+ nsIDOMProcessingInstruction** aReturn)
+{
+ ErrorResult rv;
+ *aReturn =
+ nsIDocument::CreateProcessingInstruction(aTarget, aData, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<ProcessingInstruction>
+nsIDocument::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(NS_LITERAL_STRING("?>"), aData)) {
+ rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+ return nullptr;
+ }
+
+ RefPtr<ProcessingInstruction> pi =
+ NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
+
+ return pi.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateAttribute(const nsAString& aName,
+ nsIDOMAttr** aReturn)
+{
+ ErrorResult rv;
+ *aReturn = nsIDocument::CreateAttribute(aName, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<Attr>
+nsIDocument::CreateAttribute(const nsAString& aName, ErrorResult& rv)
+{
+ WarnOnceAbout(eCreateAttribute);
+
+ 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,
+ nsIDOMNode::ATTRIBUTE_NODE,
+ getter_AddRefs(nodeInfo));
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ RefPtr<Attr> attribute = new Attr(nullptr, nodeInfo.forget(),
+ EmptyString());
+ return attribute.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateAttributeNS(const nsAString & aNamespaceURI,
+ const nsAString & aQualifiedName,
+ nsIDOMAttr **aResult)
+{
+ ErrorResult rv;
+ *aResult =
+ nsIDocument::CreateAttributeNS(aNamespaceURI, aQualifiedName, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<Attr>
+nsIDocument::CreateAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ ErrorResult& rv)
+{
+ WarnOnceAbout(eCreateAttributeNS);
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI,
+ aQualifiedName,
+ mNodeInfoManager,
+ nsIDOMNode::ATTRIBUTE_NODE,
+ getter_AddRefs(nodeInfo));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<Attr> attribute = new Attr(nullptr, nodeInfo.forget(),
+ EmptyString());
+ return attribute.forget();
+}
+
+bool
+nsDocument::CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+ JS::Rooted<JSObject*> global(aCx,
+ JS_GetGlobalForObject(aCx, &args.callee()));
+ RefPtr<nsGlobalWindow> window;
+ UNWRAP_OBJECT(Window, global, window);
+ MOZ_ASSERT(window, "Should have a non-null window");
+
+ nsDocument* document = static_cast<nsDocument*>(window->GetDoc());
+
+ // Function name is the type of the custom element.
+ JSString* jsFunName =
+ JS_GetFunctionId(JS_ValueToFunction(aCx, args.calleev()));
+ nsAutoJSString elemName;
+ if (!elemName.init(aCx, jsFunName)) {
+ return true;
+ }
+
+ RefPtr<mozilla::dom::CustomElementRegistry> registry = window->CustomElements();
+ if (!registry) {
+ return true;
+ }
+
+ nsCOMPtr<nsIAtom> typeAtom(NS_Atomize(elemName));
+ CustomElementDefinition* definition = registry->mCustomDefinitions.Get(typeAtom);
+ if (!definition) {
+ return true;
+ }
+
+ nsDependentAtomString localName(definition->mLocalName);
+
+ nsCOMPtr<Element> element =
+ document->CreateElem(localName, nullptr, kNameSpaceID_XHTML);
+ NS_ENSURE_TRUE(element, true);
+
+ if (definition->mLocalName != typeAtom) {
+ // This element is a custom element by extension, thus we need to
+ // do some special setup. For non-extended custom elements, this happens
+ // when the element is created.
+ nsContentUtils::SetupCustomElement(element, &elemName);
+ }
+
+ nsresult rv = nsContentUtils::WrapNative(aCx, element, element, args.rval());
+ NS_ENSURE_SUCCESS(rv, true);
+
+ return true;
+}
+
+bool
+nsDocument::IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject)
+{
+ JS::Rooted<JSObject*> obj(aCx, aObject);
+
+ if (Preferences::GetBool("dom.webcomponents.enabled")) {
+ return true;
+ }
+
+ // Check for the webcomponents permission. See Bug 1181555.
+ JSAutoCompartment ac(aCx, obj);
+ JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, obj));
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(global));
+
+ return IsWebComponentsEnabled(window);
+}
+
+bool
+nsDocument::IsWebComponentsEnabled(dom::NodeInfo* aNodeInfo)
+{
+ if (Preferences::GetBool("dom.webcomponents.enabled")) {
+ return true;
+ }
+
+ nsIDocument* doc = aNodeInfo->GetDocument();
+ // Use GetScopeObject() here so that data documents work the same way as the
+ // main document they're associated with.
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(doc->GetScopeObject());
+ return IsWebComponentsEnabled(window);
+}
+
+bool
+nsDocument::IsWebComponentsEnabled(nsPIDOMWindowInner* aWindow)
+{
+ if (aWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ uint32_t perm;
+ rv = permMgr->TestPermissionFromWindow(
+ aWindow, "moz-extremely-unstable-and-will-change-webcomponents", &perm);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return perm == nsIPermissionManager::ALLOW_ACTION;
+ }
+
+ return false;
+}
+
+void
+nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
+ const ElementRegistrationOptions& aOptions,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& rv)
+{
+ RefPtr<CustomElementRegistry> registry(GetCustomElementRegistry());
+ if (!registry) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ // Unconditionally convert TYPE to lowercase.
+ nsAutoString lcType;
+ nsContentUtils::ASCIIToLower(aType, lcType);
+
+ nsIGlobalObject* sgo = GetScopeObject();
+ if (!sgo) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JS::Rooted<JSObject*> global(aCx, sgo->GetGlobalJSObject());
+ JS::Rooted<JSObject*> protoObject(aCx);
+
+ if (!aOptions.mPrototype) {
+ JS::Rooted<JSObject*> htmlProto(aCx);
+ htmlProto = HTMLElementBinding::GetProtoObjectHandle(aCx);
+ if (!htmlProto) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ protoObject = JS_NewObjectWithGivenProto(aCx, nullptr, htmlProto);
+ if (!protoObject) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ } else {
+ protoObject = aOptions.mPrototype;
+
+ // Get the unwrapped prototype to do some checks.
+ JS::Rooted<JSObject*> protoObjectUnwrapped(aCx, js::CheckedUnwrap(protoObject));
+ if (!protoObjectUnwrapped) {
+ // If the caller's compartment does not have permission to access the
+ // unwrapped prototype then throw.
+ rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ // If PROTOTYPE is already an interface prototype object for any interface
+ // object or PROTOTYPE has a non-configurable property named constructor,
+ // throw a NotSupportedError and stop.
+ const js::Class* clasp = js::GetObjectClass(protoObjectUnwrapped);
+ if (IsDOMIfaceAndProtoClass(clasp)) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ JS::Rooted<JS::PropertyDescriptor> descRoot(aCx);
+ JS::MutableHandle<JS::PropertyDescriptor> desc(&descRoot);
+ // This check may go through a wrapper, but as we checked above
+ // it should be transparent or an xray. This should be fine for now,
+ // until the spec is sorted out.
+ if (!JS_GetPropertyDescriptor(aCx, protoObject, "constructor", desc)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ if (!desc.configurable()) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+ }
+
+ JS::Rooted<JSFunction*> constructor(aCx);
+ {
+ // Go into the document's global compartment when creating the constructor
+ // function because we want to get the correct document (where the
+ // definition is registered) when it is called.
+ JSAutoCompartment ac(aCx, global);
+
+ // Create constructor to return. Store the name of the custom element as the
+ // name of the function.
+ constructor = JS_NewFunction(aCx, nsDocument::CustomElementConstructor, 0,
+ JSFUN_CONSTRUCTOR,
+ NS_ConvertUTF16toUTF8(lcType).get());
+ if (!constructor) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ JS::Rooted<JSObject*> wrappedConstructor(aCx);
+ wrappedConstructor = JS_GetFunctionObject(constructor);
+ if (!JS_WrapObject(aCx, &wrappedConstructor)) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ if (!JS_LinkConstructorAndPrototype(aCx, wrappedConstructor, protoObject)) {
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ ElementDefinitionOptions options;
+ if (!aOptions.mExtends.IsVoid()) {
+ // Only convert NAME to lowercase in HTML documents.
+ nsAutoString lcName;
+ IsHTMLDocument() ? nsContentUtils::ASCIIToLower(aOptions.mExtends, lcName)
+ : lcName.Assign(aOptions.mExtends);
+
+ options.mExtends.Construct(lcName);
+ }
+
+ RootedCallback<OwningNonNull<binding_detail::FastFunction>> functionConstructor(aCx);
+ functionConstructor = new binding_detail::FastFunction(aCx, wrappedConstructor, sgo);
+
+ registry->Define(lcType, functionConstructor, options, rv);
+
+ aRetval.set(wrappedConstructor);
+}
+
+NS_IMETHODIMP
+nsDocument::GetElementsByTagName(const nsAString& aTagname,
+ nsIDOMNodeList** aReturn)
+{
+ RefPtr<nsContentList> list = GetElementsByTagName(aTagname);
+ NS_ENSURE_TRUE(list, NS_ERROR_OUT_OF_MEMORY);
+
+ // transfer ref to aReturn
+ list.forget(aReturn);
+ return NS_OK;
+}
+
+long
+nsDocument::BlockedTrackingNodeCount() const
+{
+ return mBlockedTrackingNodes.Length();
+}
+
+already_AddRefed<nsSimpleContentList>
+nsDocument::BlockedTrackingNodes() const
+{
+ RefPtr<nsSimpleContentList> list = new nsSimpleContentList(nullptr);
+
+ nsTArray<nsWeakPtr> blockedTrackingNodes;
+ blockedTrackingNodes = mBlockedTrackingNodes;
+
+ for (unsigned long i = 0; i < blockedTrackingNodes.Length(); i++) {
+ nsWeakPtr weakNode = blockedTrackingNodes[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();
+}
+
+already_AddRefed<nsContentList>
+nsIDocument::GetElementsByTagNameNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ ErrorResult& aResult)
+{
+ int32_t nameSpaceId = kNameSpaceID_Wildcard;
+
+ if (!aNamespaceURI.EqualsLiteral("*")) {
+ aResult =
+ nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
+ nameSpaceId);
+ if (aResult.Failed()) {
+ return nullptr;
+ }
+ }
+
+ NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!");
+
+ return NS_GetContentList(this, nameSpaceId, aLocalName);
+}
+
+NS_IMETHODIMP
+nsDocument::GetElementsByTagNameNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ nsIDOMNodeList** aReturn)
+{
+ ErrorResult rv;
+ RefPtr<nsContentList> list =
+ nsIDocument::GetElementsByTagNameNS(aNamespaceURI, aLocalName, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ // transfer ref to aReturn
+ list.forget(aReturn);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetStyleSheets(nsIDOMStyleSheetList** aStyleSheets)
+{
+ NS_ADDREF(*aStyleSheets = StyleSheets());
+ return NS_OK;
+}
+
+StyleSheetList*
+nsDocument::StyleSheets()
+{
+ if (!mDOMStyleSheets) {
+ mDOMStyleSheets = new nsDOMStyleSheetList(this);
+ }
+ return mDOMStyleSheets;
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozSelectedStyleSheetSet(nsAString& aSheetSet)
+{
+ nsIDocument::GetSelectedStyleSheetSet(aSheetSet);
+ return NS_OK;
+}
+
+void
+nsIDocument::GetSelectedStyleSheetSet(nsAString& aSheetSet)
+{
+ aSheetSet.Truncate();
+
+ // Look through our sheets, find the selected set title
+ int32_t count = GetNumberOfStyleSheets();
+ nsAutoString title;
+ for (int32_t index = 0; index < count; index++) {
+ StyleSheet* sheet = GetStyleSheetAt(index);
+ NS_ASSERTION(sheet, "Null sheet in sheet list!");
+
+ // XXXheycam Make this work with ServoStyleSheets.
+ if (sheet->IsServo()) {
+ NS_ERROR("stylo: can't handle alternate ServoStyleSheets yet");
+ continue;
+ }
+
+ bool disabled;
+ sheet->AsGecko()->GetDisabled(&disabled);
+ if (disabled) {
+ // Disabled sheets don't affect the currently selected set
+ continue;
+ }
+
+ sheet->AsGecko()->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;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocument::SetMozSelectedStyleSheetSet(const nsAString& aSheetSet)
+{
+ SetSelectedStyleSheetSet(aSheetSet);
+ return NS_OK;
+}
+
+void
+nsDocument::SetSelectedStyleSheetSet(const nsAString& aSheetSet)
+{
+ if (DOMStringIsNull(aSheetSet)) {
+ return;
+ }
+
+ // Must update mLastStyleSheetSet before doing anything else with stylesheets
+ // or CSSLoaders.
+ mLastStyleSheetSet = aSheetSet;
+ EnableStyleSheetsForSetInternal(aSheetSet, true);
+}
+
+NS_IMETHODIMP
+nsDocument::GetLastStyleSheetSet(nsAString& aSheetSet)
+{
+ nsString sheetSet;
+ GetLastStyleSheetSet(sheetSet);
+ aSheetSet = sheetSet;
+ return NS_OK;
+}
+
+void
+nsDocument::GetLastStyleSheetSet(nsString& aSheetSet)
+{
+ aSheetSet = mLastStyleSheetSet;
+}
+
+NS_IMETHODIMP
+nsDocument::GetPreferredStyleSheetSet(nsAString& aSheetSet)
+{
+ nsIDocument::GetPreferredStyleSheetSet(aSheetSet);
+ return NS_OK;
+}
+
+void
+nsIDocument::GetPreferredStyleSheetSet(nsAString& aSheetSet)
+{
+ GetHeaderData(nsGkAtoms::headerDefaultStyle, aSheetSet);
+}
+
+NS_IMETHODIMP
+nsDocument::GetStyleSheetSets(nsISupports** aList)
+{
+ NS_ADDREF(*aList = StyleSheetSets());
+ return NS_OK;
+}
+
+DOMStringList*
+nsDocument::StyleSheetSets()
+{
+ if (!mStyleSheetSetList) {
+ mStyleSheetSetList = new nsDOMStyleSheetSetList(this);
+ }
+ return mStyleSheetSetList;
+}
+
+NS_IMETHODIMP
+nsDocument::MozEnableStyleSheetsForSet(const nsAString& aSheetSet)
+{
+ EnableStyleSheetsForSet(aSheetSet);
+ return NS_OK;
+}
+
+void
+nsDocument::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
+nsDocument::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet,
+ bool aUpdateCSSLoader)
+{
+ BeginUpdate(UPDATE_STYLE);
+ int32_t count = GetNumberOfStyleSheets();
+ nsAutoString title;
+ for (int32_t index = 0; index < count; index++) {
+ StyleSheet* sheet = GetStyleSheetAt(index);
+ NS_ASSERTION(sheet, "Null sheet in sheet list!");
+
+ // XXXheycam Make this work with ServoStyleSheets.
+ if (sheet->IsServo()) {
+ NS_ERROR("stylo: can't handle alternate ServoStyleSheets yet");
+ continue;
+ }
+
+ sheet->AsGecko()->GetTitle(title);
+ if (!title.IsEmpty()) {
+ sheet->AsGecko()->SetEnabled(title.Equals(aSheetSet));
+ }
+ }
+ if (aUpdateCSSLoader) {
+ CSSLoader()->SetPreferredSheet(aSheetSet);
+ }
+ EndUpdate(UPDATE_STYLE);
+}
+
+NS_IMETHODIMP
+nsDocument::GetCharacterSet(nsAString& aCharacterSet)
+{
+ nsIDocument::GetCharacterSet(aCharacterSet);
+ return NS_OK;
+}
+
+void
+nsIDocument::GetCharacterSet(nsAString& aCharacterSet) const
+{
+ CopyASCIItoUTF16(GetDocumentCharacterSet(), aCharacterSet);
+}
+
+NS_IMETHODIMP
+nsDocument::ImportNode(nsIDOMNode* aImportedNode,
+ bool aDeep,
+ uint8_t aArgc,
+ nsIDOMNode** aResult)
+{
+ if (aArgc == 0) {
+ aDeep = true;
+ }
+
+ *aResult = nullptr;
+
+ nsCOMPtr<nsINode> imported = do_QueryInterface(aImportedNode);
+ NS_ENSURE_TRUE(imported, NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ nsCOMPtr<nsINode> result = nsIDocument::ImportNode(*imported, aDeep, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ NS_ADDREF(*aResult = result->AsDOMNode());
+ return NS_OK;
+}
+
+already_AddRefed<nsINode>
+nsIDocument::ImportNode(nsINode& aNode, bool aDeep, ErrorResult& rv) const
+{
+ nsINode* imported = &aNode;
+
+ switch (imported->NodeType()) {
+ case nsIDOMNode::DOCUMENT_NODE:
+ {
+ break;
+ }
+ case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
+ {
+ if (ShadowRoot::FromNode(imported)) {
+ break;
+ }
+ MOZ_FALLTHROUGH;
+ }
+ case nsIDOMNode::ATTRIBUTE_NODE:
+ case nsIDOMNode::ELEMENT_NODE:
+ case nsIDOMNode::PROCESSING_INSTRUCTION_NODE:
+ case nsIDOMNode::TEXT_NODE:
+ case nsIDOMNode::CDATA_SECTION_NODE:
+ case nsIDOMNode::COMMENT_NODE:
+ case nsIDOMNode::DOCUMENT_TYPE_NODE:
+ {
+ nsCOMPtr<nsINode> newNode;
+ nsCOMArray<nsINode> nodesWithProperties;
+ rv = nsNodeUtils::Clone(imported, aDeep, mNodeInfoManager,
+ nodesWithProperties, getter_AddRefs(newNode));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return newNode.forget();
+ }
+ default:
+ {
+ NS_WARNING("Don't know how to clone this nodetype for importNode.");
+ }
+ }
+
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDocument::LoadBindingDocument(const nsAString& aURI)
+{
+ ErrorResult rv;
+ nsIDocument::LoadBindingDocument(aURI,
+ nsContentUtils::GetCurrentJSContext()
+ ? Some(nsContentUtils::SubjectPrincipal())
+ : Nothing(),
+ rv);
+ return rv.StealNSResult();
+}
+
+void
+nsIDocument::LoadBindingDocument(const nsAString& aURI,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& rv)
+{
+ LoadBindingDocument(aURI, Some(&aSubjectPrincipal), rv);
+}
+
+void
+nsIDocument::LoadBindingDocument(const nsAString& aURI,
+ const Maybe<nsIPrincipal*>& aSubjectPrincipal,
+ ErrorResult& rv)
+{
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), aURI,
+ mCharacterSet.get(),
+ GetDocBaseURI());
+ if (rv.Failed()) {
+ return;
+ }
+
+ // Note - This computation of subjectPrincipal isn't necessarily sensical.
+ // It's just designed to preserve the old semantics during a mass-conversion
+ // patch.
+ nsCOMPtr<nsIPrincipal> subjectPrincipal =
+ aSubjectPrincipal.isSome() ? aSubjectPrincipal.value() : NodePrincipal();
+ BindingManager()->LoadBindingDocument(this, uri, subjectPrincipal);
+}
+
+NS_IMETHODIMP
+nsDocument::GetBindingParent(nsIDOMNode* aNode, nsIDOMElement** aResult)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ NS_ENSURE_ARG_POINTER(node);
+
+ Element* bindingParent = nsIDocument::GetBindingParent(*node);
+ nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(bindingParent);
+ retval.forget(aResult);
+ return NS_OK;
+}
+
+Element*
+nsIDocument::GetBindingParent(nsINode& aNode)
+{
+ nsCOMPtr<nsIContent> content(do_QueryInterface(&aNode));
+ if (!content)
+ return nullptr;
+
+ nsIContent* bindingParent = content->GetBindingParent();
+ return bindingParent ? bindingParent->AsElement() : nullptr;
+}
+
+static Element*
+GetElementByAttribute(nsIContent* aContent, nsIAtom* aAttrName,
+ const nsAString& aAttrValue, bool aUniversalMatch)
+{
+ if (aUniversalMatch ? aContent->HasAttr(kNameSpaceID_None, aAttrName) :
+ aContent->AttrValueIs(kNameSpaceID_None, aAttrName,
+ aAttrValue, eCaseMatters)) {
+ return aContent->AsElement();
+ }
+
+ for (nsIContent* child = aContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ Element* matchedElement =
+ GetElementByAttribute(child, aAttrName, aAttrValue, aUniversalMatch);
+ if (matchedElement)
+ return matchedElement;
+ }
+
+ return nullptr;
+}
+
+Element*
+nsDocument::GetAnonymousElementByAttribute(nsIContent* aElement,
+ nsIAtom* aAttrName,
+ const nsAString& aAttrValue) const
+{
+ nsINodeList* nodeList = BindingManager()->GetAnonymousNodesFor(aElement);
+ if (!nodeList)
+ return nullptr;
+
+ uint32_t length = 0;
+ nodeList->GetLength(&length);
+
+ bool universalMatch = aAttrValue.EqualsLiteral("*");
+
+ for (uint32_t i = 0; i < length; ++i) {
+ nsIContent* current = nodeList->Item(i);
+ Element* matchedElm =
+ GetElementByAttribute(current, aAttrName, aAttrValue, universalMatch);
+ if (matchedElm)
+ return matchedElm;
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDocument::GetAnonymousElementByAttribute(nsIDOMElement* aElement,
+ const nsAString& aAttrName,
+ const nsAString& aAttrValue,
+ nsIDOMElement** aResult)
+{
+ nsCOMPtr<Element> element = do_QueryInterface(aElement);
+ NS_ENSURE_ARG_POINTER(element);
+
+ Element* anonEl =
+ nsIDocument::GetAnonymousElementByAttribute(*element, aAttrName,
+ aAttrValue);
+ nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(anonEl);
+ retval.forget(aResult);
+ return NS_OK;
+}
+
+Element*
+nsIDocument::GetAnonymousElementByAttribute(Element& aElement,
+ const nsAString& aAttrName,
+ const nsAString& aAttrValue)
+{
+ nsCOMPtr<nsIAtom> attribute = NS_Atomize(aAttrName);
+
+ return GetAnonymousElementByAttribute(&aElement, attribute, aAttrValue);
+}
+
+
+NS_IMETHODIMP
+nsDocument::GetAnonymousNodes(nsIDOMElement* aElement,
+ nsIDOMNodeList** aResult)
+{
+ *aResult = nullptr;
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aElement));
+ return BindingManager()->GetAnonymousNodesFor(content, aResult);
+}
+
+nsINodeList*
+nsIDocument::GetAnonymousNodes(Element& aElement)
+{
+ return BindingManager()->GetAnonymousNodesFor(&aElement);
+}
+
+NS_IMETHODIMP
+nsDocument::CreateRange(nsIDOMRange** aReturn)
+{
+ ErrorResult rv;
+ *aReturn = nsIDocument::CreateRange(rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<nsRange>
+nsIDocument::CreateRange(ErrorResult& rv)
+{
+ RefPtr<nsRange> range = new nsRange(this);
+ nsresult res = range->Set(this, 0, this, 0);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ return range.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateNodeIterator(nsIDOMNode *aRoot,
+ uint32_t aWhatToShow,
+ nsIDOMNodeFilter *aFilter,
+ uint8_t aOptionalArgc,
+ nsIDOMNodeIterator **_retval)
+{
+ *_retval = nullptr;
+
+ if (!aOptionalArgc) {
+ aWhatToShow = nsIDOMNodeFilter::SHOW_ALL;
+ }
+
+ if (!aRoot) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ nsCOMPtr<nsINode> root = do_QueryInterface(aRoot);
+ NS_ENSURE_TRUE(root, NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ *_retval = nsIDocument::CreateNodeIterator(*root, aWhatToShow,
+ NodeFilterHolder(aFilter),
+ rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<NodeIterator>
+nsIDocument::CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow,
+ NodeFilter* aFilter,
+ ErrorResult& rv) const
+{
+ return CreateNodeIterator(aRoot, aWhatToShow, NodeFilterHolder(aFilter), rv);
+}
+
+already_AddRefed<NodeIterator>
+nsIDocument::CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow,
+ NodeFilterHolder aFilter,
+ ErrorResult& rv) const
+{
+ nsINode* root = &aRoot;
+ RefPtr<NodeIterator> iterator = new NodeIterator(root, aWhatToShow,
+ Move(aFilter));
+ return iterator.forget();
+}
+
+NS_IMETHODIMP
+nsDocument::CreateTreeWalker(nsIDOMNode *aRoot,
+ uint32_t aWhatToShow,
+ nsIDOMNodeFilter *aFilter,
+ uint8_t aOptionalArgc,
+ nsIDOMTreeWalker **_retval)
+{
+ *_retval = nullptr;
+
+ if (!aOptionalArgc) {
+ aWhatToShow = nsIDOMNodeFilter::SHOW_ALL;
+ }
+
+ nsCOMPtr<nsINode> root = do_QueryInterface(aRoot);
+ NS_ENSURE_TRUE(root, NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+
+ ErrorResult rv;
+ *_retval = nsIDocument::CreateTreeWalker(*root, aWhatToShow,
+ NodeFilterHolder(aFilter),
+ rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<TreeWalker>
+nsIDocument::CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow,
+ NodeFilter* aFilter,
+ ErrorResult& rv) const
+{
+ return CreateTreeWalker(aRoot, aWhatToShow, NodeFilterHolder(aFilter), rv);
+}
+
+already_AddRefed<TreeWalker>
+nsIDocument::CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow,
+ NodeFilterHolder aFilter, ErrorResult& rv) const
+{
+ nsINode* root = &aRoot;
+ RefPtr<TreeWalker> walker = new TreeWalker(root, aWhatToShow, Move(aFilter));
+ return walker.forget();
+}
+
+
+NS_IMETHODIMP
+nsDocument::GetDefaultView(mozIDOMWindowProxy** aDefaultView)
+{
+ *aDefaultView = nullptr;
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ win.forget(aDefaultView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetLocation(nsIDOMLocation **_retval)
+{
+ *_retval = nsIDocument::GetLocation().take();
+ return NS_OK;
+}
+
+already_AddRefed<Location>
+nsIDocument::GetLocation() const
+{
+ nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(mScriptGlobalObject);
+
+ if (!w) {
+ return nullptr;
+ }
+
+ nsGlobalWindow* window = nsGlobalWindow::Cast(w);
+ ErrorResult dummy;
+ RefPtr<Location> loc = window->GetLocation(dummy);
+ dummy.SuppressException();
+ return loc.forget();
+}
+
+Element*
+nsIDocument::GetHtmlElement() const
+{
+ Element* rootElement = GetRootElement();
+ if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html))
+ return rootElement;
+ return nullptr;
+}
+
+Element*
+nsIDocument::GetHtmlChildElement(nsIAtom* 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;
+}
+
+Element*
+nsDocument::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.
+ RefPtr<nsContentList> list =
+ NS_GetContentList(this, kNameSpaceID_XHTML, NS_LITERAL_STRING("title"));
+
+ nsIContent* first = list->Item(0, false);
+
+ return first ? first->AsElement() : nullptr;
+}
+
+NS_IMETHODIMP
+nsDocument::GetTitle(nsAString& aTitle)
+{
+ nsString title;
+ GetTitle(title);
+ aTitle = title;
+ return NS_OK;
+}
+
+void
+nsDocument::GetTitle(nsString& aTitle)
+{
+ aTitle.Truncate();
+
+ Element* rootElement = GetRootElement();
+ if (!rootElement) {
+ return;
+ }
+
+ nsAutoString tmp;
+
+#ifdef MOZ_XUL
+ if (rootElement->IsXULElement()) {
+ rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp);
+ } else
+#endif
+ {
+ Element* title = GetTitleElement();
+ if (!title) {
+ return;
+ }
+ nsContentUtils::GetNodeTextContent(title, false, tmp);
+ }
+
+ tmp.CompressWhitespace();
+ aTitle = tmp;
+}
+
+NS_IMETHODIMP
+nsDocument::SetTitle(const nsAString& aTitle)
+{
+ Element* rootElement = GetRootElement();
+ if (!rootElement) {
+ return NS_OK;
+ }
+
+#ifdef MOZ_XUL
+ if (rootElement->IsXULElement()) {
+ return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title,
+ aTitle, true);
+ }
+#endif
+
+ // Batch updates so that mutation events don't change "the title
+ // element" under us
+ mozAutoDocUpdate updateBatch(this, UPDATE_CONTENT_MODEL, true);
+
+ nsCOMPtr<Element> title = GetTitleElement();
+ if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
+ if (!title) {
+ RefPtr<mozilla::dom::NodeInfo> titleInfo =
+ mNodeInfoManager->GetNodeInfo(nsGkAtoms::title, nullptr,
+ kNameSpaceID_SVG,
+ nsIDOMNode::ELEMENT_NODE);
+ NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
+ NOT_FROM_PARSER);
+ if (!title) {
+ return NS_OK;
+ }
+ rootElement->InsertChildAt(title, 0, true);
+ }
+ } else if (rootElement->IsHTMLElement()) {
+ if (!title) {
+ Element* head = GetHeadElement();
+ if (!head) {
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> titleInfo;
+ titleInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::title, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+ title = NS_NewHTMLTitleElement(titleInfo.forget());
+ if (!title) {
+ return NS_OK;
+ }
+
+ head->AppendChildTo(title, true);
+ }
+ } else {
+ return NS_OK;
+ }
+
+ return nsContentUtils::SetNodeTextContent(title, aTitle, false);
+}
+
+void
+nsDocument::SetTitle(const nsAString& aTitle, ErrorResult& rv)
+{
+ rv = SetTitle(aTitle);
+}
+
+void
+nsDocument::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;
+
+ RefPtr<nsRunnableMethod<nsDocument, void, false> > event =
+ NewNonOwningRunnableMethod(this,
+ &nsDocument::DoNotifyPossibleTitleChange);
+ nsresult rv = NS_DispatchToCurrentThread(event);
+ if (NS_SUCCEEDED(rv)) {
+ mPendingTitleChangeEvent = event;
+ }
+}
+
+void
+nsDocument::DoNotifyPossibleTitleChange()
+{
+ mPendingTitleChangeEvent.Forget();
+ mHaveFiredTitleChange = true;
+
+ nsAutoString title;
+ GetTitle(title);
+
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ nsCOMPtr<nsISupports> container =
+ shell->GetPresContext()->GetContainerWeak();
+ if (container) {
+ nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container);
+ if (docShellWin) {
+ docShellWin->SetTitle(title.get());
+ }
+ }
+ }
+
+ // Fire a DOM event for the title change.
+ nsContentUtils::DispatchChromeEvent(this, static_cast<nsIDocument*>(this),
+ NS_LITERAL_STRING("DOMTitleChanged"),
+ true, true);
+}
+
+already_AddRefed<BoxObject>
+nsDocument::GetBoxObjectFor(Element* aElement, ErrorResult& aRv)
+{
+ if (!aElement) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ nsIDocument* doc = aElement->OwnerDoc();
+ if (doc != this) {
+ aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+ return nullptr;
+ }
+
+ if (!mHasWarnedAboutBoxObjects && !aElement->IsXULElement()) {
+ mHasWarnedAboutBoxObjects = true;
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("BoxObjects"), this,
+ nsContentUtils::eDOM_PROPERTIES,
+ "UseOfGetBoxObjectForWarning");
+ }
+
+ if (!mBoxObjectTable) {
+ mBoxObjectTable = new nsInterfaceHashtable<nsPtrHashKey<nsIContent>, nsPIBoxObject>(6);
+ } else {
+ nsCOMPtr<nsPIBoxObject> boxObject = mBoxObjectTable->Get(aElement);
+ if (boxObject) {
+ return boxObject.forget().downcast<BoxObject>();
+ }
+ }
+
+ int32_t namespaceID;
+ nsCOMPtr<nsIAtom> tag = BindingManager()->ResolveTag(aElement, &namespaceID);
+
+ nsAutoCString contractID("@mozilla.org/layout/xul-boxobject");
+ if (namespaceID == kNameSpaceID_XUL) {
+ if (tag == nsGkAtoms::browser ||
+ tag == nsGkAtoms::editor ||
+ tag == nsGkAtoms::iframe)
+ contractID += "-container";
+ else if (tag == nsGkAtoms::menu)
+ contractID += "-menu";
+ else if (tag == nsGkAtoms::popup ||
+ tag == nsGkAtoms::menupopup ||
+ tag == nsGkAtoms::panel ||
+ tag == nsGkAtoms::tooltip)
+ contractID += "-popup";
+ else if (tag == nsGkAtoms::tree)
+ contractID += "-tree";
+ else if (tag == nsGkAtoms::listbox)
+ contractID += "-listbox";
+ else if (tag == nsGkAtoms::scrollbox)
+ contractID += "-scrollbox";
+ }
+ contractID += ";1";
+
+ nsCOMPtr<nsPIBoxObject> boxObject(do_CreateInstance(contractID.get()));
+ if (!boxObject) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ boxObject->Init(aElement);
+
+ if (mBoxObjectTable) {
+ mBoxObjectTable->Put(aElement, boxObject.get());
+ }
+
+ return boxObject.forget().downcast<BoxObject>();
+}
+
+void
+nsDocument::ClearBoxObjectFor(nsIContent* aContent)
+{
+ if (mBoxObjectTable) {
+ nsPIBoxObject *boxObject = mBoxObjectTable->GetWeak(aContent);
+ if (boxObject) {
+ boxObject->Clear();
+ mBoxObjectTable->Remove(aContent);
+ }
+ }
+}
+
+already_AddRefed<MediaQueryList>
+nsIDocument::MatchMedia(const nsAString& aMediaQueryList)
+{
+ RefPtr<MediaQueryList> result = new MediaQueryList(this, aMediaQueryList);
+
+ // Insert the new item at the end of the linked list.
+ PR_INSERT_BEFORE(result, &mDOMMediaQueryLists);
+
+ return result.forget();
+}
+
+void
+nsDocument::FlushSkinBindings()
+{
+ BindingManager()->FlushSkinBindings();
+}
+
+nsresult
+nsDocument::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(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders);
+ NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
+ nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsDocument::FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer)
+{
+ mInitializableFrameLoaders.RemoveElement(aLoader);
+ if (mInDestructor) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mFrameLoaderFinalizers.AppendElement(aFinalizer);
+ if (!mFrameLoaderRunner) {
+ mFrameLoaderRunner =
+ NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders);
+ NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY);
+ nsContentUtils::AddScriptRunner(mFrameLoaderRunner);
+ }
+ return NS_OK;
+}
+
+void
+nsDocument::MaybeInitializeFinalizeFrameLoaders()
+{
+ if (mDelayFrameLoaderInitialization || mUpdateNestLevel != 0) {
+ // This method will be recalled when mUpdateNestLevel drops to 0,
+ // or 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(this, &nsDocument::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;
+ mFrameLoaderFinalizers.SwapElements(finalizers);
+ for (uint32_t i = 0; i < length; ++i) {
+ finalizers[i]->Run();
+ }
+ }
+}
+
+void
+nsDocument::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;
+ }
+ }
+}
+
+nsIDocument*
+nsDocument::RequestExternalResource(nsIURI* aURI,
+ nsINode* aRequestingNode,
+ ExternalResourceLoad** aPendingLoad)
+{
+ NS_PRECONDITION(aURI, "Must have a URI");
+ NS_PRECONDITION(aRequestingNode, "Must have a node");
+ if (mDisplayDocument) {
+ return mDisplayDocument->RequestExternalResource(aURI,
+ aRequestingNode,
+ aPendingLoad);
+ }
+
+ return mExternalResourceMap.RequestResource(aURI, aRequestingNode,
+ this, aPendingLoad);
+}
+
+void
+nsDocument::EnumerateExternalResources(nsSubDocEnumFunc aCallback, void* aData)
+{
+ mExternalResourceMap.EnumerateResources(aCallback, aData);
+}
+
+nsSMILAnimationController*
+nsDocument::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 || mLoadedAsInteractiveData)
+ return nullptr;
+
+ mAnimationController = new nsSMILAnimationController(this);
+
+ // If there's a presContext then check the animation mode and pause if
+ // necessary.
+ nsIPresShell *shell = GetShell();
+ if (mAnimationController && shell) {
+ nsPresContext *context = shell->GetPresContext();
+ if (context &&
+ context->ImageAnimationMode() == imgIContainer::kDontAnimMode) {
+ mAnimationController->Pause(nsSMILTimeContainer::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*
+nsDocument::GetOrCreatePendingAnimationTracker()
+{
+ if (!mPendingAnimationTracker) {
+ mPendingAnimationTracker = new PendingAnimationTracker(this);
+ }
+
+ return mPendingAnimationTracker;
+}
+
+/**
+ * Retrieve the "direction" property of the document.
+ *
+ * @lina 01/09/2001
+ */
+NS_IMETHODIMP
+nsDocument::GetDir(nsAString& aDirection)
+{
+ nsIDocument::GetDir(aDirection);
+ return NS_OK;
+}
+
+void
+nsIDocument::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
+ */
+NS_IMETHODIMP
+nsDocument::SetDir(const nsAString& aDirection)
+{
+ nsIDocument::SetDir(aDirection);
+ return NS_OK;
+}
+
+void
+nsIDocument::SetDir(const nsAString& aDirection)
+{
+ Element* rootElement = GetHtmlElement();
+ if (rootElement) {
+ rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir,
+ aDirection, true);
+ }
+}
+
+NS_IMETHODIMP
+nsDocument::GetInputEncoding(nsAString& aInputEncoding)
+{
+ nsIDocument::GetCharacterSet(aInputEncoding);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozSyntheticDocument(bool *aSyntheticDocument)
+{
+ *aSyntheticDocument = mIsSyntheticDocument;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetDocumentURI(nsAString& aDocumentURI)
+{
+ nsString temp;
+ nsresult rv = nsIDocument::GetDocumentURI(temp);
+ aDocumentURI = temp;
+ return rv;
+}
+
+nsresult
+nsIDocument::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
+NS_IMETHODIMP
+nsDocument::GetURL(nsAString& aURL)
+{
+ return GetDocumentURI(aURL);
+}
+
+nsresult
+nsIDocument::GetURL(nsString& aURL) const
+{
+ return GetDocumentURI(aURL);
+}
+
+void
+nsIDocument::GetDocumentURIFromJS(nsString& aDocumentURI, ErrorResult& aRv) const
+{
+ if (!mChromeXHRDocURI || !nsContentUtils::IsCallerChrome()) {
+ aRv = GetDocumentURI(aDocumentURI);
+ return;
+ }
+
+ nsAutoCString uri;
+ nsresult res = mChromeXHRDocURI->GetSpec(uri);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ CopyUTF8toUTF16(uri, aDocumentURI);
+}
+
+nsIURI*
+nsIDocument::GetDocumentURIObject() const
+{
+ if (!mChromeXHRDocURI) {
+ return GetDocumentURI();
+ }
+
+ return mChromeXHRDocURI;
+}
+
+
+// Returns "BackCompat" if we are in quirks mode, "CSS1Compat" if we are
+// in almost standards or full standards mode. See bug 105640. This was
+// implemented to match MSIE's compatMode property.
+NS_IMETHODIMP
+nsDocument::GetCompatMode(nsAString& aCompatMode)
+{
+ nsString temp;
+ nsIDocument::GetCompatMode(temp);
+ aCompatMode = temp;
+ return NS_OK;
+}
+
+void
+nsIDocument::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");
+ }
+}
+
+void
+nsDOMAttributeMap::BlastSubtreeToPieces(nsINode *aNode)
+{
+ if (aNode->IsElement()) {
+ Element *element = aNode->AsElement();
+ const nsDOMAttributeMap *map = element->GetAttributeMap();
+ if (map) {
+ // This non-standard style of iteration is presumably used because some
+ // of the code in the loop body can trigger element removal, which
+ // invalidates the iterator.
+ while (true) {
+ auto iter = map->mAttributeCache.ConstIter();
+ if (iter.Done()) {
+ break;
+ }
+ nsCOMPtr<nsIAttribute> attr = iter.UserData();
+ NS_ASSERTION(attr.get(),
+ "non-nsIAttribute somehow made it into the hashmap?!");
+
+ BlastSubtreeToPieces(attr);
+
+ 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!");
+ }
+ }
+ }
+
+ uint32_t count = aNode->GetChildCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ BlastSubtreeToPieces(aNode->GetFirstChild());
+ aNode->RemoveChildAt(0, false);
+ }
+}
+
+NS_IMETHODIMP
+nsDocument::AdoptNode(nsIDOMNode *aAdoptedNode, nsIDOMNode **aResult)
+{
+ *aResult = nullptr;
+
+ nsCOMPtr<nsINode> adoptedNode = do_QueryInterface(aAdoptedNode);
+ NS_ENSURE_TRUE(adoptedNode, NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ nsINode* result = nsIDocument::AdoptNode(*adoptedNode, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ NS_ADDREF(*aResult = result->AsDOMNode());
+ return NS_OK;
+}
+
+nsINode*
+nsIDocument::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv)
+{
+ nsINode* adoptedNode = &aAdoptedNode;
+
+ // Scope firing mutation events so that we don't carry any state that
+ // might be stale
+ {
+ nsINode* parent = adoptedNode->GetParentNode();
+ if (parent) {
+ nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent,
+ adoptedNode->OwnerDoc());
+ }
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ switch (adoptedNode->NodeType()) {
+ case nsIDOMNode::ATTRIBUTE_NODE:
+ {
+ // Remove from ownerElement.
+ RefPtr<Attr> adoptedAttr = static_cast<Attr*>(adoptedNode);
+
+ nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement(rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ if (ownerElement) {
+ RefPtr<Attr> newAttr =
+ ownerElement->RemoveAttributeNode(*adoptedAttr, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ newAttr.swap(adoptedAttr);
+ }
+
+ break;
+ }
+ case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
+ {
+ if (ShadowRoot::FromNode(adoptedNode)) {
+ rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return nullptr;
+ }
+ MOZ_FALLTHROUGH;
+ }
+ case nsIDOMNode::ELEMENT_NODE:
+ case nsIDOMNode::PROCESSING_INSTRUCTION_NODE:
+ case nsIDOMNode::TEXT_NODE:
+ case nsIDOMNode::CDATA_SECTION_NODE:
+ case nsIDOMNode::COMMENT_NODE:
+ case nsIDOMNode::DOCUMENT_TYPE_NODE:
+ {
+ // Don't allow adopting a node's anonymous subtree out from under it.
+ if (adoptedNode->AsContent()->IsRootOfAnonymousSubtree()) {
+ 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.
+ nsIDocument *doc = this;
+ do {
+ if (nsPIDOMWindowOuter *win = doc->GetWindow()) {
+ nsCOMPtr<nsINode> node = win->GetFrameElementInternal();
+ if (node &&
+ nsContentUtils::ContentIsDescendantOf(node, adoptedNode)) {
+ rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return nullptr;
+ }
+ }
+ } while ((doc = doc->GetParentDocument()));
+
+ // Remove from parent.
+ nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode();
+ if (parent) {
+ int32_t idx = parent->IndexOf(adoptedNode);
+ MOZ_ASSERT(idx >= 0);
+ parent->RemoveChildAt(idx, true);
+ } else {
+ MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc());
+
+ // If we're adopting a node that's not in a document, it might still
+ // have a binding applied. Remove the binding from the element now
+ // that it's getting adopted into a new document.
+ // TODO Fully tear down the binding.
+ adoptedNode->AsContent()->SetXBLBinding(nullptr);
+ }
+
+ break;
+ }
+ case nsIDOMNode::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<nsIDocument> oldDocument = adoptedNode->OwnerDoc();
+ bool sameDocument = oldDocument == this;
+
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> newScope(cx, nullptr);
+ if (!sameDocument) {
+ newScope = GetWrapper();
+ if (!newScope && GetScopeObject() && GetScopeObject()->GetGlobalJSObject()) {
+ // 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.
+ JSAutoCompartment ac(cx, GetScopeObject()->GetGlobalJSObject());
+ JS::Rooted<JS::Value> v(cx);
+ rv = nsContentUtils::WrapNative(cx, this, this, &v,
+ /* aAllowWrapping = */ false);
+ if (rv.Failed())
+ return nullptr;
+ newScope = &v.toObject();
+ }
+ }
+
+ nsCOMArray<nsINode> nodesWithProperties;
+ rv = nsNodeUtils::Adopt(adoptedNode, sameDocument ? nullptr : mNodeInfoManager,
+ newScope, nodesWithProperties);
+ 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);
+
+ if (!sameDocument && oldDocument) {
+ uint32_t count = nodesWithProperties.Count();
+ for (uint32_t j = 0; j < oldDocument->GetPropertyTableCount(); ++j) {
+ for (uint32_t i = 0; i < count; ++i) {
+ // Remove all properties.
+ oldDocument->PropertyTable(j)->
+ DeleteAllPropertiesFor(nodesWithProperties[i]);
+ }
+ }
+ }
+
+ return nullptr;
+ }
+
+ uint32_t count = nodesWithProperties.Count();
+ if (!sameDocument && oldDocument) {
+ for (uint32_t j = 0; j < oldDocument->GetPropertyTableCount(); ++j) {
+ nsPropertyTable *oldTable = oldDocument->PropertyTable(j);
+ nsPropertyTable *newTable = PropertyTable(j);
+ for (uint32_t i = 0; i < count; ++i) {
+ rv = oldTable->TransferOrDeleteAllPropertiesFor(nodesWithProperties[i],
+ newTable);
+ }
+ }
+
+ if (rv.Failed()) {
+ // Disconnect all nodes from their parents.
+ nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode);
+
+ return nullptr;
+ }
+ }
+
+ NS_ASSERTION(adoptedNode->OwnerDoc() == this,
+ "Should still be in the document we just got adopted into");
+
+ return adoptedNode;
+}
+
+nsViewportInfo
+nsDocument::GetViewportInfo(const ScreenIntSize& aDisplaySize)
+{
+ // 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 necessay 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
+ nsPIDOMWindowOuter* win = GetWindow();
+ if (win && win->IsDesktopModeViewport() && !IsAboutPage())
+ {
+ CSSCoord viewportWidth = gfxPrefs::DesktopViewportWidth() / 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,
+ /*allowZoom*/ true);
+ }
+
+ if (!gfxPrefs::MetaViewportEnabled()) {
+ return nsViewportInfo(aDisplaySize,
+ defaultScale,
+ /*allowZoom*/ false);
+ }
+
+ // 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,
+ /*allowZoom*/ true);
+ case Unknown:
+ {
+ nsAutoString viewport;
+ GetHeaderData(nsGkAtoms::viewport, viewport);
+ if (viewport.IsEmpty()) {
+ // 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.
+ nsCOMPtr<nsIDOMDocumentType> docType;
+ nsresult rv = GetDoctype(getter_AddRefs(docType));
+ if (NS_SUCCEEDED(rv) && docType) {
+ nsAutoString docId;
+ rv = docType->GetPublicId(docId);
+ if (NS_SUCCEEDED(rv)) {
+ if ((docId.Find("WAP") != -1) ||
+ (docId.Find("Mobile") != -1) ||
+ (docId.Find("WML") != -1))
+ {
+ // We're making an assumption that the docType can't change here
+ mViewportType = DisplayWidthHeight;
+ return nsViewportInfo(aDisplaySize,
+ defaultScale,
+ /*allowZoom*/true);
+ }
+ }
+ }
+
+ nsAutoString handheldFriendly;
+ GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
+ if (handheldFriendly.EqualsLiteral("true")) {
+ mViewportType = DisplayWidthHeight;
+ return nsViewportInfo(aDisplaySize,
+ defaultScale,
+ /*allowZoom*/true);
+ }
+ }
+
+ nsAutoString minScaleStr;
+ GetHeaderData(nsGkAtoms::viewport_minimum_scale, minScaleStr);
+
+ nsresult errorCode;
+ mScaleMinFloat = LayoutDeviceToScreenScale(minScaleStr.ToFloat(&errorCode));
+
+ if (NS_FAILED(errorCode)) {
+ mScaleMinFloat = kViewportMinScale;
+ }
+
+ mScaleMinFloat = mozilla::clamped(
+ mScaleMinFloat, kViewportMinScale, kViewportMaxScale);
+
+ nsAutoString maxScaleStr;
+ GetHeaderData(nsGkAtoms::viewport_maximum_scale, maxScaleStr);
+
+ // We define a special error code variable for the scale and max scale,
+ // because they are used later (see the width calculations).
+ nsresult scaleMaxErrorCode;
+ mScaleMaxFloat = LayoutDeviceToScreenScale(maxScaleStr.ToFloat(&scaleMaxErrorCode));
+
+ if (NS_FAILED(scaleMaxErrorCode)) {
+ mScaleMaxFloat = kViewportMaxScale;
+ }
+
+ mScaleMaxFloat = mozilla::clamped(
+ mScaleMaxFloat, kViewportMinScale, kViewportMaxScale);
+
+ nsAutoString scaleStr;
+ GetHeaderData(nsGkAtoms::viewport_initial_scale, scaleStr);
+
+ nsresult scaleErrorCode;
+ mScaleFloat = LayoutDeviceToScreenScale(scaleStr.ToFloat(&scaleErrorCode));
+
+ nsAutoString widthStr, heightStr;
+
+ GetHeaderData(nsGkAtoms::viewport_height, heightStr);
+ GetHeaderData(nsGkAtoms::viewport_width, widthStr);
+
+ mAutoSize = false;
+
+ if (widthStr.EqualsLiteral("device-width")) {
+ mAutoSize = true;
+ }
+
+ if (widthStr.IsEmpty() &&
+ (heightStr.EqualsLiteral("device-height") ||
+ (mScaleFloat.scale == 1.0)))
+ {
+ mAutoSize = true;
+ }
+
+ nsresult widthErrorCode, heightErrorCode;
+ mViewportSize.width = widthStr.ToInteger(&widthErrorCode);
+ mViewportSize.height = heightStr.ToInteger(&heightErrorCode);
+
+ // If width or height has not been set to a valid number by this point,
+ // fall back to a default value.
+ mValidWidth = (!widthStr.IsEmpty() && NS_SUCCEEDED(widthErrorCode) && mViewportSize.width > 0);
+ mValidHeight = (!heightStr.IsEmpty() && NS_SUCCEEDED(heightErrorCode) && mViewportSize.height > 0);
+
+ mAllowZoom = true;
+ nsAutoString userScalable;
+ GetHeaderData(nsGkAtoms::viewport_user_scalable, userScalable);
+
+ if ((userScalable.EqualsLiteral("0")) ||
+ (userScalable.EqualsLiteral("no")) ||
+ (userScalable.EqualsLiteral("false"))) {
+ mAllowZoom = false;
+ }
+
+ mScaleStrEmpty = scaleStr.IsEmpty();
+ mWidthStrEmpty = widthStr.IsEmpty();
+ mValidScaleFloat = !scaleStr.IsEmpty() && NS_SUCCEEDED(scaleErrorCode);
+ mValidMaxScale = !maxScaleStr.IsEmpty() && NS_SUCCEEDED(scaleMaxErrorCode);
+
+ mViewportType = Specified;
+ MOZ_FALLTHROUGH;
+ }
+ case Specified:
+ default:
+ LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat;
+ LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat;
+ bool effectiveValidMaxScale = mValidMaxScale;
+ bool effectiveAllowZoom = mAllowZoom;
+ if (gfxPrefs::ForceUserScalable()) {
+ // 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
+ // ForceUserScalable pref. Other codepaths that return nsViewportInfo
+ // instances are all consistent with ForceUserScalable() already.
+ effectiveMinScale = kViewportMinScale;
+ effectiveMaxScale = kViewportMaxScale;
+ effectiveValidMaxScale = true;
+ effectiveAllowZoom = true;
+ }
+
+ CSSSize size = mViewportSize;
+
+ if (!mValidWidth) {
+ if (mValidHeight && !aDisplaySize.IsEmpty()) {
+ size.width = size.height * aDisplaySize.width / aDisplaySize.height;
+ } else {
+ // Stretch CSS pixel size of viewport to keep device pixel size
+ // unchanged after full zoom applied.
+ // See bug 974242.
+ size.width = gfxPrefs::DesktopViewportWidth() / fullZoom;
+ }
+ }
+
+ if (!mValidHeight) {
+ if (!aDisplaySize.IsEmpty()) {
+ size.height = size.width * aDisplaySize.height / aDisplaySize.width;
+ } else {
+ size.height = size.width;
+ }
+ }
+
+ CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale;
+ CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale;
+ CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale;
+
+ if (mAutoSize) {
+ // aDisplaySize is in screen pixels; convert them to CSS pixels for the viewport size.
+ CSSToScreenScale defaultPixelScale = layoutDeviceScale * LayoutDeviceToScreenScale(1.0f);
+ size = ScreenSize(aDisplaySize) / defaultPixelScale;
+ }
+
+ size.width = clamped(size.width, float(kViewportMinSize.width), float(kViewportMaxSize.width));
+
+ // Also recalculate the default zoom, if it wasn't specified in the metadata,
+ // and the width is specified.
+ if (mScaleStrEmpty && !mWidthStrEmpty) {
+ CSSToScreenScale defaultScale(float(aDisplaySize.width) / size.width);
+ scaleFloat = (scaleFloat > defaultScale) ? scaleFloat : defaultScale;
+ }
+
+ size.height = clamped(size.height, float(kViewportMinSize.height), float(kViewportMaxSize.height));
+
+ // 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,
+ mAutoSize, effectiveAllowZoom);
+ }
+}
+
+EventListenerManager*
+nsDocument::GetOrCreateListenerManager()
+{
+ if (!mListenerManager) {
+ mListenerManager =
+ new EventListenerManager(static_cast<EventTarget*>(this));
+ SetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ return mListenerManager;
+}
+
+EventListenerManager*
+nsDocument::GetExistingListenerManager() const
+{
+ return mListenerManager;
+}
+
+nsresult
+nsDocument::PreHandleEvent(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) {
+ nsGlobalWindow* window = nsGlobalWindow::Cast(GetWindow());
+ aVisitor.mParentTarget =
+ window ? window->GetTargetForEventTargetChain() : nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::CreateEvent(const nsAString& aEventType, nsIDOMEvent** aReturn)
+{
+ NS_ENSURE_ARG_POINTER(aReturn);
+ ErrorResult rv;
+ *aReturn = nsIDocument::CreateEvent(aEventType, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<Event>
+nsIDocument::CreateEvent(const nsAString& aEventType, ErrorResult& rv) const
+{
+ nsIPresShell *shell = GetShell();
+
+ nsPresContext *presContext = nullptr;
+
+ if (shell) {
+ // Retrieve the context
+ presContext = shell->GetPresContext();
+ }
+
+ // Create event even without presContext.
+ RefPtr<Event> ev =
+ EventDispatcher::CreateEvent(const_cast<nsIDocument*>(this), presContext,
+ nullptr, aEventType);
+ 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
+nsDocument::FlushPendingNotifications(mozFlushType aType)
+{
+ nsDocumentOnStack dos(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() ||
+ (aType > Flush_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 && (aType == Flush_Content || IsSafeToFlush())) {
+ sink->FlushPendingNotifications(aType);
+ }
+ }
+
+ // Should we be flushing pending binding constructors in here?
+
+ if (aType <= Flush_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. 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.
+ // 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.
+ if (mParentDocument && IsSafeToFlush()) {
+ mozFlushType parentType = aType;
+ if (aType >= Flush_Style)
+ parentType = std::max(Flush_Layout, aType);
+ mParentDocument->FlushPendingNotifications(parentType);
+ }
+
+ // We can optimize away getting our presshell and calling
+ // FlushPendingNotifications on it if we don't need a flush of the sort we're
+ // looking at. The one exception is if mInFlush is true, because in that
+ // case we might have set mNeedStyleFlush and mNeedLayoutFlush to false
+ // already but the presshell hasn't actually done the corresponding work yet.
+ // So if mInFlush and reentering this code, we need to flush the presshell.
+ if (mNeedStyleFlush ||
+ (mNeedLayoutFlush && aType >= Flush_InterruptibleLayout) ||
+ aType >= Flush_Display ||
+ mInFlush) {
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ mNeedStyleFlush = false;
+ mNeedLayoutFlush = mNeedLayoutFlush && (aType < Flush_InterruptibleLayout);
+ // mInFlush is a bitfield, so can't us AutoRestore here. But we
+ // need to keep track of multi-level reentry correctly, so need
+ // to restore the old mInFlush value.
+ bool oldInFlush = mInFlush;
+ mInFlush = true;
+ shell->FlushPendingNotifications(aType);
+ mInFlush = oldInFlush;
+ }
+ }
+}
+
+static bool
+Copy(nsIDocument* aDocument, void* aData)
+{
+ nsTArray<nsCOMPtr<nsIDocument> >* resources =
+ static_cast<nsTArray<nsCOMPtr<nsIDocument> >* >(aData);
+ resources->AppendElement(aDocument);
+ return true;
+}
+
+void
+nsDocument::FlushExternalResources(mozFlushType aType)
+{
+ NS_ASSERTION(aType >= Flush_Style,
+ "should only need to flush for style or higher in external resources");
+ if (GetDisplayDocument()) {
+ return;
+ }
+ nsTArray<nsCOMPtr<nsIDocument> > resources;
+ EnumerateExternalResources(Copy, &resources);
+
+ for (uint32_t i = 0; i < resources.Length(); i++) {
+ resources[i]->FlushPendingNotifications(aType);
+ }
+}
+
+void
+nsDocument::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
+nsDocument::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");
+ }
+ }
+}
+
+bool
+nsDocument::IsScriptEnabled()
+{
+ // If this document is sandboxed without 'allow-scripts'
+ // script is not enabled
+ if (HasScriptsBlockedBySandbox()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(GetInnerWindow());
+ if (!globalObject && mMasterDocument) {
+ globalObject = do_QueryInterface(mMasterDocument->GetInnerWindow());
+ }
+ if (!globalObject || !globalObject->GetGlobalJSObject()) {
+ return false;
+ }
+
+ return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed();
+}
+
+nsRadioGroupStruct*
+nsDocument::GetRadioGroupInternal(const nsAString& aName) const
+{
+#ifdef DEBUG
+ if (IsHTMLDocument()) {
+ nsAutoString lcName;
+ ToLowerCase(aName, lcName);
+ MOZ_ASSERT(aName == lcName);
+ }
+#endif
+
+ nsRadioGroupStruct* radioGroup;
+ if (!mRadioGroups.Get(aName, &radioGroup)) {
+ return nullptr;
+ }
+
+ return radioGroup;
+}
+
+nsRadioGroupStruct*
+nsDocument::GetRadioGroup(const nsAString& aName) const
+{
+ nsAutoString tmKey(aName);
+ if (IsHTMLDocument()) {
+ ToLowerCase(tmKey); //should case-insensitive.
+ }
+
+ return GetRadioGroupInternal(tmKey);
+}
+
+nsRadioGroupStruct*
+nsDocument::GetOrCreateRadioGroup(const nsAString& aName)
+{
+ nsAutoString tmKey(aName);
+ if (IsHTMLDocument()) {
+ ToLowerCase(tmKey); //should case-insensitive.
+ }
+
+ if (nsRadioGroupStruct* radioGroup = GetRadioGroupInternal(tmKey)) {
+ return radioGroup;
+ }
+
+ nsAutoPtr<nsRadioGroupStruct> newRadioGroup(new nsRadioGroupStruct());
+ mRadioGroups.Put(tmKey, newRadioGroup);
+
+ return newRadioGroup.forget();
+}
+
+void
+nsDocument::SetCurrentRadioButton(const nsAString& aName,
+ HTMLInputElement* aRadio)
+{
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+ radioGroup->mSelectedRadioButton = aRadio;
+}
+
+HTMLInputElement*
+nsDocument::GetCurrentRadioButton(const nsAString& aName)
+{
+ return GetOrCreateRadioGroup(aName)->mSelectedRadioButton;
+}
+
+NS_IMETHODIMP
+nsDocument::GetNextRadioButton(const nsAString& aName,
+ const bool aPrevious,
+ HTMLInputElement* aFocusedRadio,
+ HTMLInputElement** aRadioOut)
+{
+ // XXX Can we combine the HTML radio button method impls of
+ // nsDocument and nsHTMLFormControl?
+ // XXX Why is HTML radio button stuff in nsDocument, as
+ // opposed to nsHTMLDocument?
+ *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 = radioGroup->mRadioButtons.Count();
+ RefPtr<HTMLInputElement> radio;
+ do {
+ if (aPrevious) {
+ if (--index < 0) {
+ index = numRadios -1;
+ }
+ }
+ else if (++index >= numRadios) {
+ index = 0;
+ }
+ NS_ASSERTION(static_cast<nsGenericHTMLFormElement*>(radioGroup->mRadioButtons[index])->IsHTMLElement(nsGkAtoms::input),
+ "mRadioButtons holding a non-radio button");
+ radio = static_cast<HTMLInputElement*>(radioGroup->mRadioButtons[index]);
+ } while (radio->Disabled() && radio != currentRadio);
+
+ radio.forget(aRadioOut);
+ return NS_OK;
+}
+
+void
+nsDocument::AddToRadioGroup(const nsAString& aName,
+ nsIFormControl* aRadio)
+{
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+ radioGroup->mRadioButtons.AppendObject(aRadio);
+
+ nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio);
+ NS_ASSERTION(element, "radio controls have to be content elements");
+ if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+ radioGroup->mRequiredRadioCount++;
+ }
+}
+
+void
+nsDocument::RemoveFromRadioGroup(const nsAString& aName,
+ nsIFormControl* aRadio)
+{
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+ radioGroup->mRadioButtons.RemoveObject(aRadio);
+
+ nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio);
+ NS_ASSERTION(element, "radio controls have to be content elements");
+ if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+ NS_ASSERTION(radioGroup->mRequiredRadioCount != 0,
+ "mRequiredRadioCount about to wrap below 0!");
+ radioGroup->mRequiredRadioCount--;
+ }
+}
+
+NS_IMETHODIMP
+nsDocument::WalkRadioGroup(const nsAString& aName,
+ nsIRadioVisitor* aVisitor,
+ bool aFlushContent)
+{
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+
+ for (int i = 0; i < radioGroup->mRadioButtons.Count(); i++) {
+ if (!aVisitor->Visit(radioGroup->mRadioButtons[i])) {
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+uint32_t
+nsDocument::GetRequiredRadioCount(const nsAString& aName) const
+{
+ nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
+ return radioGroup ? radioGroup->mRequiredRadioCount : 0;
+}
+
+void
+nsDocument::RadioRequiredWillChange(const nsAString& aName, bool aRequiredAdded)
+{
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+
+ if (aRequiredAdded) {
+ radioGroup->mRequiredRadioCount++;
+ } else {
+ NS_ASSERTION(radioGroup->mRequiredRadioCount != 0,
+ "mRequiredRadioCount about to wrap below 0!");
+ radioGroup->mRequiredRadioCount--;
+ }
+}
+
+bool
+nsDocument::GetValueMissingState(const nsAString& aName) const
+{
+ nsRadioGroupStruct* radioGroup = GetRadioGroup(aName);
+ return radioGroup && radioGroup->mGroupSuffersFromValueMissing;
+}
+
+void
+nsDocument::SetValueMissingState(const nsAString& aName, bool aValue)
+{
+ nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName);
+ radioGroup->mGroupSuffersFromValueMissing = aValue;
+}
+
+void
+nsDocument::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(NS_LITERAL_CSTRING("last-modified"),
+ tmp);
+
+ if (NS_SUCCEEDED(rv)) {
+ PRTime time;
+ PRStatus st = PR_ParseTimeString(tmp.get(), true, &time);
+ if (st == PR_SUCCESS) {
+ modDate = time;
+ }
+ }
+
+ // The misspelled key 'referer' is as per the HTTP spec
+ rv = httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("referer"),
+ mReferrer);
+
+ static const char *const headers[] = {
+ "default-style",
+ "content-style-type",
+ "content-language",
+ "content-disposition",
+ "refresh",
+ "x-dns-prefetch-control",
+ "x-frame-options",
+ "referrer-policy",
+ // 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()) {
+ nsCOMPtr<nsIAtom> 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);
+ }
+}
+
+already_AddRefed<Element>
+nsDocument::CreateElem(const nsAString& aName, nsIAtom *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
+ // nsDocument::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 nsDocument::CreateElem, "
+ "check caller.");
+#endif
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID,
+ nsIDOMNode::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
+nsDocument::IsSafeToFlush() const
+{
+ nsIPresShell* shell = GetShell();
+ if (!shell)
+ return true;
+
+ return shell->IsSafeToFlush();
+}
+
+void
+nsDocument::Sanitize()
+{
+ // Sanitize the document by resetting all 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(NS_LITERAL_STRING("input"));
+
+ 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::FromContentOrNull(nodes->Item(i));
+ if (!input)
+ continue;
+
+ bool resetValue = false;
+
+ input->GetAttribute(NS_LITERAL_STRING("autocomplete"), value);
+ if (value.LowerCaseEqualsLiteral("off")) {
+ resetValue = true;
+ } else {
+ input->GetType(value);
+ if (value.LowerCaseEqualsLiteral("password"))
+ resetValue = true;
+ }
+
+ if (resetValue) {
+ input->Reset();
+ }
+ }
+
+ // Now locate all _form_ elements that have autocomplete=off and reset them
+ nodes = GetElementsByTagName(NS_LITERAL_STRING("form"));
+
+ length = nodes->Length(true);
+ for (uint32_t i = 0; i < length; ++i) {
+ NS_ASSERTION(nodes->Item(i), "null item in nodelist");
+
+ nsCOMPtr<nsIDOMHTMLFormElement> form = do_QueryInterface(nodes->Item(i));
+ if (!form)
+ continue;
+
+ nodes->Item(i)->AsElement()->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::autocomplete, value);
+ if (value.LowerCaseEqualsLiteral("off"))
+ form->Reset();
+ }
+}
+
+struct SubDocEnumArgs
+{
+ nsIDocument::nsSubDocEnumFunc callback;
+ void *data;
+};
+
+void
+nsDocument::EnumerateSubDocuments(nsSubDocEnumFunc aCallback, void *aData)
+{
+ 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<nsCOMPtr<nsIDocument>, 8> subdocs;
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ nsIDocument* subdoc = entry->mSubDocument;
+ if (subdoc) {
+ subdocs.AppendElement(subdoc);
+ }
+ }
+ for (auto subdoc : subdocs) {
+ if (!aCallback(subdoc, aData)) {
+ break;
+ }
+ }
+}
+
+#ifdef DEBUG_bryner
+#define DEBUG_PAGE_CACHE
+#endif
+
+bool
+nsDocument::CanSavePresentation(nsIRequest *aNewRequest)
+{
+ if (EventHandlingSuppressed()) {
+ return 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()) {
+ return false;
+ }
+
+ // Check our event listener manager for unload/beforeunload listeners.
+ nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject);
+ if (piTarget) {
+ EventListenerManager* manager = piTarget->GetExistingListenerManager();
+ if (manager && manager->HasUnloadListeners()) {
+ return 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) {
+#ifdef DEBUG_PAGE_CACHE
+ nsAutoCString requestName, docSpec;
+ request->GetName(requestName);
+ if (mDocumentURI)
+ mDocumentURI->GetSpec(docSpec);
+
+ printf("document %s has request %s\n",
+ docSpec.get(), requestName.get());
+#endif
+ return false;
+ }
+ }
+ }
+
+ // Check if we have active GetUserMedia use
+ if (MediaManager::Exists() && win &&
+ MediaManager::Get()->IsWindowStillActive(win->WindowID())) {
+ return false;
+ }
+
+#ifdef MOZ_WEBRTC
+ // Check if we have active PeerConnections
+ nsCOMPtr<IPeerConnectionManager> pcManager =
+ do_GetService(IPEERCONNECTION_MANAGER_CONTRACTID);
+
+ if (pcManager && win) {
+ bool active;
+ pcManager->HasActivePeerConnection(win->WindowID(), &active);
+ if (active) {
+ return false;
+ }
+ }
+#endif // MOZ_WEBRTC
+
+ // Don't save presentations for documents containing EME content, so that
+ // CDMs reliably shutdown upon user navigation.
+ if (ContainsEMEContent()) {
+ return false;
+ }
+
+ // Don't save presentations for documents containing MSE content, to
+ // reduce memory usage.
+ if (ContainsMSEContent()) {
+ return false;
+ }
+
+ // Don't save presentation if there are active FlyWeb connections or FlyWeb
+ // servers.
+ FlyWebService* flyWebService = FlyWebService::GetExisting();
+ if (flyWebService && flyWebService->HasConnectionOrServer(win->WindowID())) {
+ return false;
+ }
+
+ if (mSubDocuments) {
+ for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<SubDocMapEntry*>(iter.Get());
+ nsIDocument* subdoc = entry->mSubDocument;
+
+ // The aIgnoreRequest we were passed is only for us, so don't pass it on.
+ bool canCache = subdoc ? subdoc->CanSavePresentation(nullptr) : false;
+ if (!canCache) {
+ return false;
+ }
+ }
+ }
+
+#ifdef MOZ_WEBSPEECH
+ if (win) {
+ auto* globalWindow = nsGlobalWindow::Cast(win);
+ if (globalWindow->HasActiveSpeechSynthesis()) {
+ return false;
+ }
+ }
+#endif
+
+ return true;
+}
+
+void
+nsDocument::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;
+
+ mIsGoingAway = true;
+
+ SetScriptGlobalObject(nullptr);
+ RemovedFromDocShell();
+
+ bool oldVal = mInUnlinkOrDeletion;
+ mInUnlinkOrDeletion = true;
+ uint32_t i, count = mChildren.ChildCount();
+ for (i = 0; i < count; ++i) {
+ mChildren.ChildAt(i)->DestroyContent();
+ }
+ mInUnlinkOrDeletion = oldVal;
+
+ mLayoutHistoryState = nullptr;
+
+ // 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();
+}
+
+void
+nsDocument::RemovedFromDocShell()
+{
+ if (mRemovedFromDocShell)
+ return;
+
+ mRemovedFromDocShell = true;
+ EnumerateActivityObservers(NotifyActivityChanged, nullptr);
+
+ uint32_t i, count = mChildren.ChildCount();
+ for (i = 0; i < count; ++i) {
+ mChildren.ChildAt(i)->SaveSubtreeState();
+ }
+}
+
+already_AddRefed<nsILayoutHistoryState>
+nsDocument::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
+nsDocument::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
+nsDocument::AsyncBlockOnload()
+{
+ while (mAsyncOnloadBlockCount) {
+ --mAsyncOnloadBlockCount;
+ BlockOnload();
+ }
+}
+
+void
+nsDocument::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 (!nsContentUtils::IsSafeToRunScript()) {
+ // Because AddRequest may lead to OnStateChange calls in chrome,
+ // block onload only when there are no script blockers.
+ ++mAsyncOnloadBlockCount;
+ if (mAsyncOnloadBlockCount == 1) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &nsDocument::AsyncBlockOnload));
+ }
+ return;
+ }
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+ if (loadGroup) {
+ loadGroup->AddRequest(mOnloadBlocker, nullptr);
+ }
+ }
+ ++mOnloadBlockCount;
+}
+
+void
+nsDocument::UnblockOnload(bool aFireSync)
+{
+ if (mDisplayDocument) {
+ mDisplayDocument->UnblockOnload(aFireSync);
+ return;
+ }
+
+ if (mOnloadBlockCount == 0 && mAsyncOnloadBlockCount == 0) {
+ NS_NOTREACHED("More UnblockOnload() calls than BlockOnload() calls; dropping call");
+ 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 && mAsyncOnloadBlockCount == 0) {
+ // 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,
+ NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"),
+ false,
+ false);
+ asyncDispatcher->PostDOMEvent();
+ }
+ }
+}
+
+class nsUnblockOnloadEvent : public Runnable {
+public:
+ explicit nsUnblockOnloadEvent(nsDocument* aDoc) : mDoc(aDoc) {}
+ NS_IMETHOD Run() override {
+ mDoc->DoUnblockOnload();
+ return NS_OK;
+ }
+private:
+ RefPtr<nsDocument> mDoc;
+};
+
+void
+nsDocument::PostUnblockOnloadEvent()
+{
+ nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this);
+ nsresult rv = NS_DispatchToCurrentThread(evt);
+ 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
+nsDocument::DoUnblockOnload()
+{
+ NS_PRECONDITION(!mDisplayDocument,
+ "Shouldn't get here for resource document");
+ NS_PRECONDITION(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 (mAsyncOnloadBlockCount != 0) {
+ // We need to wait until the async onload block has been handled.
+ PostUnblockOnloadEvent();
+ }
+
+ // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup
+ // -- it's not ours.
+ if (mScriptGlobalObject) {
+ nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
+ if (loadGroup) {
+ loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
+ }
+ }
+}
+
+nsIContent*
+nsDocument::GetContentInThisDocument(nsIFrame* aFrame) const
+{
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
+ nsIContent* content = f->GetContent();
+ if (!content || content->IsInAnonymousSubtree())
+ 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
+nsDocument::DispatchPageTransition(EventTarget* aDispatchTarget,
+ const nsAString& aType,
+ bool aPersisted)
+{
+ if (!aDispatchTarget) {
+ return;
+ }
+
+ PageTransitionEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ init.mPersisted = aPersisted;
+
+ RefPtr<PageTransitionEvent> event =
+ PageTransitionEvent::Constructor(this, aType, init);
+
+ event->SetTrusted(true);
+ event->SetTarget(this);
+ EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event,
+ nullptr, nullptr);
+}
+
+static bool
+NotifyPageShow(nsIDocument* aDocument, void* aData)
+{
+ const bool* aPersistedPtr = static_cast<const bool*>(aData);
+ aDocument->OnPageShow(*aPersistedPtr, nullptr);
+ return true;
+}
+
+void
+nsDocument::OnPageShow(bool aPersisted,
+ EventTarget* aDispatchStartTarget)
+{
+ mVisible = true;
+
+ EnumerateActivityObservers(NotifyActivityChanged, nullptr);
+ EnumerateExternalResources(NotifyPageShow, &aPersisted);
+
+ Element* root = GetRootElement();
+ if (aPersisted && root) {
+ // Send out notifications that our <link> elements are attached.
+ RefPtr<nsContentList> links = NS_GetContentList(root,
+ kNameSpaceID_XHTML,
+ NS_LITERAL_STRING("link"));
+
+ uint32_t linkCount = links->Length(true);
+ for (uint32_t i = 0; i < linkCount; ++i) {
+ static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded();
+ }
+ }
+
+ // See nsIDocument
+ if (!aDispatchStartTarget) {
+ // Set mIsShowing before firing events, in case those event handlers
+ // move us around.
+ mIsShowing = true;
+ }
+
+ if (mAnimationController) {
+ mAnimationController->OnPageShow();
+ }
+
+ if (aPersisted) {
+ ImageTracker()->SetAnimatingState(true);
+ }
+
+ UpdateVisibilityState();
+
+ nsCOMPtr<EventTarget> target = aDispatchStartTarget;
+ if (!target) {
+ target = do_QueryInterface(GetWindow());
+ }
+
+ // Dispatch observer notification to notify observers page is shown.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal *principal = GetPrincipal();
+ os->NotifyObservers(static_cast<nsIDocument*>(this),
+ nsContentUtils::IsSystemPrincipal(principal) ?
+ "chrome-page-shown" :
+ "content-page-shown",
+ nullptr);
+ }
+
+ DispatchPageTransition(target, NS_LITERAL_STRING("pageshow"), aPersisted);
+}
+
+static bool
+NotifyPageHide(nsIDocument* aDocument, void* aData)
+{
+ const bool* aPersistedPtr = static_cast<const bool*>(aData);
+ aDocument->OnPageHide(*aPersistedPtr, nullptr);
+ return true;
+}
+
+static void
+DispatchCustomEventWithFlush(nsINode* aTarget, const nsAString& aEventType,
+ bool aBubbles, bool aOnlyChromeDispatch)
+{
+ RefPtr<Event> event = NS_NewDOMEvent(aTarget, nullptr, nullptr);
+ event->InitEvent(aEventType, aBubbles, false);
+ event->SetTrusted(true);
+ if (aOnlyChromeDispatch) {
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ }
+ if (nsIPresShell* shell = aTarget->OwnerDoc()->GetShell()) {
+ shell->GetPresContext()->
+ RefreshDriver()->ScheduleEventDispatch(aTarget, event);
+ }
+}
+
+static void
+DispatchFullScreenChange(nsIDocument* aTarget)
+{
+ DispatchCustomEventWithFlush(
+ aTarget, NS_LITERAL_STRING("fullscreenchange"),
+ /* Bubbles */ true, /* OnlyChrome */ false);
+}
+
+static void ClearPendingFullscreenRequests(nsIDocument* aDoc);
+
+void
+nsDocument::OnPageHide(bool aPersisted,
+ EventTarget* aDispatchStartTarget)
+{
+ // 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,
+ NS_LITERAL_STRING("link"));
+
+ uint32_t linkCount = links->Length(true);
+ for (uint32_t i = 0; i < linkCount; ++i) {
+ static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved();
+ }
+ }
+
+ // See nsIDocument
+ if (!aDispatchStartTarget) {
+ // Set mIsShowing before firing events, in case those event handlers
+ // move us around.
+ mIsShowing = false;
+ }
+
+ if (mAnimationController) {
+ mAnimationController->OnPageHide();
+ }
+
+ // We do not stop the animations (bug 1024343)
+ // when the page is refreshing while being dragged out
+ nsDocShell* docShell = mDocumentContainer.get();
+ if (aPersisted && !(docShell && docShell->InFrameSwap())) {
+ ImageTracker()->SetAnimatingState(false);
+ }
+
+ ExitPointerLock();
+
+ // Now send out a PageHide event.
+ nsCOMPtr<EventTarget> target = aDispatchStartTarget;
+ if (!target) {
+ target = do_QueryInterface(GetWindow());
+ }
+
+ // Dispatch observer notification to notify observers page is hidden.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal* principal = GetPrincipal();
+ os->NotifyObservers(static_cast<nsIDocument*>(this),
+ nsContentUtils::IsSystemPrincipal(principal) ?
+ "chrome-page-hidden" :
+ "content-page-hidden",
+ nullptr);
+ }
+
+ {
+ PageUnloadingEventTimeStamp timeStamp(this);
+ DispatchPageTransition(target, NS_LITERAL_STRING("pagehide"), aPersisted);
+ }
+
+ mVisible = false;
+
+ UpdateVisibilityState();
+
+ EnumerateExternalResources(NotifyPageHide, &aPersisted);
+ EnumerateActivityObservers(NotifyActivityChanged, nullptr);
+
+ ClearPendingFullscreenRequests(this);
+ if (GetFullscreenElement()) {
+ // 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 nsDocument::mFullscreenRoot) since we can't
+ // just traverse the doctree to get the root.
+ nsIDocument::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 nsDocument::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();
+
+ // If anyone was listening to this document's state, advertizing the state
+ // change would be the least of the politeness.
+ DispatchFullScreenChange(this);
+ }
+}
+
+void
+nsDocument::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
+nsDocument::MutationEventDispatched(nsINode* aTarget)
+{
+ --mSubtreeModifiedDepth;
+ if (mSubtreeModifiedDepth == 0) {
+ 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 (int32_t i = 0; i < count; ++i) {
+ nsINode* possibleTarget = mSubtreeModifiedTargets[i];
+ nsCOMPtr<nsIContent> content = do_QueryInterface(possibleTarget);
+ if (content && content->ChromeOnlyAccess()) {
+ continue;
+ }
+
+ nsINode* commonAncestor = nullptr;
+ int32_t realTargetCount = realTargets.Count();
+ for (int32_t j = 0; j < realTargetCount; ++j) {
+ commonAncestor =
+ nsContentUtils::GetCommonAncestor(possibleTarget, realTargets[j]);
+ if (commonAncestor) {
+ realTargets.ReplaceObjectAt(commonAncestor, j);
+ break;
+ }
+ }
+ if (!commonAncestor) {
+ realTargets.AppendObject(possibleTarget);
+ }
+ }
+
+ mSubtreeModifiedTargets.Clear();
+
+ int32_t realTargetCount = realTargets.Count();
+ for (int32_t k = 0; k < realTargetCount; ++k) {
+ InternalMutationEvent mutation(true, eLegacySubtreeModified);
+ (new AsyncEventDispatcher(realTargets[k], mutation))->
+ RunDOMEventWhenSafe();
+ }
+ }
+}
+
+void
+nsDocument::AddStyleRelevantLink(Link* aLink)
+{
+ NS_ASSERTION(aLink, "Passing in a null link. Expect crashes RSN!");
+#ifdef DEBUG
+ nsPtrHashKey<Link>* entry = mStyledLinks.GetEntry(aLink);
+ NS_ASSERTION(!entry, "Document already knows about this Link!");
+ mStyledLinksCleared = false;
+#endif
+ (void)mStyledLinks.PutEntry(aLink);
+}
+
+void
+nsDocument::ForgetLink(Link* aLink)
+{
+ NS_ASSERTION(aLink, "Passing in a null link. Expect crashes RSN!");
+#ifdef DEBUG
+ nsPtrHashKey<Link>* entry = mStyledLinks.GetEntry(aLink);
+ NS_ASSERTION(entry || mStyledLinksCleared,
+ "Document knows nothing about this Link!");
+#endif
+ mStyledLinks.RemoveEntry(aLink);
+}
+
+void
+nsDocument::DestroyElementMaps()
+{
+#ifdef DEBUG
+ mStyledLinksCleared = true;
+#endif
+ mStyledLinks.Clear();
+ mIdentifierMap.Clear();
+ ++mExpandoAndGeneration.generation;
+}
+
+void
+nsDocument::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.
+ LinkArray linksToNotify(mStyledLinks.Count());
+ for (auto iter = mStyledLinks.ConstIter(); !iter.Done(); iter.Next()) {
+ linksToNotify.AppendElement(iter.Get()->GetKey());
+ }
+
+ // 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
+nsDocument::CloneDocHelper(nsDocument* clone) const
+{
+ clone->mIsStaticDocument = mCreatingStaticClone;
+
+ // Init document
+ nsresult rv = clone->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCreatingStaticClone) {
+ 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 = nsIDocument::GetDocumentURI();
+ }
+ clone->mChannel = channel;
+ if (uri) {
+ clone->ResetToURI(uri, loadGroup, NodePrincipal());
+ }
+
+ clone->SetContainer(mDocumentContainer);
+ }
+
+ // 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->nsDocument::SetDocumentURI(nsIDocument::GetDocumentURI());
+ clone->SetChromeXHRDocURI(mChromeXHRDocURI);
+ clone->SetPrincipal(NodePrincipal());
+ clone->mDocumentBaseURI = mDocumentBaseURI;
+ clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI);
+
+ // Set scripting object
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ GetScriptHandlingObject(hasHadScriptObject);
+ NS_ENSURE_STATE(scriptObject || !hasHadScriptObject);
+ if (scriptObject) {
+ clone->SetScriptHandlingObject(scriptObject);
+ } else {
+ clone->SetScopeObject(GetScopeObject());
+ }
+ // Make the clone a data document
+ clone->SetLoadedAsData(true);
+
+ // Misc state
+
+ // State from nsIDocument
+ clone->mCharacterSet = mCharacterSet;
+ clone->mCharacterSetSource = mCharacterSetSource;
+ clone->mCompatMode = mCompatMode;
+ clone->mBidiOptions = mBidiOptions;
+ clone->mContentLanguage = mContentLanguage;
+ clone->SetContentTypeInternal(GetContentTypeInternal());
+ clone->mSecurityInfo = mSecurityInfo;
+
+ // State from nsDocument
+ clone->mType = mType;
+ clone->mXMLDeclarationBits = mXMLDeclarationBits;
+ clone->mBaseTarget = mBaseTarget;
+ return NS_OK;
+}
+
+void
+nsDocument::SetReadyStateInternal(ReadyState rs)
+{
+ mReadyState = rs;
+ if (rs == 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.
+ return;
+ }
+ if (mTiming) {
+ switch (rs) {
+ case READYSTATE_LOADING:
+ mTiming->NotifyDOMLoading(nsIDocument::GetDocumentURI());
+ break;
+ case READYSTATE_INTERACTIVE:
+ mTiming->NotifyDOMInteractive(nsIDocument::GetDocumentURI());
+ break;
+ case READYSTATE_COMPLETE:
+ mTiming->NotifyDOMComplete(nsIDocument::GetDocumentURI());
+ break;
+ default:
+ NS_WARNING("Unexpected ReadyState value");
+ break;
+ }
+ }
+ // At the time of loading start, we don't have timing object, record time.
+ if (READYSTATE_LOADING == rs) {
+ mLoadingTimeStamp = mozilla::TimeStamp::Now();
+ }
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"),
+ false, false);
+ asyncDispatcher->RunDOMEventWhenSafe();
+}
+
+NS_IMETHODIMP
+nsDocument::GetReadyState(nsAString& aReadyState)
+{
+ nsIDocument::GetReadyState(aReadyState);
+ return NS_OK;
+}
+
+void
+nsIDocument::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");
+ }
+}
+
+namespace {
+
+struct SuppressArgs
+{
+ nsIDocument::SuppressionType mWhat;
+ uint32_t mIncrease;
+};
+
+} // namespace
+
+static bool
+SuppressEventHandlingInDocument(nsIDocument* aDocument, void* aData)
+{
+ SuppressArgs* args = static_cast<SuppressArgs*>(aData);
+ aDocument->SuppressEventHandling(args->mWhat, args->mIncrease);
+ return true;
+}
+
+void
+nsDocument::SuppressEventHandling(nsIDocument::SuppressionType aWhat,
+ uint32_t aIncrease)
+{
+ if (aWhat == eAnimationsOnly) {
+ mAnimationsPaused += aIncrease;
+ } else {
+ mEventsSuppressed += aIncrease;
+ for (uint32_t i = 0; i < aIncrease; ++i) {
+ ScriptLoader()->AddExecuteBlocker();
+ }
+ }
+
+ UpdateFrameRequestCallbackSchedulingState();
+
+ SuppressArgs args = { aWhat, aIncrease };
+ EnumerateSubDocuments(SuppressEventHandlingInDocument, &args);
+}
+
+static void
+FireOrClearDelayedEvents(nsTArray<nsCOMPtr<nsIDocument> >& aDocuments,
+ bool aFireEvents)
+{
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm)
+ return;
+
+ for (uint32_t i = 0; i < aDocuments.Length(); ++i) {
+ // NB: Don't bother trying to fire delayed events on documents that were
+ // closed before this event ran.
+ if (!aDocuments[i]->EventHandlingSuppressed()) {
+ fm->FireDelayedEvents(aDocuments[i]);
+ nsCOMPtr<nsIPresShell> shell = aDocuments[i]->GetShell();
+ if (shell) {
+ // Only fire events for active documents.
+ bool fire = aFireEvents &&
+ aDocuments[i]->GetInnerWindow() &&
+ aDocuments[i]->GetInnerWindow()->IsCurrentInnerWindow();
+ shell->FireOrClearDelayedEvents(fire);
+ }
+ }
+ }
+}
+
+void
+nsDocument::PreloadPictureOpened()
+{
+ mPreloadPictureDepth++;
+}
+
+void
+nsDocument::PreloadPictureClosed()
+{
+ mPreloadPictureDepth--;
+ if (mPreloadPictureDepth == 0) {
+ mPreloadPictureFoundSource.SetIsVoid(true);
+ } else {
+ MOZ_ASSERT(mPreloadPictureDepth >= 0);
+ }
+}
+
+void
+nsDocument::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, NullString(),
+ aSrcsetAttr, aSizesAttr,
+ aTypeAttr, aMediaAttr,
+ mPreloadPictureFoundSource);
+ if (found && mPreloadPictureFoundSource.IsVoid()) {
+ // Found an empty source, which counts
+ mPreloadPictureFoundSource.SetIsVoid(false);
+ }
+ }
+}
+
+already_AddRefed<nsIURI>
+nsDocument::ResolvePreloadImage(nsIURI *aBaseURI,
+ const nsAString& aSrcAttr,
+ const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr)
+{
+ nsString sourceURL;
+ 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;
+ } else {
+ // Otherwise try to use this <img> as a source
+ HTMLImageElement::SelectSourceForTagWithAttrs(this, false, aSrcAttr,
+ aSrcsetAttr, aSizesAttr,
+ NullString(), NullString(),
+ sourceURL);
+ }
+
+ // 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;
+ }
+
+ // 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
+nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr,
+ ReferrerPolicy aReferrerPolicy)
+{
+ // 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.
+ int16_t blockingStatus;
+ if (nsContentUtils::IsImageInCache(uri, static_cast<nsIDocument *>(this)) ||
+ !nsContentUtils::CanLoadImage(uri, static_cast<nsIDocument *>(this),
+ this, NodePrincipal(), &blockingStatus,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD)) {
+ return;
+ }
+
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ switch (Element::StringToCORSMode(aCrossOriginAttr)) {
+ case CORS_NONE:
+ // Nothing to do
+ break;
+ case CORS_ANONYMOUS:
+ loadFlags |= imgILoader::LOAD_CORS_ANONYMOUS;
+ break;
+ case CORS_USE_CREDENTIALS:
+ loadFlags |= imgILoader::LOAD_CORS_USE_CREDENTIALS;
+ break;
+ default:
+ MOZ_CRASH("Unknown CORS mode!");
+ }
+
+ // Image not in cache - trigger preload
+ RefPtr<imgRequestProxy> request;
+ nsresult rv =
+ nsContentUtils::LoadImage(uri,
+ static_cast<nsINode*>(this),
+ this,
+ NodePrincipal(),
+ mDocumentURI, // uri of document used as referrer
+ aReferrerPolicy,
+ nullptr, // no observer
+ loadFlags,
+ NS_LITERAL_STRING("img"),
+ getter_AddRefs(request),
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD);
+
+ // Pin image-reference to avoid evicting it from the img-cache before
+ // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
+ // unlink
+ if (NS_SUCCEEDED(rv)) {
+ mPreloadingImages.Put(uri, request.forget());
+ }
+}
+
+void
+nsDocument::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode)
+{
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(aOrigURI->Clone(getter_AddRefs(uri)))) {
+ 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) {
+ uri->SetPath(NS_LITERAL_CSTRING("/anonymous"));
+ } else {
+ uri->SetPath(NS_LITERAL_CSTRING("/"));
+ }
+
+ if (mPreloadedPreconnects.Contains(uri)) {
+ return;
+ }
+ mPreloadedPreconnects.Put(uri, true);
+
+ nsCOMPtr<nsISpeculativeConnect>
+ speculator(do_QueryInterface(nsContentUtils::GetIOService()));
+ if (!speculator) {
+ return;
+ }
+
+ if (aCORSMode == CORS_ANONYMOUS) {
+ speculator->SpeculativeAnonymousConnect2(uri, NodePrincipal(), nullptr);
+ } else {
+ speculator->SpeculativeConnect2(uri, NodePrincipal(), nullptr);
+ }
+}
+
+void
+nsDocument::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);
+ }
+ }
+}
+
+EventStates
+nsDocument::GetDocumentState()
+{
+ if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_RTL_LOCALE)) {
+ if (IsDocumentRightToLeft()) {
+ mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
+ }
+ mGotDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE;
+ }
+ if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
+ nsIPresShell* shell = GetShell();
+ if (shell && shell->GetPresContext() &&
+ shell->GetPresContext()->IsTopLevelWindowInactive()) {
+ mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
+ }
+ mGotDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE;
+ }
+ return mDocumentState;
+}
+
+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() {}
+public:
+ NS_IMETHOD
+ StyleSheetLoaded(StyleSheet*, bool, nsresult) override
+ {
+ return NS_OK;
+ }
+ NS_DECL_ISUPPORTS
+};
+NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
+
+} // namespace
+
+void
+nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
+ const nsAString& aCrossOriginAttr,
+ const ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity)
+{
+ // The CSSLoader will retain this object after we return.
+ nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
+
+ // Charset names are always ASCII.
+ CSSLoader()->LoadSheet(uri, true, NodePrincipal(),
+ NS_LossyConvertUTF16toASCII(charset),
+ obs,
+ Element::StringToCORSMode(aCrossOriginAttr),
+ aReferrerPolicy, aIntegrity);
+}
+
+nsresult
+nsDocument::LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
+ RefPtr<mozilla::StyleSheet>* aSheet)
+{
+ css::SheetParsingMode mode =
+ isAgentSheet ? css::eAgentSheetFeatures
+ : css::eAuthorSheetFeatures;
+ return CSSLoader()->LoadSheetSync(uri, mode, isAgentSheet, aSheet);
+}
+
+class nsDelayedEventDispatcher : public Runnable
+{
+public:
+ explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<nsIDocument>>& aDocuments)
+ {
+ mDocuments.SwapElements(aDocuments);
+ }
+ virtual ~nsDelayedEventDispatcher() {}
+
+ NS_IMETHOD Run() override
+ {
+ FireOrClearDelayedEvents(mDocuments, true);
+ return NS_OK;
+ }
+
+private:
+ nsTArray<nsCOMPtr<nsIDocument> > mDocuments;
+};
+
+namespace {
+
+struct UnsuppressArgs
+{
+ explicit UnsuppressArgs(nsIDocument::SuppressionType aWhat)
+ : mWhat(aWhat)
+ {
+ }
+
+ nsIDocument::SuppressionType mWhat;
+ nsTArray<nsCOMPtr<nsIDocument>> mDocs;
+};
+
+} // namespace
+
+static bool
+GetAndUnsuppressSubDocuments(nsIDocument* aDocument,
+ void* aData)
+{
+ UnsuppressArgs* args = static_cast<UnsuppressArgs*>(aData);
+ if (args->mWhat != nsIDocument::eAnimationsOnly &&
+ aDocument->EventHandlingSuppressed() > 0) {
+ static_cast<nsDocument*>(aDocument)->DecreaseEventSuppression();
+ aDocument->ScriptLoader()->RemoveExecuteBlocker();
+ } else if (args->mWhat == nsIDocument::eAnimationsOnly &&
+ aDocument->AnimationsPaused()) {
+ static_cast<nsDocument*>(aDocument)->ResumeAnimations();
+ }
+
+ if (args->mWhat != nsIDocument::eAnimationsOnly) {
+ // No need to remember documents if we only care about animation frames.
+ args->mDocs.AppendElement(aDocument);
+ }
+
+ aDocument->EnumerateSubDocuments(GetAndUnsuppressSubDocuments, aData);
+ return true;
+}
+
+void
+nsDocument::UnsuppressEventHandlingAndFireEvents(nsIDocument::SuppressionType aWhat,
+ bool aFireEvents)
+{
+ UnsuppressArgs args(aWhat);
+ GetAndUnsuppressSubDocuments(this, &args);
+
+ if (aWhat == nsIDocument::eAnimationsOnly) {
+ // No need to fire events if we only care about animations here.
+ return;
+ }
+
+ if (aFireEvents) {
+ NS_DispatchToCurrentThread(new nsDelayedEventDispatcher(args.mDocs));
+ } else {
+ FireOrClearDelayedEvents(args.mDocs, false);
+ }
+}
+
+nsISupports*
+nsDocument::GetCurrentContentSink()
+{
+ return mParser ? mParser->GetContentSink() : nullptr;
+}
+
+nsIDocument*
+nsDocument::GetTemplateContentsOwner()
+{
+ if (!mTemplateContentsOwner) {
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ GetScriptHandlingObject(hasHadScriptObject);
+
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ nsresult rv = NS_NewDOMDocument(getter_AddRefs(domDocument),
+ EmptyString(), // aNamespaceURI
+ EmptyString(), // aQualifiedName
+ nullptr, // aDoctype
+ nsIDocument::GetDocumentURI(),
+ nsIDocument::GetDocBaseURI(),
+ NodePrincipal(),
+ true, // aLoadedAsData
+ scriptObject, // aEventObject
+ DocumentFlavorHTML);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ mTemplateContentsOwner = do_QueryInterface(domDocument);
+ NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr);
+
+ nsDocument* doc = static_cast<nsDocument*>(mTemplateContentsOwner.get());
+
+ if (!scriptObject) {
+ mTemplateContentsOwner->SetScopeObject(GetScopeObject());
+ }
+
+ doc->mHasHadScriptHandlingObject = hasHadScriptObject;
+
+ // Set |doc| as the template contents owner of itself so that
+ // |doc| is the template contents owner of template elements created
+ // by |doc|.
+ doc->mTemplateContentsOwner = doc;
+ }
+
+ return mTemplateContentsOwner;
+}
+
+void
+nsDocument::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
+nsDocument::ScrollToRef()
+{
+ if (mScrolledToRefAlready) {
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ shell->ScrollToAnchor();
+ }
+ return;
+ }
+
+ if (mScrollToRef.IsEmpty()) {
+ return;
+ }
+
+ char* tmpstr = ToNewCString(mScrollToRef);
+ if (!tmpstr) {
+ return;
+ }
+
+ nsUnescape(tmpstr);
+ nsAutoCString unescapedRef;
+ unescapedRef.Assign(tmpstr);
+ free(tmpstr);
+
+ 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(unescapedRef);
+
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ // 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 = shell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ // If UTF-8 URI failed then try to assume the string as a
+ // document's charset.
+
+ if (NS_FAILED(rv)) {
+ const nsACString &docCharset = GetDocumentCharacterSet();
+
+ rv = nsContentUtils::ConvertStringFromEncoding(docCharset,
+ unescapedRef,
+ ref);
+
+ if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
+ rv = shell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
+ }
+ }
+ if (NS_SUCCEEDED(rv)) {
+ mScrolledToRefAlready = true;
+ }
+ }
+}
+
+void
+nsDocument::ResetScrolledToRefAlready()
+{
+ mScrolledToRefAlready = false;
+}
+
+void
+nsDocument::SetChangeScrollPosWhenScrollingToRef(bool aValue)
+{
+ mChangeScrollPosWhenScrollingToRef = aValue;
+}
+
+void
+nsIDocument::RegisterActivityObserver(nsISupports* aSupports)
+{
+ if (!mActivityObservers) {
+ mActivityObservers = new nsTHashtable<nsPtrHashKey<nsISupports> >();
+ }
+ mActivityObservers->PutEntry(aSupports);
+}
+
+bool
+nsIDocument::UnregisterActivityObserver(nsISupports* aSupports)
+{
+ if (!mActivityObservers) {
+ return false;
+ }
+ nsPtrHashKey<nsISupports>* entry = mActivityObservers->GetEntry(aSupports);
+ if (!entry) {
+ return false;
+ }
+ mActivityObservers->RemoveEntry(entry);
+ return true;
+}
+
+void
+nsIDocument::EnumerateActivityObservers(ActivityObserverEnumerator aEnumerator,
+ void* aData)
+{
+ if (!mActivityObservers)
+ return;
+
+ for (auto iter = mActivityObservers->ConstIter(); !iter.Done();
+ iter.Next()) {
+ aEnumerator(iter.Get()->GetKey(), aData);
+ }
+}
+
+void
+nsIDocument::RegisterPendingLinkUpdate(Link* aLink)
+{
+ MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden);
+ mLinksToUpdate.PutEntry(aLink);
+ mHasLinksToUpdate = true;
+}
+
+void
+nsIDocument::UnregisterPendingLinkUpdate(Link* aLink)
+{
+ MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden);
+ if (!mHasLinksToUpdate)
+ return;
+
+ mLinksToUpdate.RemoveEntry(aLink);
+}
+
+void
+nsIDocument::FlushPendingLinkUpdates()
+{
+ MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden);
+ if (!mHasLinksToUpdate)
+ return;
+
+#ifdef DEBUG
+ AutoRestore<bool> saved(mIsLinkUpdateRegistrationsForbidden);
+ mIsLinkUpdateRegistrationsForbidden = true;
+#endif
+ for (auto iter = mLinksToUpdate.ConstIter(); !iter.Done(); iter.Next()) {
+ Link* link = iter.Get()->GetKey();
+ link->GetElement()->UpdateLinkState(link->LinkState());
+ }
+ mLinksToUpdate.Clear();
+ mHasLinksToUpdate = false;
+}
+
+already_AddRefed<nsIDocument>
+nsIDocument::CreateStaticClone(nsIDocShell* aCloneContainer)
+{
+ nsDocument* thisAsDoc = static_cast<nsDocument*>(this);
+ mCreatingStaticClone = true;
+
+ // Make document use different container during cloning.
+ RefPtr<nsDocShell> originalShell = mDocumentContainer.get();
+ SetContainer(static_cast<nsDocShell*>(aCloneContainer));
+ nsCOMPtr<nsIDOMNode> clonedNode;
+ nsresult rv = thisAsDoc->CloneNode(true, 1, getter_AddRefs(clonedNode));
+ SetContainer(originalShell);
+
+ RefPtr<nsDocument> clonedDoc;
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIDocument> tmp = do_QueryInterface(clonedNode);
+ if (tmp) {
+ clonedDoc = static_cast<nsDocument*>(tmp.get());
+ if (IsStaticDocument()) {
+ clonedDoc->mOriginalDocument = mOriginalDocument;
+ } else {
+ clonedDoc->mOriginalDocument = this;
+ }
+
+ clonedDoc->mOriginalDocument->mStaticCloneCount++;
+
+ int32_t sheetsCount = GetNumberOfStyleSheets();
+ for (int32_t i = 0; i < sheetsCount; ++i) {
+ RefPtr<StyleSheet> sheet = GetStyleSheetAt(i);
+ if (sheet) {
+ if (sheet->IsApplicable()) {
+ // XXXheycam Need to make ServoStyleSheet cloning work.
+ if (sheet->IsGecko()) {
+ RefPtr<CSSStyleSheet> clonedSheet =
+ sheet->AsGecko()->Clone(nullptr, nullptr, clonedDoc, nullptr);
+ NS_WARNING_ASSERTION(clonedSheet,
+ "Cloning a stylesheet didn't work!");
+ if (clonedSheet) {
+ clonedDoc->AddStyleSheet(clonedSheet);
+ }
+ } else {
+ NS_ERROR("stylo: ServoStyleSheet doesn't support cloning");
+ }
+ }
+ }
+ }
+
+ // Iterate backwards to maintain order
+ for (StyleSheet* sheet : Reversed(thisAsDoc->mOnDemandBuiltInUASheets)) {
+ if (sheet) {
+ if (sheet->IsApplicable()) {
+ // XXXheycam Need to make ServoStyleSheet cloning work.
+ if (sheet->IsGecko()) {
+ RefPtr<CSSStyleSheet> clonedSheet =
+ sheet->AsGecko()->Clone(nullptr, nullptr, clonedDoc, nullptr);
+ NS_WARNING_ASSERTION(clonedSheet,
+ "Cloning a stylesheet didn't work!");
+ if (clonedSheet) {
+ clonedDoc->AddOnDemandBuiltInUASheet(clonedSheet);
+ }
+ } else {
+ NS_ERROR("stylo: ServoStyleSheet doesn't support cloning");
+ }
+ }
+ }
+ }
+ }
+ }
+ mCreatingStaticClone = false;
+ return clonedDoc.forget();
+}
+
+void
+nsIDocument::UnlinkOriginalDocumentIfStatic()
+{
+ if (IsStaticDocument() && mOriginalDocument) {
+ MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0);
+ mOriginalDocument->mStaticCloneCount--;
+ mOriginalDocument = nullptr;
+ }
+ MOZ_ASSERT(!mOriginalDocument);
+}
+
+nsresult
+nsIDocument::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback,
+ int32_t *aHandle)
+{
+ if (mFrameRequestCallbackCounter == INT32_MAX) {
+ // Can't increment without overflowing; bail out
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ int32_t newHandle = ++mFrameRequestCallbackCounter;
+
+ DebugOnly<FrameRequest*> request =
+ mFrameRequestCallbacks.AppendElement(FrameRequest(aCallback, newHandle));
+ NS_ASSERTION(request, "This is supposed to be infallible!");
+ UpdateFrameRequestCallbackSchedulingState();
+
+ *aHandle = newHandle;
+ return NS_OK;
+}
+
+void
+nsIDocument::CancelFrameRequestCallback(int32_t aHandle)
+{
+ // mFrameRequestCallbacks is stored sorted by handle
+ if (mFrameRequestCallbacks.RemoveElementSorted(aHandle)) {
+ UpdateFrameRequestCallbackSchedulingState();
+ }
+}
+
+nsresult
+nsDocument::GetStateObject(nsIVariant** 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 (!mStateObjectCached && mStateObjectContainer) {
+ AutoJSContext cx;
+ nsIGlobalObject* sgo = GetScopeObject();
+ NS_ENSURE_TRUE(sgo, NS_ERROR_UNEXPECTED);
+ JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject());
+ NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
+ JSAutoCompartment ac(cx, global);
+
+ mStateObjectContainer->
+ DeserializeToVariant(cx, getter_AddRefs(mStateObjectCached));
+ }
+
+ NS_IF_ADDREF(*aState = mStateObjectCached);
+
+ return NS_OK;
+}
+
+nsDOMNavigationTiming*
+nsDocument::GetNavigationTiming() const
+{
+ return mTiming;
+}
+
+nsresult
+nsDocument::SetNavigationTiming(nsDOMNavigationTiming* aTiming)
+{
+ mTiming = aTiming;
+ if (!mLoadingTimeStamp.IsNull() && mTiming) {
+ mTiming->SetDOMLoadingTimeStamp(nsIDocument::GetDocumentURI(), mLoadingTimeStamp);
+ }
+ return NS_OK;
+}
+
+Element*
+nsDocument::FindImageMap(const nsAString& aUseMapValue)
+{
+ if (aUseMapValue.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsAString::const_iterator start, end;
+ aUseMapValue.BeginReading(start);
+ aUseMapValue.EndReading(end);
+
+ int32_t hash = aUseMapValue.FindChar('#');
+ if (hash < 0) {
+ return nullptr;
+ }
+ // aUsemap contains a '#', set start to point right after the '#'
+ start.advance(hash + 1);
+
+ if (start == end) {
+ return nullptr; // aUsemap == "#"
+ }
+
+ const nsAString& mapName = Substring(start, end);
+
+ if (!mImageMaps) {
+ mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map, nsGkAtoms::map);
+ }
+
+ uint32_t i, n = mImageMaps->Length(true);
+ nsString name;
+ for (i = 0; i < n; ++i) {
+ nsIContent* map = mImageMaps->Item(i);
+ if (map->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, mapName,
+ eCaseMatters) ||
+ (map->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name) &&
+ mapName.Equals(name, nsCaseInsensitiveStringComparator()))) {
+ return map->AsElement();
+ }
+ }
+
+ return nullptr;
+}
+
+#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(nsIDocument::DeprecatedOperations aOperation)
+{
+ switch(aOperation) {
+#define DEPRECATED_OPERATION(_op) \
+ case nsIDocument::e##_op: return eUseCounter_##_op;
+#include "nsDeprecatedOperationList.h"
+#undef DEPRECATED_OPERATION
+ default:
+ MOZ_CRASH();
+ }
+}
+
+bool
+nsIDocument::HasWarnedAbout(DeprecatedOperations aOperation) const
+{
+ return mDeprecationWarnedAbout[aOperation];
+}
+
+void
+nsIDocument::WarnOnceAbout(DeprecatedOperations aOperation,
+ bool asError /* = false */) const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasWarnedAbout(aOperation)) {
+ return;
+ }
+ mDeprecationWarnedAbout[aOperation] = true;
+ const_cast<nsIDocument*>(this)->SetDocumentAndPageUseCounter(OperationToUseCounter(aOperation));
+ uint32_t flags = asError ? nsIScriptError::errorFlag
+ : nsIScriptError::warningFlag;
+ nsContentUtils::ReportToConsole(flags,
+ NS_LITERAL_CSTRING("DOM Core"), this,
+ nsContentUtils::eDOM_PROPERTIES,
+ kDeprecationWarnings[aOperation]);
+}
+
+bool
+nsIDocument::HasWarnedAbout(DocumentWarnings aWarning) const
+{
+ return mDocWarningWarnedAbout[aWarning];
+}
+
+void
+nsIDocument::WarnOnceAbout(DocumentWarnings aWarning,
+ bool asError /* = false */,
+ const char16_t **aParams /* = nullptr */,
+ uint32_t aParamsLength /* = 0 */) const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasWarnedAbout(aWarning)) {
+ return;
+ }
+ mDocWarningWarnedAbout[aWarning] = true;
+ uint32_t flags = asError ? nsIScriptError::errorFlag
+ : nsIScriptError::warningFlag;
+ nsContentUtils::ReportToConsole(flags,
+ NS_LITERAL_CSTRING("DOM Core"), this,
+ nsContentUtils::eDOM_PROPERTIES,
+ kDocumentWarnings[aWarning],
+ aParams,
+ aParamsLength);
+}
+
+mozilla::dom::ImageTracker*
+nsIDocument::ImageTracker()
+{
+ if (!mImageTracker) {
+ mImageTracker = new mozilla::dom::ImageTracker;
+ }
+ return mImageTracker;
+}
+
+nsresult
+nsDocument::AddPlugin(nsIObjectLoadingContent* aPlugin)
+{
+ MOZ_ASSERT(aPlugin);
+ if (!mPlugins.PutEntry(aPlugin)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+void
+nsDocument::RemovePlugin(nsIObjectLoadingContent* aPlugin)
+{
+ MOZ_ASSERT(aPlugin);
+ mPlugins.RemoveEntry(aPlugin);
+}
+
+static bool
+AllSubDocumentPluginEnum(nsIDocument* aDocument, void* userArg)
+{
+ nsTArray<nsIObjectLoadingContent*>* plugins =
+ reinterpret_cast< nsTArray<nsIObjectLoadingContent*>* >(userArg);
+ MOZ_ASSERT(plugins);
+ aDocument->GetPlugins(*plugins);
+ return true;
+}
+
+void
+nsDocument::GetPlugins(nsTArray<nsIObjectLoadingContent*>& aPlugins)
+{
+ aPlugins.SetCapacity(aPlugins.Length() + mPlugins.Count());
+ for (auto iter = mPlugins.ConstIter(); !iter.Done(); iter.Next()) {
+ aPlugins.AppendElement(iter.Get()->GetKey());
+ }
+ EnumerateSubDocuments(AllSubDocumentPluginEnum, &aPlugins);
+}
+
+nsresult
+nsDocument::AddResponsiveContent(nsIContent* aContent)
+{
+ MOZ_ASSERT(aContent);
+ MOZ_ASSERT(aContent->IsHTMLElement(nsGkAtoms::img));
+ mResponsiveContent.PutEntry(aContent);
+ return NS_OK;
+}
+
+void
+nsDocument::RemoveResponsiveContent(nsIContent* aContent)
+{
+ MOZ_ASSERT(aContent);
+ mResponsiveContent.RemoveEntry(aContent);
+}
+
+void
+nsDocument::NotifyMediaFeatureValuesChanged()
+{
+ for (auto iter = mResponsiveContent.ConstIter(); !iter.Done();
+ iter.Next()) {
+ nsCOMPtr<nsIContent> content = iter.Get()->GetKey();
+ if (content->IsHTMLElement(nsGkAtoms::img)) {
+ auto* imageElement = static_cast<HTMLImageElement*>(content.get());
+ imageElement->MediaFeatureValuesChanged();
+ }
+ }
+}
+
+already_AddRefed<Touch>
+nsIDocument::CreateTouch(nsGlobalWindow* 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)
+{
+ MOZ_ASSERT_IF(aView, aView->IsInnerWindow());
+ RefPtr<Touch> touch = new Touch(aTarget,
+ aIdentifier,
+ aPageX, aPageY,
+ aScreenX, aScreenY,
+ aClientX, aClientY,
+ aRadiusX, aRadiusY,
+ aRotationAngle,
+ aForce);
+ return touch.forget();
+}
+
+already_AddRefed<TouchList>
+nsIDocument::CreateTouchList()
+{
+ RefPtr<TouchList> retval = new TouchList(ToSupports(this));
+ return retval.forget();
+}
+
+already_AddRefed<TouchList>
+nsIDocument::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>
+nsIDocument::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>
+nsIDocument::CaretPositionFromPoint(float aX, float aY)
+{
+ nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
+ nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
+ nsPoint pt(x, y);
+
+ FlushPendingNotifications(Flush_Layout);
+
+ nsIPresShell *ps = GetShell();
+ if (!ps) {
+ return nullptr;
+ }
+
+ nsIFrame *rootFrame = ps->GetRootFrame();
+
+ // XUL docs, unlike HTML, have no frame tree until everything's done loading
+ if (!rootFrame) {
+ return nullptr;
+ }
+
+ nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, pt,
+ nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC);
+ if (!ptFrame) {
+ return nullptr;
+ }
+
+ // GetContentOffsetsFromPoint requires frame-relative coordinates, so we need
+ // to adjust to frame-relative coordinates before we can perform this call.
+ // It should also not take into account the padding of the frame.
+ nsPoint adjustedPoint = pt - ptFrame->GetOffsetTo(rootFrame);
+
+ nsFrame::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();
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea = do_QueryInterface(nonanon);
+ nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
+ nsNumberControlFrame* numberFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
+ if (textFrame || numberFrame) {
+ // 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();
+}
+
+NS_IMETHODIMP
+nsDocument::CaretPositionFromPoint(float aX, float aY, nsISupports** aCaretPos)
+{
+ NS_ENSURE_ARG_POINTER(aCaretPos);
+ *aCaretPos = nsIDocument::CaretPositionFromPoint(aX, aY).take();
+ return NS_OK;
+}
+
+static bool
+IsPotentiallyScrollable(HTMLBodyElement* aBody)
+{
+ // An element is potentially scrollable if all of the following conditions are
+ // true:
+
+ // The element has an associated CSS layout box.
+ nsIFrame* bodyFrame = aBody->GetPrimaryFrame();
+ if (!bodyFrame) {
+ return false;
+ }
+
+ // The element is not the HTML body element, or it is and the root element's
+ // used value of the overflow-x or overflow-y properties is not visible.
+ MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement());
+ nsIFrame* parentFrame = aBody->GetParent()->GetPrimaryFrame();
+ if (parentFrame &&
+ parentFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE &&
+ parentFrame->StyleDisplay()->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE) {
+ return false;
+ }
+
+ // The element's used value of the overflow-x or overflow-y properties is not
+ // visible.
+ if (bodyFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE &&
+ bodyFrame->StyleDisplay()->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE) {
+ return false;
+ }
+
+ return true;
+}
+
+Element*
+nsIDocument::GetScrollingElement()
+{
+ if (GetCompatibilityMode() == eCompatibility_NavQuirks) {
+ FlushPendingNotifications(Flush_Layout);
+ HTMLBodyElement* body = GetBodyElement();
+ if (body && !IsPotentiallyScrollable(body)) {
+ return body;
+ }
+
+ return nullptr;
+ }
+
+ return GetRootElement();
+}
+
+void
+nsIDocument::ObsoleteSheet(nsIURI *aSheetURI, ErrorResult& rv)
+{
+ nsresult res = CSSLoader()->ObsoleteSheet(aSheetURI);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ }
+}
+
+void
+nsIDocument::ObsoleteSheet(const nsAString& aSheetURI, ErrorResult& rv)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult res = NS_NewURI(getter_AddRefs(uri), aSheetURI);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return;
+ }
+ res = CSSLoader()->ObsoleteSheet(uri);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ }
+}
+
+already_AddRefed<nsIURI>
+nsIDocument::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();
+}
+
+nsIHTMLCollection*
+nsIDocument::Children()
+{
+ if (!mChildrenCollection) {
+ mChildrenCollection = new nsContentList(this, kNameSpaceID_Wildcard,
+ nsGkAtoms::_asterisk,
+ nsGkAtoms::_asterisk,
+ false);
+ }
+
+ return mChildrenCollection;
+}
+
+uint32_t
+nsIDocument::ChildElementCount()
+{
+ return Children()->Length();
+}
+
+namespace mozilla {
+
+// 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(nsIDocument* 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)(nsIDocument* aDoc));
+
+ // Removes the root of a specific document from the manager.
+ static void Remove(nsIDocument* aDoc);
+
+ // Returns true if all roots added to the list have been removed.
+ static bool IsEmpty();
+
+private:
+
+ FullscreenRoots() {
+ MOZ_COUNT_CTOR(FullscreenRoots);
+ }
+ ~FullscreenRoots() {
+ MOZ_COUNT_DTOR(FullscreenRoots);
+ }
+
+ enum {
+ NotFound = uint32_t(-1)
+ };
+ // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound.
+ static uint32_t Find(nsIDocument* aRoot);
+
+ // Returns true if aRoot is in the list of fullscreen roots.
+ static bool Contains(nsIDocument* 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)(nsIDocument* 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);
+ // Call aFunction on all entries.
+ for (uint32_t i = 0; i < roots.Length(); i++) {
+ nsCOMPtr<nsIDocument> 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(nsIDocument* aRoot)
+{
+ return FullscreenRoots::Find(aRoot) != NotFound;
+}
+
+/* static */
+void
+FullscreenRoots::Add(nsIDocument* aDoc)
+{
+ nsCOMPtr<nsIDocument> root = nsContentUtils::GetRootDocument(aDoc);
+ if (!FullscreenRoots::Contains(root)) {
+ if (!sInstance) {
+ sInstance = new FullscreenRoots();
+ }
+ sInstance->mRoots.AppendElement(do_GetWeakReference(root));
+ }
+}
+
+/* static */
+uint32_t
+FullscreenRoots::Find(nsIDocument* aRoot)
+{
+ if (!sInstance) {
+ return NotFound;
+ }
+ nsTArray<nsWeakPtr>& roots = sInstance->mRoots;
+ for (uint32_t i = 0; i < roots.Length(); i++) {
+ nsCOMPtr<nsIDocument> otherRoot(do_QueryReferent(roots[i]));
+ if (otherRoot == aRoot) {
+ return i;
+ }
+ }
+ return NotFound;
+}
+
+/* static */
+void
+FullscreenRoots::Remove(nsIDocument* aDoc)
+{
+ nsCOMPtr<nsIDocument> root = nsContentUtils::GetRootDocument(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;
+}
+
+} // end namespace mozilla.
+using mozilla::FullscreenRoots;
+
+nsIDocument*
+nsDocument::GetFullscreenRoot()
+{
+ nsCOMPtr<nsIDocument> root = do_QueryReferent(mFullscreenRoot);
+ return root;
+}
+
+void
+nsDocument::SetFullscreenRoot(nsIDocument* aRoot)
+{
+ mFullscreenRoot = do_GetWeakReference(aRoot);
+}
+
+NS_IMETHODIMP
+nsDocument::MozCancelFullScreen()
+{
+ nsIDocument::ExitFullscreen();
+ return NS_OK;
+}
+
+void
+nsIDocument::ExitFullscreen()
+{
+ RestorePreviousFullScreenState();
+}
+
+static void
+AskWindowToExitFullscreen(nsIDocument* aDoc)
+{
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ aDoc, ToSupports(aDoc), NS_LITERAL_STRING("MozDOMFullscreen:Exit"),
+ /* Bubbles */ true, /* Cancelable */ false,
+ /* DefaultAction */ nullptr);
+ } else {
+ if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
+ win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false);
+ }
+ }
+}
+
+class nsCallExitFullscreen : public Runnable
+{
+public:
+ explicit nsCallExitFullscreen(nsIDocument* aDoc)
+ : mDoc(aDoc) {}
+
+ NS_IMETHOD Run() override final
+ {
+ if (!mDoc) {
+ FullscreenRoots::ForEach(&AskWindowToExitFullscreen);
+ } else {
+ AskWindowToExitFullscreen(mDoc);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDoc;
+};
+
+/* static */ void
+nsIDocument::AsyncExitFullscreen(nsIDocument* aDoc)
+{
+ NS_DispatchToCurrentThread(new nsCallExitFullscreen(aDoc));
+}
+
+static bool
+CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData)
+{
+ if (aDoc->GetFullscreenElement()) {
+ uint32_t* count = static_cast<uint32_t*>(aData);
+ (*count)++;
+ }
+ return true;
+}
+
+static uint32_t
+CountFullscreenSubDocuments(nsIDocument* aDoc)
+{
+ uint32_t count = 0;
+ aDoc->EnumerateSubDocuments(CountFullscreenSubDocuments, &count);
+ return count;
+}
+
+bool
+nsDocument::IsFullscreenLeaf()
+{
+ // A fullscreen leaf document is fullscreen, and has no fullscreen
+ // subdocuments.
+ if (!GetFullscreenElement()) {
+ return false;
+ }
+ return CountFullscreenSubDocuments(this) == 0;
+}
+
+static bool
+ResetFullScreen(nsIDocument* aDocument, void* aData)
+{
+ if (aDocument->GetFullscreenElement()) {
+ NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
+ "Should have at most 1 fullscreen subdocument.");
+ static_cast<nsDocument*>(aDocument)->CleanupFullscreenState();
+ NS_ASSERTION(!aDocument->GetFullscreenElement(),
+ "Should reset full-screen");
+ auto changed = reinterpret_cast<nsCOMArray<nsIDocument>*>(aData);
+ changed->AppendElement(aDocument);
+ aDocument->EnumerateSubDocuments(ResetFullScreen, aData);
+ }
+ return true;
+}
+
+// Since nsIDocument::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(nsCOMArray<nsIDocument>&& aDocuments)
+ : mDocuments(Move(aDocuments)) { }
+
+ NS_IMETHOD Run() override
+ {
+ // Dispatch MozDOMFullscreen:Exited to the last document in
+ // the list since we want this event to follow the same path
+ // MozDOMFullscreen:Entered dispatched.
+ nsIDocument* lastDocument = mDocuments[mDocuments.Length() - 1];
+ nsContentUtils::DispatchEventOnlyToChrome(
+ lastDocument, ToSupports(lastDocument),
+ NS_LITERAL_STRING("MozDOMFullscreen:Exited"),
+ /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
+ // Ensure the window exits fullscreen.
+ if (nsPIDOMWindowOuter* win = mDocuments[0]->GetWindow()) {
+ win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen, false);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCOMArray<nsIDocument> mDocuments;
+};
+
+/* static */ void
+nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc)
+{
+ MOZ_ASSERT(aMaybeNotARootDoc);
+
+ // Unlock the pointer
+ UnlockPointer();
+
+ nsCOMPtr<nsIDocument> root = aMaybeNotARootDoc->GetFullscreenRoot();
+ if (!root || !root->GetFullscreenElement()) {
+ // 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;
+ }
+
+ // Stores a list of documents to which we must dispatch "fullscreenchange".
+ // We're required by the spec to dispatch the events in leaf-to-root
+ // order when exiting fullscreen, but we traverse the doctree in a
+ // root-to-leaf order, so we save references to the documents we must
+ // dispatch to so that we dispatch in the specified order.
+ nsCOMArray<nsIDocument> changed;
+
+ // Walk the tree of fullscreen documents, and reset their fullscreen state.
+ ResetFullScreen(root, static_cast<void*>(&changed));
+
+ // Dispatch "fullscreenchange" events. Note this loop is in reverse
+ // order so that the events for the leaf document arrives before the root
+ // document, as required by the spec.
+ for (uint32_t i = 0; i < changed.Length(); ++i) {
+ DispatchFullScreenChange(changed[changed.Length() - i - 1]);
+ }
+
+ NS_ASSERTION(!root->GetFullscreenElement(),
+ "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(Move(changed)));
+}
+
+bool
+GetFullscreenLeaf(nsIDocument* aDoc, void* aData)
+{
+ if (aDoc->IsFullscreenLeaf()) {
+ nsIDocument** result = static_cast<nsIDocument**>(aData);
+ *result = aDoc;
+ return false;
+ } else if (aDoc->GetFullscreenElement()) {
+ aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData);
+ }
+ return true;
+}
+
+static nsIDocument*
+GetFullscreenLeaf(nsIDocument* aDoc)
+{
+ nsIDocument* leaf = nullptr;
+ GetFullscreenLeaf(aDoc, &leaf);
+ if (leaf) {
+ 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.
+ nsIDocument* root = nsContentUtils::GetRootDocument(aDoc);
+ // Check that the root is actually fullscreen so we don't waste time walking
+ // around its descendants.
+ if (!root->GetFullscreenElement()) {
+ return nullptr;
+ }
+ GetFullscreenLeaf(root, &leaf);
+ return leaf;
+}
+
+void
+nsDocument::RestorePreviousFullScreenState()
+{
+ NS_ASSERTION(!GetFullscreenElement() || !FullscreenRoots::IsEmpty(),
+ "Should have at least 1 fullscreen root when fullscreen!");
+
+ if (!GetFullscreenElement() || !GetWindow() || FullscreenRoots::IsEmpty()) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> fullScreenDoc = GetFullscreenLeaf(this);
+ AutoTArray<nsDocument*, 8> exitDocs;
+
+ nsIDocument* doc = fullScreenDoc;
+ // Collect all subdocuments.
+ for (; doc != this; doc = doc->GetParentDocument()) {
+ exitDocs.AppendElement(static_cast<nsDocument*>(doc));
+ }
+ MOZ_ASSERT(doc == this, "Must have reached this doc");
+ // Collect all ancestor documents which we are going to change.
+ for (; doc; doc = doc->GetParentDocument()) {
+ nsDocument* theDoc = static_cast<nsDocument*>(doc);
+ MOZ_ASSERT(!theDoc->mFullScreenStack.IsEmpty(),
+ "Ancestor of fullscreen document must also be in fullscreen");
+ if (doc != this) {
+ Element* top = theDoc->FullScreenStackTop();
+ if (top->IsHTMLElement(nsGkAtoms::iframe)) {
+ if (static_cast<HTMLIFrameElement*>(top)->FullscreenFlag()) {
+ // If this is an iframe, and it explicitly requested
+ // fullscreen, don't rollback it automatically.
+ break;
+ }
+ }
+ }
+ exitDocs.AppendElement(theDoc);
+ if (theDoc->mFullScreenStack.Length() > 1) {
+ break;
+ }
+ }
+
+ nsDocument* lastDoc = exitDocs.LastElement();
+ if (!lastDoc->GetParentDocument() &&
+ lastDoc->mFullScreenStack.Length() == 1) {
+ // If we are fully exiting fullscreen, don't touch anything here,
+ // just wait for the window to get out from fullscreen first.
+ AskWindowToExitFullscreen(this);
+ return;
+ }
+
+ // If fullscreen mode is updated the pointer should be unlocked
+ UnlockPointer();
+ // All documents listed in the array except the last one are going to
+ // completely exit from the fullscreen state.
+ for (auto i : MakeRange(exitDocs.Length() - 1)) {
+ exitDocs[i]->CleanupFullscreenState();
+ }
+ // The last document will either rollback one fullscreen element, or
+ // completely exit from the fullscreen state as well.
+ nsIDocument* newFullscreenDoc;
+ if (lastDoc->mFullScreenStack.Length() > 1) {
+ lastDoc->FullScreenStackPop();
+ newFullscreenDoc = lastDoc;
+ } else {
+ lastDoc->CleanupFullscreenState();
+ newFullscreenDoc = lastDoc->GetParentDocument();
+ }
+ // Dispatch the fullscreenchange event to all document listed.
+ for (nsDocument* d : exitDocs) {
+ DispatchFullScreenChange(d);
+ }
+
+ 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.
+ DispatchCustomEventWithFlush(
+ newFullscreenDoc, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"),
+ /* Bubbles */ true, /* ChromeOnly */ true);
+ }
+}
+
+class nsCallRequestFullScreen : public Runnable
+{
+public:
+ explicit nsCallRequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
+ : mRequest(Move(aRequest)) { }
+
+ NS_IMETHOD Run() override
+ {
+ mRequest->GetDocument()->RequestFullScreen(Move(mRequest));
+ return NS_OK;
+ }
+
+ UniquePtr<FullscreenRequest> mRequest;
+};
+
+void
+nsDocument::AsyncRequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
+{
+ if (!aRequest->GetElement()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Must pass non-null element to nsDocument::AsyncRequestFullScreen");
+ return;
+ }
+
+ // Request full-screen asynchronously.
+ nsCOMPtr<nsIRunnable> event(new nsCallRequestFullScreen(Move(aRequest)));
+ NS_DispatchToCurrentThread(event);
+}
+
+void
+nsIDocument::DispatchFullscreenError(const char* aMessage)
+{
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this,
+ NS_LITERAL_STRING("fullscreenerror"),
+ true,
+ false);
+ asyncDispatcher->PostDOMEvent();
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"), this,
+ nsContentUtils::eDOM_PROPERTIES,
+ aMessage);
+}
+
+static void
+UpdateViewportScrollbarOverrideForFullscreen(nsIDocument* aDoc)
+{
+ if (nsIPresShell* presShell = aDoc->GetShell()) {
+ if (nsPresContext* presContext = presShell->GetPresContext()) {
+ presContext->UpdateViewportScrollbarStylesOverride();
+ }
+ }
+}
+
+static void
+ClearFullscreenStateOnElement(Element* aElement)
+{
+ // Remove styles from existing top element.
+ EventStateManager::SetFullScreenState(aElement, false);
+ // Reset iframe fullscreen flag.
+ if (aElement->IsHTMLElement(nsGkAtoms::iframe)) {
+ static_cast<HTMLIFrameElement*>(aElement)->SetFullscreenFlag(false);
+ }
+}
+
+void
+nsDocument::CleanupFullscreenState()
+{
+ // Iterate the fullscreen stack and clear the fullscreen states.
+ // Since we also need to clear the fullscreen-ancestor state, and
+ // currently fullscreen elements can only be placed in hierarchy
+ // order in the stack, reversely iterating the stack could be more
+ // efficient. NOTE that fullscreen-ancestor state would be removed
+ // in bug 1199529, and the elements may not in hierarchy order
+ // after bug 1195213.
+ for (nsWeakPtr& weakPtr : Reversed(mFullScreenStack)) {
+ if (nsCOMPtr<Element> element = do_QueryReferent(weakPtr)) {
+ ClearFullscreenStateOnElement(element);
+ }
+ }
+ mFullScreenStack.Clear();
+ mFullscreenRoot = nullptr;
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+}
+
+bool
+nsDocument::FullScreenStackPush(Element* aElement)
+{
+ NS_ASSERTION(aElement, "Must pass non-null to FullScreenStackPush()");
+ Element* top = FullScreenStackTop();
+ if (top == aElement || !aElement) {
+ return false;
+ }
+ EventStateManager::SetFullScreenState(aElement, true);
+ mFullScreenStack.AppendElement(do_GetWeakReference(aElement));
+ NS_ASSERTION(GetFullscreenElement() == aElement, "Should match");
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+ return true;
+}
+
+void
+nsDocument::FullScreenStackPop()
+{
+ if (mFullScreenStack.IsEmpty()) {
+ return;
+ }
+
+ ClearFullscreenStateOnElement(FullScreenStackTop());
+
+ // Remove top element. Note the remaining top element in the stack
+ // will not have full-screen style bits set, so we will need to restore
+ // them on the new top element before returning.
+ uint32_t last = mFullScreenStack.Length() - 1;
+ mFullScreenStack.RemoveElementAt(last);
+
+ // 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.
+ while (!mFullScreenStack.IsEmpty()) {
+ Element* element = FullScreenStackTop();
+ if (!element || !element->IsInUncomposedDoc() || element->OwnerDoc() != this) {
+ NS_ASSERTION(!element->State().HasState(NS_EVENT_STATE_FULL_SCREEN),
+ "Should have already removed full-screen styles");
+ uint32_t last = mFullScreenStack.Length() - 1;
+ mFullScreenStack.RemoveElementAt(last);
+ } else {
+ // The top element of the stack is now an in-doc element. Return here.
+ break;
+ }
+ }
+
+ UpdateViewportScrollbarOverrideForFullscreen(this);
+}
+
+Element*
+nsDocument::FullScreenStackTop()
+{
+ if (mFullScreenStack.IsEmpty()) {
+ return nullptr;
+ }
+ uint32_t last = mFullScreenStack.Length() - 1;
+ nsCOMPtr<Element> element(do_QueryReferent(mFullScreenStack[last]));
+ NS_ASSERTION(element, "Should have full-screen element!");
+ NS_ASSERTION(element->IsInUncomposedDoc(), "Full-screen element should be in doc");
+ NS_ASSERTION(element->OwnerDoc() == this, "Full-screen element should be in this doc");
+ return element;
+}
+
+/* virtual */ nsTArray<Element*>
+nsDocument::GetFullscreenStack() const
+{
+ nsTArray<Element*> elements;
+ for (const nsWeakPtr& ptr : mFullScreenStack) {
+ if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) {
+ MOZ_ASSERT(elem->State().HasState(NS_EVENT_STATE_FULL_SCREEN));
+ elements.AppendElement(elem);
+ }
+ }
+ return elements;
+}
+
+// Returns true if aDoc is in the focused tab in the active window.
+static bool
+IsInActiveTab(nsIDocument* aDoc)
+{
+ nsCOMPtr<nsIDocShell> docshell = aDoc->GetDocShell();
+ if (!docshell) {
+ return false;
+ }
+
+ bool isActive = false;
+ docshell->GetIsActive(&isActive);
+ if (!isActive) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ docshell->GetRootTreeItem(getter_AddRefs(rootItem));
+ if (!rootItem) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
+ if (!rootWin) {
+ return false;
+ }
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return false;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> activeWindow;
+ fm->GetActiveWindow(getter_AddRefs(activeWindow));
+ if (!activeWindow) {
+ return false;
+ }
+
+ return activeWindow == rootWin;
+}
+
+nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* 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.
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aFrameElement));
+ auto request = MakeUnique<FullscreenRequest>(content->AsElement());
+ request->mIsCallerChrome = false;
+ request->mShouldNotifyNewOrigin = false;
+ RequestFullScreen(Move(request));
+
+ return NS_OK;
+}
+
+nsresult nsDocument::RemoteFrameFullscreenReverted()
+{
+ RestorePreviousFullScreenState();
+ return NS_OK;
+}
+
+/* static */ bool
+nsDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return nsContentUtils::IsCallerChrome() ||
+ nsContentUtils::IsUnprefixedFullscreenApiEnabled();
+}
+
+static bool
+HasFullScreenSubDocument(nsIDocument* 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.
+static const char*
+GetFullscreenError(nsIDocument* aDoc, bool aCallerIsChrome)
+{
+ if (nsContentUtils::IsFullScreenApiEnabled() && aCallerIsChrome) {
+ // Chrome code can always use the full-screen API, provided it's not
+ // explicitly disabled. Note IsCallerChrome() returns true when running
+ // in a Runnable, so don't use GetMozFullScreenEnabled() from a
+ // Runnable!
+ return nullptr;
+ }
+
+ if (!nsContentUtils::IsFullScreenApiEnabled()) {
+ return "FullscreenDeniedDisabled";
+ }
+
+ // Ensure that all containing elements are <iframe> and have
+ // allowfullscreen attribute set.
+ nsCOMPtr<nsIDocShell> docShell(aDoc->GetDocShell());
+ if (!docShell || !docShell->GetFullscreenAllowed()) {
+ return "FullscreenDeniedContainerNotAllowed";
+ }
+ return nullptr;
+}
+
+bool
+nsDocument::FullscreenElementReadyCheck(Element* aElement,
+ bool aWasCallerChrome)
+{
+ NS_ASSERTION(aElement,
+ "Must pass non-null element to nsDocument::RequestFullScreen");
+ if (!aElement || aElement == GetFullscreenElement()) {
+ return false;
+ }
+ if (!aElement->IsInUncomposedDoc()) {
+ DispatchFullscreenError("FullscreenDeniedNotInDocument");
+ return false;
+ }
+ if (aElement->OwnerDoc() != this) {
+ DispatchFullscreenError("FullscreenDeniedMovedDocument");
+ return false;
+ }
+ if (!GetWindow()) {
+ DispatchFullscreenError("FullscreenDeniedLostWindow");
+ return false;
+ }
+ if (const char* msg = GetFullscreenError(this, aWasCallerChrome)) {
+ DispatchFullscreenError(msg);
+ return false;
+ }
+ if (!IsVisible()) {
+ DispatchFullscreenError("FullscreenDeniedHidden");
+ return false;
+ }
+ if (HasFullScreenSubDocument(this)) {
+ DispatchFullscreenError("FullscreenDeniedSubDocFullScreen");
+ return false;
+ }
+ if (GetFullscreenElement() &&
+ !nsContentUtils::ContentIsDescendantOf(aElement, GetFullscreenElement())) {
+ // If this document is full-screen, only grant full-screen requests from
+ // a descendant of the current full-screen element.
+ DispatchFullscreenError("FullscreenDeniedNotDescendant");
+ return false;
+ }
+ if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) {
+ DispatchFullscreenError("FullscreenDeniedNotFocusedTab");
+ return false;
+ }
+ // Deny requests when a windowed plugin is focused.
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ NS_WARNING("Failed to retrieve focus manager in full-screen request.");
+ return false;
+ }
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ fm->GetFocusedElement(getter_AddRefs(focusedElement));
+ if (focusedElement) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(focusedElement);
+ if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(content)) {
+ DispatchFullscreenError("FullscreenDeniedFocusedPlugin");
+ return false;
+ }
+ }
+ return true;
+}
+
+FullscreenRequest::FullscreenRequest(Element* aElement)
+ : mElement(aElement)
+ , mDocument(static_cast<nsDocument*>(aElement->OwnerDoc()))
+{
+ MOZ_COUNT_CTOR(FullscreenRequest);
+}
+
+FullscreenRequest::~FullscreenRequest()
+{
+ MOZ_COUNT_DTOR(FullscreenRequest);
+}
+
+// Any fullscreen request waiting for the widget to finish being full-
+// screen is queued here. This is declared static instead of a member
+// of nsDocument because in the majority of time, there would be at most
+// one document requesting fullscreen. We shouldn't waste the space to
+// hold for it in every document.
+class PendingFullscreenRequestList
+{
+public:
+ static void Add(UniquePtr<FullscreenRequest>&& aRequest)
+ {
+ sList.insertBack(aRequest.release());
+ }
+
+ static const FullscreenRequest* 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
+ };
+
+ class Iterator
+ {
+ public:
+ explicit Iterator(nsIDocument* aDoc, IteratorOption aOption)
+ : mCurrent(PendingFullscreenRequestList::sList.getFirst())
+ , mRootShellForIteration(aDoc->GetDocShell())
+ {
+ if (mCurrent) {
+ if (mRootShellForIteration && aOption == eDocumentsWithSameRoot) {
+ mRootShellForIteration->
+ GetRootTreeItem(getter_AddRefs(mRootShellForIteration));
+ }
+ SkipToNextMatch();
+ }
+ }
+
+ void DeleteAndNext()
+ {
+ DeleteAndNextInternal();
+ SkipToNextMatch();
+ }
+ bool AtEnd() const { return mCurrent == nullptr; }
+ const FullscreenRequest& Get() const { return *mCurrent; }
+
+ private:
+ void DeleteAndNextInternal()
+ {
+ FullscreenRequest* thisRequest = mCurrent;
+ mCurrent = mCurrent->getNext();
+ delete thisRequest;
+ }
+ void SkipToNextMatch()
+ {
+ while (mCurrent) {
+ nsCOMPtr<nsIDocShellTreeItem>
+ docShell = mCurrent->GetDocument()->GetDocShell();
+ if (!docShell) {
+ // Always automatically drop documents which has been
+ // detached from the doc shell.
+ DeleteAndNextInternal();
+ } else {
+ while (docShell && docShell != mRootShellForIteration) {
+ docShell->GetParent(getter_AddRefs(docShell));
+ }
+ if (!docShell) {
+ // We've gone over the root, but haven't find the target
+ // ancestor, so skip this item.
+ mCurrent = mCurrent->getNext();
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ FullscreenRequest* mCurrent;
+ nsCOMPtr<nsIDocShellTreeItem> mRootShellForIteration;
+ };
+
+private:
+ PendingFullscreenRequestList() = delete;
+
+ static LinkedList<FullscreenRequest> sList;
+};
+
+/* static */ LinkedList<FullscreenRequest> PendingFullscreenRequestList::sList;
+
+static nsCOMPtr<nsPIDOMWindowOuter>
+GetRootWindow(nsIDocument* aDoc)
+{
+ nsIDocShell* docShell = aDoc->GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ docShell->GetRootTreeItem(getter_AddRefs(rootItem));
+ return rootItem ? rootItem->GetWindow() : nullptr;
+}
+
+static bool
+ShouldApplyFullscreenDirectly(nsIDocument* aDoc,
+ nsPIDOMWindowOuter* aRootWin)
+{
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ // 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.
+ return !!nsContentUtils::GetRootDocument(aDoc)->GetFullscreenElement();
+ } else {
+ // 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.
+ PendingFullscreenRequestList::Iterator
+ iter(aDoc, PendingFullscreenRequestList::eDocumentsWithSameRoot);
+ if (!iter.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;
+ }
+}
+
+void
+nsDocument::RequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this);
+ if (!rootWin) {
+ return;
+ }
+
+ if (ShouldApplyFullscreenDirectly(this, rootWin)) {
+ ApplyFullscreen(*aRequest);
+ return;
+ }
+
+ // Per spec only HTML, <svg>, and <math> should be allowed, but
+ // we also need to allow XUL elements right now.
+ Element* elem = aRequest->GetElement();
+ if (!elem->IsHTMLElement() && !elem->IsXULElement() &&
+ !elem->IsSVGElement(nsGkAtoms::svg) &&
+ !elem->IsMathMLElement(nsGkAtoms::math)) {
+ DispatchFullscreenError("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(elem, aRequest->mIsCallerChrome)) {
+ return;
+ }
+
+ PendingFullscreenRequestList::Add(Move(aRequest));
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ // If we are not the top level process, dispatch an event to make
+ // our parent process go fullscreen first.
+ nsContentUtils::DispatchEventOnlyToChrome(
+ this, ToSupports(this), NS_LITERAL_STRING("MozDOMFullscreen:Request"),
+ /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
+ } else {
+ // Make the window fullscreen.
+ rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true);
+ }
+}
+
+/* static */ bool
+nsIDocument::HandlePendingFullscreenRequests(nsIDocument* aDoc)
+{
+ bool handled = false;
+ PendingFullscreenRequestList::Iterator iter(
+ aDoc, PendingFullscreenRequestList::eDocumentsWithSameRoot);
+ while (!iter.AtEnd()) {
+ const FullscreenRequest& request = iter.Get();
+ if (request.GetDocument()->ApplyFullscreen(request)) {
+ handled = true;
+ }
+ iter.DeleteAndNext();
+ }
+ return handled;
+}
+
+static void
+ClearPendingFullscreenRequests(nsIDocument* aDoc)
+{
+ PendingFullscreenRequestList::Iterator iter(
+ aDoc, PendingFullscreenRequestList::eInclusiveDescendants);
+ while (!iter.AtEnd()) {
+ iter.DeleteAndNext();
+ }
+}
+
+bool
+nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest)
+{
+ Element* elem = aRequest.GetElement();
+ if (!FullscreenElementReadyCheck(elem, aRequest.mIsCallerChrome)) {
+ return false;
+ }
+
+ // Stash a reference to any existing fullscreen doc, we'll use this later
+ // to detect if the origin which is fullscreen has changed.
+ nsCOMPtr<nsIDocument> 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<nsIDocument*, 8> changed;
+
+ // Remember the root document, so that if a full-screen document is hidden
+ // we can reset full-screen state in the remaining visible full-screen documents.
+ nsIDocument* fullScreenRootDoc = nsContentUtils::GetRootDocument(this);
+
+ // If a document is already in fullscreen, then unlock the mouse pointer
+ // before setting a new document to fullscreen
+ UnlockPointer();
+
+ // Set the full-screen element. This sets the full-screen style on the
+ // element, and the full-screen-ancestor styles on ancestors of the element
+ // in this document.
+ DebugOnly<bool> x = FullScreenStackPush(elem);
+ NS_ASSERTION(x, "Full-screen state of requesting doc should always change!");
+ // Set the iframe fullscreen flag.
+ if (elem->IsHTMLElement(nsGkAtoms::iframe)) {
+ static_cast<HTMLIFrameElement*>(elem)->SetFullscreenFlag(true);
+ }
+ changed.AppendElement(this);
+
+ // Propagate up the document hierarchy, setting the full-screen 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 full-screen element (or its container) is not
+ // visible there. Stop when we reach the root document.
+ nsIDocument* child = this;
+ while (true) {
+ child->SetFullscreenRoot(fullScreenRootDoc);
+ NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc,
+ "Fullscreen root should be set!");
+ if (child == fullScreenRootDoc) {
+ break;
+ }
+ nsIDocument* parent = child->GetParentDocument();
+ Element* element = parent->FindContentForSubDocument(child)->AsElement();
+ if (static_cast<nsDocument*>(parent)->FullScreenStackPush(element)) {
+ changed.AppendElement(parent);
+ child = parent;
+ } else {
+ // We've reached either the root, or a point in the doctree where the
+ // new full-screen element container is the same as the previous
+ // full-screen element's container. No more changes need to be made
+ // to the full-screen stacks of documents further up the tree.
+ break;
+ }
+ }
+
+ 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), NS_LITERAL_STRING("MozDOMFullscreen:Entered"),
+ /* Bubbles */ true, /* Cancelable */ false, /* 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, so we always
+ // take this path on the first entry. 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 &&
+ !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
+ DispatchCustomEventWithFlush(
+ this, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"),
+ /* Bubbles */ true, /* ChromeOnly */ true);
+ }
+
+ // Dispatch "fullscreenchange" events. Note this loop is in reverse
+ // order so that the events for the root document arrives before the leaf
+ // document, as required by the spec.
+ for (uint32_t i = 0; i < changed.Length(); ++i) {
+ DispatchFullScreenChange(changed[changed.Length() - i - 1]);
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozFullScreenElement(nsIDOMElement **aFullScreenElement)
+{
+ Element* el = GetFullscreenElement();
+ nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(el);
+ retval.forget(aFullScreenElement);
+ return NS_OK;
+}
+
+Element*
+nsDocument::GetFullscreenElement()
+{
+ Element* element = FullScreenStackTop();
+ NS_ASSERTION(!element ||
+ element->State().HasState(NS_EVENT_STATE_FULL_SCREEN),
+ "Fullscreen element should have fullscreen styles applied");
+ return element;
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozFullScreen(bool *aFullScreen)
+{
+ *aFullScreen = Fullscreen();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozFullScreenEnabled(bool *aFullScreen)
+{
+ NS_ENSURE_ARG_POINTER(aFullScreen);
+ *aFullScreen = FullscreenEnabled();
+ return NS_OK;
+}
+
+bool
+nsDocument::FullscreenEnabled()
+{
+ return !GetFullscreenError(this, nsContentUtils::IsCallerChrome());
+}
+
+uint16_t
+nsDocument::CurrentOrientationAngle() const
+{
+ return mCurrentOrientationAngle;
+}
+
+OrientationType
+nsDocument::CurrentOrientationType() const
+{
+ return mCurrentOrientationType;
+}
+
+void
+nsDocument::SetCurrentOrientation(mozilla::dom::OrientationType aType,
+ uint16_t aAngle)
+{
+ mCurrentOrientationType = aType;
+ mCurrentOrientationAngle = aAngle;
+}
+
+Promise*
+nsDocument::GetOrientationPendingPromise() const
+{
+ return mOrientationPendingPromise;
+}
+
+void
+nsDocument::SetOrientationPendingPromise(Promise* aPromise)
+{
+ mOrientationPendingPromise = aPromise;
+}
+
+static void
+DispatchPointerLockChange(nsIDocument* aTarget)
+{
+ if (!aTarget) {
+ return;
+ }
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(aTarget,
+ NS_LITERAL_STRING("pointerlockchange"),
+ true,
+ false);
+ asyncDispatcher->PostDOMEvent();
+}
+
+static void
+DispatchPointerLockError(nsIDocument* aTarget, const char* aMessage)
+{
+ if (!aTarget) {
+ return;
+ }
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(aTarget,
+ NS_LITERAL_STRING("pointerlockerror"),
+ true,
+ false);
+ asyncDispatcher->PostDOMEvent();
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"), aTarget,
+ nsContentUtils::eDOM_PROPERTIES,
+ aMessage);
+}
+
+class PointerLockRequest final : public Runnable
+{
+public:
+ PointerLockRequest(Element* aElement, bool aUserInputOrChromeCaller)
+ : mElement(do_GetWeakReference(aElement))
+ , mDocument(do_GetWeakReference(aElement->OwnerDoc()))
+ , mUserInputOrChromeCaller(aUserInputOrChromeCaller)
+ {}
+
+ NS_IMETHOD Run() final;
+
+private:
+ nsWeakPtr mElement;
+ nsWeakPtr mDocument;
+ bool mUserInputOrChromeCaller;
+};
+
+static const char*
+GetPointerLockError(Element* aElement, Element* aCurrentLock,
+ bool aNoFocusCheck = false)
+{
+ // Check if pointer lock pref is enabled
+ if (!Preferences::GetBool("full-screen-api.pointer-lock.enabled")) {
+ return "PointerLockDeniedDisabled";
+ }
+
+ nsCOMPtr<nsIDocument> ownerDoc = aElement->OwnerDoc();
+ if (aCurrentLock && aCurrentLock->OwnerDoc() != ownerDoc) {
+ return "PointerLockDeniedInUse";
+ }
+
+ if (!aElement->IsInUncomposedDoc()) {
+ 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";
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> top = ownerWindow->GetScriptableTop();
+ if (!top || !top->GetExtantDoc() || top->GetExtantDoc()->Hidden()) {
+ return "PointerLockDeniedHidden";
+ }
+
+ if (!aNoFocusCheck) {
+ mozilla::ErrorResult rv;
+ if (!top->GetExtantDoc()->HasFocus(rv)) {
+ return "PointerLockDeniedNotFocused";
+ }
+ }
+
+ return nullptr;
+}
+
+static void
+ChangePointerLockedElement(Element* aElement, nsIDocument* 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->GetUncomposedDoc() == aDocument);
+ aPointerLockedElement->ClearPointerLock();
+ }
+ if (aElement) {
+ MOZ_ASSERT(aElement->GetUncomposedDoc() == aDocument);
+ aElement->SetPointerLock();
+ EventStateManager::sPointerLockedElement = do_GetWeakReference(aElement);
+ EventStateManager::sPointerLockedDoc = do_GetWeakReference(aDocument);
+ NS_ASSERTION(EventStateManager::sPointerLockedElement &&
+ EventStateManager::sPointerLockedDoc,
+ "aElement and this should support weak references!");
+ } else {
+ EventStateManager::sPointerLockedElement = nullptr;
+ EventStateManager::sPointerLockedDoc = nullptr;
+ }
+ // Retarget all events to aElement via capture or
+ // stop retargeting if aElement is nullptr.
+ nsIPresShell::SetCapturingContent(aElement, CAPTURE_POINTERLOCK);
+ DispatchPointerLockChange(aDocument);
+}
+
+NS_IMETHODIMP
+PointerLockRequest::Run()
+{
+ nsCOMPtr<Element> e = do_QueryReferent(mElement);
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
+ nsDocument* d = static_cast<nsDocument*>(doc.get());
+ const char* error = nullptr;
+ if (!e || !d || !e->GetUncomposedDoc()) {
+ error = "PointerLockDeniedNotInDocument";
+ } else if (e->GetUncomposedDoc() != d) {
+ error = "PointerLockDeniedMovedDocument";
+ }
+ if (!error) {
+ nsCOMPtr<Element> pointerLockedElement =
+ do_QueryReferent(EventStateManager::sPointerLockedElement);
+ if (e == pointerLockedElement) {
+ DispatchPointerLockChange(d);
+ return NS_OK;
+ }
+ // Note, we must bypass focus change, so pass true as the last parameter!
+ error = GetPointerLockError(e, pointerLockedElement, true);
+ // Another element in the same document is requesting pointer lock,
+ // just grant it without user input check.
+ if (!error && pointerLockedElement) {
+ ChangePointerLockedElement(e, d, pointerLockedElement);
+ return NS_OK;
+ }
+ }
+ // If it is neither user input initiated, nor requested in fullscreen,
+ // it should be rejected.
+ if (!error && !mUserInputOrChromeCaller && !doc->GetFullscreenElement()) {
+ error = "PointerLockDeniedNotInputDriven";
+ }
+ if (!error && !d->SetPointerLock(e, NS_STYLE_CURSOR_NONE)) {
+ error = "PointerLockDeniedFailedToLock";
+ }
+ if (error) {
+ DispatchPointerLockError(d, error);
+ return NS_OK;
+ }
+
+ ChangePointerLockedElement(e, d, nullptr);
+ nsContentUtils::DispatchEventOnlyToChrome(
+ doc, ToSupports(e), NS_LITERAL_STRING("MozDOMPointerLock:Entered"),
+ /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
+ return NS_OK;
+}
+
+void
+nsDocument::RequestPointerLock(Element* aElement)
+{
+ NS_ASSERTION(aElement,
+ "Must pass non-null element to nsDocument::RequestPointerLock");
+
+ nsCOMPtr<Element> pointerLockedElement =
+ do_QueryReferent(EventStateManager::sPointerLockedElement);
+ if (aElement == pointerLockedElement) {
+ DispatchPointerLockChange(this);
+ return;
+ }
+
+ if (const char* msg = GetPointerLockError(aElement, pointerLockedElement)) {
+ DispatchPointerLockError(this, msg);
+ return;
+ }
+
+ bool userInputOrChromeCaller = EventStateManager::IsHandlingUserInput() ||
+ nsContentUtils::IsCallerChrome();
+ NS_DispatchToMainThread(new PointerLockRequest(aElement,
+ userInputOrChromeCaller));
+}
+
+bool
+nsDocument::SetPointerLock(Element* aElement, int aCursorStyle)
+{
+ MOZ_ASSERT(!aElement || aElement->OwnerDoc() == this,
+ "We should be either unlocking pointer (aElement is nullptr), "
+ "or locking pointer to an element in this document");
+#ifdef DEBUG
+ if (!aElement) {
+ nsCOMPtr<nsIDocument> pointerLockedDoc =
+ do_QueryReferent(EventStateManager::sPointerLockedDoc);
+ MOZ_ASSERT(pointerLockedDoc == this);
+ }
+#endif
+
+ nsIPresShell* shell = GetShell();
+ if (!shell) {
+ NS_WARNING("SetPointerLock(): No PresShell");
+ if (!aElement) {
+ // 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;
+ }
+ nsPresContext* presContext = shell->GetPresContext();
+ if (!presContext) {
+ NS_WARNING("SetPointerLock(): Unable to get PresContext");
+ return false;
+ }
+
+ nsCOMPtr<nsIWidget> widget;
+ nsIFrame* rootFrame = shell->GetRootFrame();
+ if (!NS_WARN_IF(!rootFrame)) {
+ widget = rootFrame->GetNearestWidget();
+ NS_WARNING_ASSERTION(
+ widget,
+ "SetPointerLock(): Unable to find widget in "
+ "shell->GetRootFrame()->GetNearestWidget();");
+ if (aElement && !widget) {
+ return false;
+ }
+ }
+
+ // Hide the cursor and set pointer lock for future mouse events
+ RefPtr<EventStateManager> esm = presContext->EventStateManager();
+ esm->SetCursor(aCursorStyle, nullptr, false,
+ 0.0f, 0.0f, widget, true);
+ EventStateManager::SetPointerLock(widget, aElement);
+
+ return true;
+}
+
+void
+nsDocument::UnlockPointer(nsIDocument* aDoc)
+{
+ if (!EventStateManager::sIsPointerLocked) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> pointerLockedDoc =
+ do_QueryReferent(EventStateManager::sPointerLockedDoc);
+ if (!pointerLockedDoc || (aDoc && aDoc != pointerLockedDoc)) {
+ return;
+ }
+ nsDocument* doc = static_cast<nsDocument*>(pointerLockedDoc.get());
+ if (!doc->SetPointerLock(nullptr, NS_STYLE_CURSOR_AUTO)) {
+ return;
+ }
+
+ nsCOMPtr<Element> pointerLockedElement =
+ do_QueryReferent(EventStateManager::sPointerLockedElement);
+ ChangePointerLockedElement(nullptr, doc, pointerLockedElement);
+
+ nsContentUtils::DispatchEventOnlyToChrome(
+ doc, ToSupports(pointerLockedElement),
+ NS_LITERAL_STRING("MozDOMPointerLock:Exited"),
+ /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
+}
+
+void
+nsIDocument::UnlockPointer(nsIDocument* aDoc)
+{
+ nsDocument::UnlockPointer(aDoc);
+}
+
+NS_IMETHODIMP
+nsDocument::MozExitPointerLock()
+{
+ nsIDocument::ExitPointerLock();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozPointerLockElement(nsIDOMElement** aPointerLockedElement)
+{
+ Element* el = nsIDocument::GetPointerLockElement();
+ nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(el);
+ retval.forget(aPointerLockedElement);
+ return NS_OK;
+}
+
+Element*
+nsIDocument::GetPointerLockElement()
+{
+ nsCOMPtr<Element> pointerLockedElement =
+ do_QueryReferent(EventStateManager::sPointerLockedElement);
+ if (!pointerLockedElement) {
+ return nullptr;
+ }
+
+ // Make sure pointer locked element is in the same document.
+ nsCOMPtr<nsIDocument> pointerLockedDoc =
+ do_QueryReferent(EventStateManager::sPointerLockedDoc);
+ if (pointerLockedDoc != this) {
+ return nullptr;
+ }
+
+ return pointerLockedElement;
+}
+
+nsresult
+nsDocument::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (strcmp("service-worker-get-client", aTopic) == 0) {
+ // No need to generate the ID if it doesn't exist here. The ID being
+ // requested must already be generated in order to passed in as
+ // aSubject.
+ nsString clientId = GetId();
+ if (!clientId.IsEmpty() && clientId.Equals(aData)) {
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_QueryInterface(aSubject);
+ if (ifptr) {
+#ifdef DEBUG
+ nsCOMPtr<nsISupports> value;
+ MOZ_ALWAYS_SUCCEEDS(ifptr->GetData(getter_AddRefs(value)));
+ MOZ_ASSERT(!value);
+#endif
+ ifptr->SetData(static_cast<nsIDocument*>(this));
+ ifptr->SetDataIID(&NS_GET_IID(nsIDocument));
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void
+nsDocument::UpdateVisibilityState()
+{
+ dom::VisibilityState oldState = mVisibilityState;
+ mVisibilityState = GetVisibilityState();
+ if (oldState != mVisibilityState) {
+ nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
+ NS_LITERAL_STRING("visibilitychange"),
+ /* bubbles = */ true,
+ /* cancelable = */ false);
+ EnumerateActivityObservers(NotifyActivityChanged, nullptr);
+ }
+
+ if (mVisibilityState == dom::VisibilityState::Visible) {
+ MaybeActiveMediaComponents();
+ }
+}
+
+VisibilityState
+nsDocument::GetVisibilityState() 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()) {
+
+ // Check if the document is in prerender state.
+ nsCOMPtr<nsIDocShell> docshell = GetDocShell();
+ if (docshell && docshell->GetIsPrerendered()) {
+ return dom::VisibilityState::Prerender;
+ }
+
+ return dom::VisibilityState::Hidden;
+ }
+
+ return dom::VisibilityState::Visible;
+}
+
+/* virtual */ void
+nsDocument::PostVisibilityUpdateEvent()
+{
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &nsDocument::UpdateVisibilityState);
+ NS_DispatchToMainThread(event);
+}
+
+void
+nsDocument::MaybeActiveMediaComponents()
+{
+ if (mEverInForeground) {
+ return;
+ }
+
+ if (!mWindow) {
+ return;
+ }
+
+ mEverInForeground = true;
+ if (GetWindow()->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK) {
+ GetWindow()->SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED);
+ }
+}
+
+NS_IMETHODIMP
+nsDocument::GetHidden(bool* aHidden)
+{
+ *aHidden = Hidden();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetVisibilityState(nsAString& aState)
+{
+ const EnumEntry& entry =
+ VisibilityStateValues::strings[static_cast<int>(mVisibilityState)];
+ aState.AssignASCII(entry.value, entry.length);
+ return NS_OK;
+}
+
+/* virtual */ void
+nsIDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const
+{
+ aWindowSizes->mDOMOtherSize +=
+ nsINode::SizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+
+ if (mPresShell) {
+ mPresShell->AddSizeOfIncludingThis(aWindowSizes->mMallocSizeOf,
+ &aWindowSizes->mArenaStats,
+ &aWindowSizes->mLayoutPresShellSize,
+ &aWindowSizes->mLayoutStyleSetsSize,
+ &aWindowSizes->mLayoutTextRunsSize,
+ &aWindowSizes->mLayoutPresContextSize);
+ }
+
+ aWindowSizes->mPropertyTablesSize +=
+ mPropertyTable.SizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+ for (uint32_t i = 0, count = mExtraPropertyTables.Length();
+ i < count; ++i) {
+ aWindowSizes->mPropertyTablesSize +=
+ mExtraPropertyTables[i]->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+ }
+
+ if (EventListenerManager* elm = GetExistingListenerManager()) {
+ aWindowSizes->mDOMEventListenersCount += elm->ListenerCount();
+ }
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - many!
+}
+
+void
+nsIDocument::DocAddSizeOfIncludingThis(nsWindowSizes* aWindowSizes) const
+{
+ aWindowSizes->mDOMOtherSize += aWindowSizes->mMallocSizeOf(this);
+ DocAddSizeOfExcludingThis(aWindowSizes);
+}
+
+static size_t
+SizeOfOwnedSheetArrayExcludingThis(const nsTArray<RefPtr<StyleSheet>>& aSheets,
+ MallocSizeOf aMallocSizeOf)
+{
+ size_t n = 0;
+ n += aSheets.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (StyleSheet* sheet : aSheets) {
+ if (!sheet->GetOwningDocument()) {
+ // Avoid over-reporting shared sheets.
+ continue;
+ }
+ n += sheet->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
+size_t
+nsDocument::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ // This SizeOfExcludingThis() 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();
+}
+
+void
+nsDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const
+{
+ nsIDocument::DocAddSizeOfExcludingThis(aWindowSizes);
+
+ for (nsIContent* node = nsINode::GetFirstChild();
+ node;
+ node = node->GetNextNode(this))
+ {
+ size_t nodeSize = node->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+ size_t* p;
+
+ switch (node->NodeType()) {
+ case nsIDOMNode::ELEMENT_NODE:
+ p = &aWindowSizes->mDOMElementNodesSize;
+ break;
+ case nsIDOMNode::TEXT_NODE:
+ p = &aWindowSizes->mDOMTextNodesSize;
+ break;
+ case nsIDOMNode::CDATA_SECTION_NODE:
+ p = &aWindowSizes->mDOMCDATANodesSize;
+ break;
+ case nsIDOMNode::COMMENT_NODE:
+ p = &aWindowSizes->mDOMCommentNodesSize;
+ break;
+ default:
+ p = &aWindowSizes->mDOMOtherSize;
+ break;
+ }
+
+ *p += nodeSize;
+
+ if (EventListenerManager* elm = node->GetExistingListenerManager()) {
+ aWindowSizes->mDOMEventListenersCount += elm->ListenerCount();
+ }
+ }
+
+ aWindowSizes->mStyleSheetsSize +=
+ SizeOfOwnedSheetArrayExcludingThis(mStyleSheets,
+ aWindowSizes->mMallocSizeOf);
+ // Note that we do not own the sheets pointed to by mOnDemandBuiltInUASheets
+ // (the nsLayoutStyleSheetCache singleton does).
+ aWindowSizes->mStyleSheetsSize +=
+ mOnDemandBuiltInUASheets.ShallowSizeOfExcludingThis(
+ aWindowSizes->mMallocSizeOf);
+ for (auto& sheetArray : mAdditionalSheets) {
+ aWindowSizes->mStyleSheetsSize +=
+ SizeOfOwnedSheetArrayExcludingThis(sheetArray,
+ aWindowSizes->mMallocSizeOf);
+ }
+ // 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.
+ aWindowSizes->mStyleSheetsSize +=
+ CSSLoader()->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+
+ aWindowSizes->mDOMOtherSize +=
+ mAttrStyleSheet ?
+ mAttrStyleSheet->DOMSizeOfIncludingThis(aWindowSizes->mMallocSizeOf) :
+ 0;
+
+ aWindowSizes->mDOMOtherSize +=
+ mSVGAttrAnimationRuleProcessor ?
+ mSVGAttrAnimationRuleProcessor->DOMSizeOfIncludingThis(
+ aWindowSizes->mMallocSizeOf) :
+ 0;
+
+ aWindowSizes->mDOMOtherSize +=
+ mStyledLinks.ShallowSizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+
+ aWindowSizes->mDOMOtherSize +=
+ mIdentifierMap.SizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - many!
+}
+
+NS_IMETHODIMP
+nsDocument::QuerySelector(const nsAString& aSelector, nsIDOMElement **aReturn)
+{
+ return nsINode::QuerySelector(aSelector, aReturn);
+}
+
+NS_IMETHODIMP
+nsDocument::QuerySelectorAll(const nsAString& aSelector, nsIDOMNodeList **aReturn)
+{
+ return nsINode::QuerySelectorAll(aSelector, aReturn);
+}
+
+already_AddRefed<nsIDocument>
+nsIDocument::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<nsIDOMDocument> document;
+ nsresult res =
+ NS_NewDOMDocument(getter_AddRefs(document),
+ NullString(),
+ EmptyString(),
+ nullptr,
+ uri,
+ uri,
+ prin->GetPrincipal(),
+ true,
+ global,
+ DocumentFlavorPlain);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(document);
+ doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE);
+
+ return doc.forget();
+}
+
+XPathExpression*
+nsIDocument::CreateExpression(const nsAString& aExpression,
+ XPathNSResolver* aResolver,
+ ErrorResult& rv)
+{
+ return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv);
+}
+
+nsINode*
+nsIDocument::CreateNSResolver(nsINode& aNodeResolver)
+{
+ return XPathEvaluator()->CreateNSResolver(aNodeResolver);
+}
+
+already_AddRefed<XPathResult>
+nsIDocument::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);
+}
+
+NS_IMETHODIMP
+nsDocument::Evaluate(const nsAString& aExpression, nsIDOMNode* aContextNode,
+ nsIDOMNode* aResolver, uint16_t aType,
+ nsISupports* aInResult, nsISupports** aResult)
+{
+ return XPathEvaluator()->Evaluate(aExpression, aContextNode, aResolver, aType,
+ aInResult, aResult);
+}
+
+nsIDocument*
+nsIDocument::GetTopLevelContentDocument()
+{
+ nsDocument* parent;
+
+ if (!mLoadedAsData) {
+ parent = static_cast<nsDocument*>(this);
+ } else {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
+ if (!window) {
+ return nullptr;
+ }
+
+ parent = static_cast<nsDocument*>(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;
+ }
+
+ nsIDocument* candidate = parent->GetParentDocument();
+ parent = static_cast<nsDocument*>(candidate);
+ } while (parent);
+
+ return parent;
+}
+
+void
+nsIDocument::PropagateUseCounters(nsIDocument* aParentDocument)
+{
+ MOZ_ASSERT(this != aParentDocument);
+
+ // What really matters here is that our use counters get propagated as
+ // high up in the content document hierarchy as possible. So,
+ // starting with aParentDocument, we need to find the toplevel content
+ // document, and propagate our use counters into its
+ // mChildDocumentUseCounters.
+ nsIDocument* contentParent = aParentDocument->GetTopLevelContentDocument();
+
+ if (!contentParent) {
+ return;
+ }
+
+ contentParent->mChildDocumentUseCounters |= mUseCounters;
+ contentParent->mChildDocumentUseCounters |= mChildDocumentUseCounters;
+}
+
+void
+nsIDocument::SetPageUseCounter(UseCounter aUseCounter)
+{
+ // We want to set the use counter on the "page" that owns us; the definition
+ // of "page" depends on what kind of document we are. See the comments below
+ // for details. In any event, checking all the conditions below is
+ // reasonably expensive, so we cache whether we've notified our owning page.
+ if (mNotifiedPageForUseCounter[aUseCounter]) {
+ return;
+ }
+ mNotifiedPageForUseCounter[aUseCounter] = true;
+
+ if (mDisplayDocument) {
+ // If we are a resource document, we won't have a docshell and so we won't
+ // record any page use counters on this document. Instead, we should
+ // forward it up to the document that loaded us.
+ MOZ_ASSERT(!mDocumentContainer);
+ mDisplayDocument->SetChildDocumentUseCounter(aUseCounter);
+ return;
+ }
+
+ if (IsBeingUsedAsImage()) {
+ // If this is an SVG image document, we also won't have a docshell.
+ MOZ_ASSERT(!mDocumentContainer);
+ return;
+ }
+
+ // We only care about use counters in content. If we're already a toplevel
+ // content document, then we should have already set the use counter on
+ // ourselves, and we are done.
+ nsIDocument* contentParent = GetTopLevelContentDocument();
+ if (!contentParent) {
+ return;
+ }
+
+ if (this == contentParent) {
+ MOZ_ASSERT(GetUseCounter(aUseCounter));
+ return;
+ }
+
+ contentParent->SetChildDocumentUseCounter(aUseCounter);
+}
+
+bool
+nsIDocument::HasScriptsBlockedBySandbox()
+{
+ return mSandboxFlags & SANDBOXED_SCRIPTS;
+}
+
+bool
+nsIDocument::InlineScriptAllowedByCSP()
+{
+ // this function assumes the inline script is parser created
+ // (e.g., before setting attribute(!) event handlers)
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ nsresult rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
+ NS_ENSURE_SUCCESS(rv, true);
+ bool allowsInlineScript = true;
+ if (csp) {
+ nsresult rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
+ EmptyString(), // aNonce
+ true, // aParserCreated
+ EmptyString(), // FIXME get script sample (bug 1314567)
+ 0, // aLineNumber
+ &allowsInlineScript);
+ NS_ENSURE_SUCCESS(rv, true);
+ }
+ return allowsInlineScript;
+}
+
+static bool
+MightBeAboutOrChromeScheme(nsIURI* aURI)
+{
+ MOZ_ASSERT(aURI);
+ bool isAbout = true;
+ bool isChrome = true;
+ aURI->SchemeIs("about", &isAbout);
+ aURI->SchemeIs("chrome", &isChrome);
+ return isAbout || isChrome;
+}
+
+void
+nsDocument::ReportUseCounters()
+{
+ static const bool sDebugUseCounters = false;
+ if (mReportedUseCounters) {
+ return;
+ }
+
+ mReportedUseCounters = true;
+
+ if (Telemetry::HistogramUseCounterCount > 0 &&
+ (IsContentDocument() || IsResourceDoc())) {
+ nsCOMPtr<nsIURI> uri;
+ NodePrincipal()->GetURI(getter_AddRefs(uri));
+ if (!uri || MightBeAboutOrChromeScheme(uri)) {
+ return;
+ }
+
+ if (sDebugUseCounters) {
+ nsCString spec = uri->GetSpecOrDefault();
+
+ // URIs can be rather long for data documents, so truncate them to
+ // some reasonable length.
+ spec.Truncate(std::min(128U, spec.Length()));
+ printf("-- Use counters for %s --\n", spec.get());
+ }
+
+ // 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.
+ Telemetry::Accumulate(Telemetry::CONTENT_DOCUMENTS_DESTROYED, 1);
+ if (IsTopLevelContentDocument()) {
+ Telemetry::Accumulate(Telemetry::TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED, 1);
+ }
+
+ for (int32_t c = 0;
+ c < eUseCounter_Count; ++c) {
+ UseCounter uc = static_cast<UseCounter>(c);
+
+ Telemetry::ID id =
+ static_cast<Telemetry::ID>(Telemetry::HistogramFirstUseCounter + uc * 2);
+ bool value = GetUseCounter(uc);
+
+ if (value) {
+ if (sDebugUseCounters) {
+ const char* name = Telemetry::GetHistogramName(id);
+ if (name) {
+ printf(" %s", name);
+ } else {
+ printf(" #%d", id);
+ }
+ printf(": %d\n", value);
+ }
+
+ Telemetry::Accumulate(id, 1);
+ }
+
+ if (IsTopLevelContentDocument()) {
+ id = static_cast<Telemetry::ID>(Telemetry::HistogramFirstUseCounter +
+ uc * 2 + 1);
+ value = GetUseCounter(uc) || GetChildDocumentUseCounter(uc);
+
+ if (value) {
+ if (sDebugUseCounters) {
+ const char* name = Telemetry::GetHistogramName(id);
+ if (name) {
+ printf(" %s", name);
+ } else {
+ printf(" #%d", id);
+ }
+ printf(": %d\n", value);
+ }
+
+ Telemetry::Accumulate(id, 1);
+ }
+ }
+ }
+ }
+}
+
+void
+nsDocument::AddIntersectionObserver(DOMIntersectionObserver* aObserver)
+{
+ NS_ASSERTION(mIntersectionObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
+ "Intersection observer already in the list");
+ mIntersectionObservers.AppendElement(aObserver);
+}
+
+void
+nsDocument::RemoveIntersectionObserver(DOMIntersectionObserver* aObserver)
+{
+ mIntersectionObservers.RemoveElement(aObserver);
+}
+
+void
+nsDocument::UpdateIntersectionObservations()
+{
+ DOMHighResTimeStamp time = 0;
+ if (nsPIDOMWindowInner* window = GetInnerWindow()) {
+ Performance* perf = window->GetPerformance();
+ if (perf) {
+ time = perf->Now();
+ }
+ }
+ for (const auto& observer : mIntersectionObservers) {
+ observer->Update(this, time);
+ }
+}
+
+void
+nsDocument::ScheduleIntersectionObserverNotification()
+{
+ nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(this,
+ &nsDocument::NotifyIntersectionObservers);
+ NS_DispatchToCurrentThread(notification);
+}
+
+void
+nsDocument::NotifyIntersectionObservers()
+{
+ for (const auto& observer : mIntersectionObservers) {
+ observer->Notify();
+ }
+}
+
+static bool
+NotifyLayerManagerRecreatedCallback(nsIDocument* aDocument, void* aData)
+{
+ aDocument->NotifyLayerManagerRecreated();
+ return true;
+}
+
+void
+nsDocument::NotifyLayerManagerRecreated()
+{
+ EnumerateActivityObservers(NotifyActivityChanged, nullptr);
+ EnumerateSubDocuments(NotifyLayerManagerRecreatedCallback, nullptr);
+}
+
+XPathEvaluator*
+nsIDocument::XPathEvaluator()
+{
+ if (!mXPathEvaluator) {
+ mXPathEvaluator = new dom::XPathEvaluator(this);
+ }
+ return mXPathEvaluator;
+}
+
+already_AddRefed<nsIDocumentEncoder>
+nsIDocument::GetCachedEncoder()
+{
+ return mCachedEncoder.forget();
+}
+
+void
+nsIDocument::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder)
+{
+ mCachedEncoder = aEncoder;
+}
+
+void
+nsIDocument::SetContentTypeInternal(const nsACString& aType)
+{
+ if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None &&
+ aType.EqualsLiteral("application/xhtml+xml")) {
+ mDefaultElementType = kNameSpaceID_XHTML;
+ }
+
+ mCachedEncoder = nullptr;
+ mContentType = aType;
+}
+
+nsILoadContext*
+nsIDocument::GetLoadContext() const
+{
+ return mDocumentContainer;
+}
+
+nsIDocShell*
+nsIDocument::GetDocShell() const
+{
+ return mDocumentContainer;
+}
+
+void
+nsIDocument::SetStateObject(nsIStructuredCloneContainer *scContainer)
+{
+ mStateObjectContainer = scContainer;
+ mStateObjectCached = nullptr;
+}
+
+already_AddRefed<Element>
+nsIDocument::CreateHTMLElement(nsIAtom* aTag)
+{
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML,
+ nsIDOMNode::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();
+}
+
+/* static */
+nsresult
+nsIDocument::GenerateDocumentId(nsAString& aId)
+{
+ nsID id;
+ nsresult rv = nsContentUtils::GenerateUUIDInPlace(id);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
+ char buffer[NSID_LENGTH];
+ id.ToProvidedString(buffer);
+ NS_ConvertASCIItoUTF16 uuid(buffer);
+
+ // Remove {} and the null terminator
+ aId.Assign(Substring(uuid, 1, NSID_LENGTH - 3));
+ return NS_OK;
+}
+
+nsresult
+nsIDocument::GetOrCreateId(nsAString& aId)
+{
+ if (mId.IsEmpty()) {
+ nsresult rv = GenerateDocumentId(mId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ aId = mId;
+ return NS_OK;
+}
+
+void
+nsIDocument::SetId(const nsAString& aId)
+{
+ // The ID should only be set one time, but we may get the same value
+ // more than once if the document is controlled coming out of bfcache.
+ MOZ_ASSERT_IF(mId != aId, mId.IsEmpty());
+ mId = aId;
+}
+
+bool
+MarkDocumentTreeToBeInSyncOperation(nsIDocument* aDoc, void* aData)
+{
+ nsCOMArray<nsIDocument>* documents =
+ static_cast<nsCOMArray<nsIDocument>*>(aData);
+ if (aDoc) {
+ aDoc->SetIsInSyncOperation(true);
+ documents->AppendObject(aDoc);
+ aDoc->EnumerateSubDocuments(MarkDocumentTreeToBeInSyncOperation, aData);
+ }
+ return true;
+}
+
+nsAutoSyncOperation::nsAutoSyncOperation(nsIDocument* aDoc)
+{
+ mMicroTaskLevel = nsContentUtils::MicroTaskLevel();
+ nsContentUtils::SetMicroTaskLevel(0);
+ if (aDoc) {
+ if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> top = win->GetTop()) {
+ nsCOMPtr<nsIDocument> doc = top->GetExtantDoc();
+ MarkDocumentTreeToBeInSyncOperation(doc, &mDocuments);
+ }
+ }
+ }
+}
+
+nsAutoSyncOperation::~nsAutoSyncOperation()
+{
+ for (int32_t i = 0; i < mDocuments.Count(); ++i) {
+ mDocuments[i]->SetIsInSyncOperation(false);
+ }
+ nsContentUtils::SetMicroTaskLevel(mMicroTaskLevel);
+}
+
+gfxUserFontSet*
+nsIDocument::GetUserFontSet(bool aFlushUserFontSet)
+{
+ // We want to initialize the user font set lazily the first time the
+ // user asks for it, rather than building it too early and forcing
+ // rule cascade creation. Thus we try to enforce the invariant that
+ // we *never* build the user font set until the first call to
+ // GetUserFontSet. However, once it's been requested, we can't wait
+ // for somebody to call GetUserFontSet in order to rebuild it (see
+ // comments below in RebuildUserFontSet for why).
+#ifdef DEBUG
+ bool userFontSetGottenBefore = mGetUserFontSetCalled;
+#endif
+ // Set mGetUserFontSetCalled up front, so that FlushUserFontSet will actually
+ // flush.
+ mGetUserFontSetCalled = true;
+ if (mFontFaceSetDirty && aFlushUserFontSet) {
+ // If this assertion fails, and there have actually been changes to
+ // @font-face rules, then we will call StyleChangeReflow in
+ // FlushUserFontSet. If we're in the middle of reflow,
+ // that's a bad thing to do, and the caller was responsible for
+ // flushing first. If we're not (e.g., in frame construction), it's
+ // ok.
+ NS_ASSERTION(!userFontSetGottenBefore ||
+ !GetShell() ||
+ !GetShell()->IsReflowLocked(),
+ "FlushUserFontSet should have been called first");
+ FlushUserFontSet();
+ }
+
+ if (!mFontFaceSet) {
+ return nullptr;
+ }
+
+ return mFontFaceSet->GetUserFontSet();
+}
+
+void
+nsIDocument::FlushUserFontSet()
+{
+ if (!mGetUserFontSetCalled) {
+ return; // No one cares about this font set yet, but we want to be careful
+ // to not unset our mFontFaceSetDirty bit, so when someone really
+ // does we'll create it.
+ }
+
+ if (mFontFaceSetDirty) {
+ if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) {
+ nsTArray<nsFontFaceRuleContainer> rules;
+ nsIPresShell* shell = GetShell();
+ if (shell) {
+ // XXXheycam ServoStyleSets don't support exposing @font-face rules yet.
+ if (shell->StyleSet()->IsGecko()) {
+ if (!shell->StyleSet()->AsGecko()->AppendFontFaceRules(rules)) {
+ return;
+ }
+ } else {
+ NS_WARNING("stylo: ServoStyleSets cannot handle @font-face rules yet. "
+ "See bug 1290237.");
+ }
+ }
+
+ bool changed = false;
+
+ if (!mFontFaceSet && !rules.IsEmpty()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
+ mFontFaceSet = new FontFaceSet(window, this);
+ }
+ 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 && shell) {
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext) {
+ presContext->UserFontSetUpdated();
+ }
+ }
+ }
+
+ mFontFaceSetDirty = false;
+ }
+}
+
+void
+nsIDocument::RebuildUserFontSet()
+{
+ if (!mGetUserFontSetCalled) {
+ // We want to lazily build the user font set the first time it's
+ // requested (so we don't force creation of rule cascades too
+ // early), so don't do anything now.
+ return;
+ }
+
+ mFontFaceSetDirty = true;
+ SetNeedStyleFlush();
+
+ // Somebody has already asked for the user font set, so we need to
+ // post an event to rebuild it. Setting the user font set to be dirty
+ // and lazily rebuilding it isn't sufficient, since it is only the act
+ // of rebuilding it that will trigger the style change reflow that
+ // calls GetUserFontSet. (This reflow causes rebuilding of text runs,
+ // which starts font loads, whose completion causes another style
+ // change reflow).
+ if (!mPostedFlushUserFontSet) {
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod(this, &nsIDocument::HandleRebuildUserFontSet);
+ if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
+ mPostedFlushUserFontSet = true;
+ }
+ }
+}
+
+FontFaceSet*
+nsIDocument::Fonts()
+{
+ if (!mFontFaceSet) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
+ mFontFaceSet = new FontFaceSet(window, this);
+ GetUserFontSet(); // this will cause the user font set to be created/updated
+ }
+ return mFontFaceSet;
+}
+
+void
+nsIDocument::ReportHasScrollLinkedEffect()
+{
+ if (mHasScrollLinkedEffect) {
+ // We already did this once for this document, don't do it again.
+ return;
+ }
+ mHasScrollLinkedEffect = true;
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Async Pan/Zoom"),
+ this, nsContentUtils::eLAYOUT_PROPERTIES,
+ "ScrollLinkedEffectFound2");
+}
+
+void
+nsIDocument::UpdateStyleBackendType()
+{
+ MOZ_ASSERT(mStyleBackendType == StyleBackendType(0),
+ "no need to call UpdateStyleBackendType now");
+
+ // Assume Gecko by default.
+ mStyleBackendType = StyleBackendType::Gecko;
+
+#ifdef MOZ_STYLO
+ // XXX For now we use a Servo-backed style set only for (X)HTML documents
+ // in content docshells. This should let us avoid implementing XUL-specific
+ // CSS features. And apart from not supporting SVG properties in Servo
+ // yet, the root SVG element likes to create a style sheet for an SVG
+ // document before we have a pres shell (i.e. before we make the decision
+ // here about whether to use a Gecko- or Servo-backed style system), so
+ // we avoid Servo-backed style sets for SVG documents.
+ if (!mDocumentContainer) {
+ NS_WARNING("stylo: No docshell yet, assuming Gecko style system");
+ } else if (nsLayoutUtils::SupportsServoStyleBackend(this)) {
+ mStyleBackendType = StyleBackendType::Servo;
+ }
+#endif
+}
+
+const nsString*
+nsDocument::CheckCustomElementName(const ElementCreationOptions& aOptions,
+ const nsAString& aLocalName,
+ uint32_t aNamespaceID,
+ ErrorResult& rv)
+{
+ // only check aOptions if 'is' is passed and the webcomponents preference
+ // is enabled
+ if (!aOptions.mIs.WasPassed() ||
+ !CustomElementRegistry::IsCustomElementEnabled()) {
+ return nullptr;
+ }
+
+ const nsString* is = &aOptions.mIs.Value();
+
+ // Throw NotFoundError if 'is' is not-null and definition is null
+ if (!nsContentUtils::LookupCustomElementDefinition(this, aLocalName,
+ aNamespaceID, is)) {
+ rv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ }
+
+ return is;
+}
diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h
new file mode 100644
index 000000000..17d936055
--- /dev/null
+++ b/dom/base/nsDocument.h
@@ -0,0 +1,1641 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.
+ */
+
+#ifndef nsDocument_h___
+#define nsDocument_h___
+
+#include "nsIDocument.h"
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsCRT.h"
+#include "nsWeakReference.h"
+#include "nsWeakPtr.h"
+#include "nsTArray.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMDocumentXBL.h"
+#include "nsStubDocumentObserver.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIContent.h"
+#include "nsIPrincipal.h"
+#include "nsIParser.h"
+#include "nsBindingManager.h"
+#include "nsInterfaceHashtable.h"
+#include "nsJSThingHashtable.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIURI.h"
+#include "nsScriptLoader.h"
+#include "nsIRadioGroupContainer.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIRequest.h"
+#include "nsILoadGroup.h"
+#include "nsTObserverArray.h"
+#include "nsStubMutationObserver.h"
+#include "nsIChannel.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsContentList.h"
+#include "nsGkAtoms.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheContainer.h"
+#include "mozilla/StyleSetHandle.h"
+#include "PLDHashTable.h"
+#include "nsAttrAndChildArray.h"
+#include "nsDOMAttributeMap.h"
+#include "nsIContentViewer.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+#include "nsIProgressEventSink.h"
+#include "nsISecurityEventSink.h"
+#include "nsIChannelEventSink.h"
+#include "imgIRequest.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PendingAnimationTracker.h"
+#include "mozilla/dom/DOMImplementation.h"
+#include "mozilla/dom/StyleSheetList.h"
+#include "nsDataHashtable.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "nsIDOMXPathEvaluator.h"
+#include "jsfriendapi.h"
+#include "ImportManager.h"
+#include "mozilla/LinkedList.h"
+#include "CustomElementRegistry.h"
+#include "mozilla/dom/Performance.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)
+
+
+class nsDOMStyleSheetSetList;
+class nsDocument;
+class nsIRadioVisitor;
+class nsIFormControl;
+struct nsRadioGroupStruct;
+class nsOnloadBlocker;
+class nsUnblockOnloadEvent;
+class nsDOMNavigationTiming;
+class nsWindowSizes;
+class nsHtml5TreeOpExecutor;
+class nsDocumentOnStack;
+class nsISecurityConsoleMessage;
+class nsPIBoxObject;
+
+namespace mozilla {
+class EventChainPreVisitor;
+namespace dom {
+class BoxObject;
+class ImageTracker;
+struct LifecycleCallbacks;
+class CallbackFunction;
+class DOMIntersectionObserver;
+class Performance;
+
+struct FullscreenRequest : public LinkedListElement<FullscreenRequest>
+{
+ explicit FullscreenRequest(Element* aElement);
+ FullscreenRequest(const FullscreenRequest&) = delete;
+ ~FullscreenRequest();
+
+ Element* GetElement() const { return mElement; }
+ nsDocument* GetDocument() const { return mDocument; }
+
+private:
+ RefPtr<Element> mElement;
+ RefPtr<nsDocument> mDocument;
+
+public:
+ // This value should be true if the fullscreen request is
+ // originated from chrome code.
+ bool mIsCallerChrome = false;
+ // 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.
+ bool mShouldNotifyNewOrigin = true;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * 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 nsIdentifierMapEntry, the document.all result is released too.
+ * Perhaps the document.all results should have their own hashtable
+ * in nsHTMLDocument.
+ */
+class nsIdentifierMapEntry : public nsStringHashKey
+{
+public:
+ typedef mozilla::dom::Element Element;
+ typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
+
+ explicit nsIdentifierMapEntry(const nsAString& aKey) :
+ nsStringHashKey(&aKey), mNameContentList(nullptr)
+ {
+ }
+ explicit nsIdentifierMapEntry(const nsAString* aKey) :
+ nsStringHashKey(aKey), mNameContentList(nullptr)
+ {
+ }
+ nsIdentifierMapEntry(const nsIdentifierMapEntry& aOther) :
+ nsStringHashKey(&aOther.GetKey())
+ {
+ NS_ERROR("Should never be called");
+ }
+ ~nsIdentifierMapEntry();
+
+ void AddNameElement(nsINode* aDocument, Element* aElement);
+ void RemoveNameElement(Element* aElement);
+ bool IsEmpty();
+ nsBaseContentList* GetNameContentList() {
+ return mNameContentList;
+ }
+ bool HasNameElement() const {
+ return mNameContentList && mNameContentList->Length() != 0;
+ }
+
+ /**
+ * Returns the element if we know the element associated with this
+ * id. Otherwise returns null.
+ */
+ Element* GetIdElement();
+ /**
+ * 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();
+ /**
+ * Append all the elements with this id to aElements
+ */
+ void AppendAllIdContent(nsCOMArray<nsIContent>* aElements);
+ /**
+ * This can fire ID change callbacks.
+ * @return true if the content could be added, false if we failed due
+ * to OOM.
+ */
+ bool 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();
+
+ bool HasContentChangeCallback() { return mChangeCallbacks != nullptr; }
+ void AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback,
+ void* aData, bool aForImage);
+ void RemoveContentChangeCallback(nsIDocument::IDTargetObserver aCallback,
+ void* aData, bool aForImage);
+
+ void Traverse(nsCycleCollectionTraversalCallback* aCallback);
+
+ struct ChangeCallback {
+ nsIDocument::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(const ChangeCallbackEntry& toCopy) :
+ mKey(toCopy.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 mozilla::HashGeneric(aKey->mCallback, aKey->mData);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ ChangeCallback mKey;
+ };
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ void FireChangeCallbacks(Element* aOldElement, Element* aNewElement,
+ bool aImageOnly = false);
+
+ // empty if there are no elements with this ID.
+ // The elements are stored as weak pointers.
+ nsTArray<Element*> mIdContentList;
+ RefPtr<nsBaseContentList> mNameContentList;
+ nsAutoPtr<nsTHashtable<ChangeCallbackEntry> > mChangeCallbacks;
+ RefPtr<Element> mImageElement;
+};
+
+namespace mozilla {
+namespace dom {
+
+} // namespace dom
+} // namespace mozilla
+
+class nsDocHeaderData
+{
+public:
+ nsDocHeaderData(nsIAtom* aField, const nsAString& aData)
+ : mField(aField), mData(aData), mNext(nullptr)
+ {
+ }
+
+ ~nsDocHeaderData(void)
+ {
+ delete mNext;
+ }
+
+ nsCOMPtr<nsIAtom> mField;
+ nsString mData;
+ nsDocHeaderData* mNext;
+};
+
+class nsDOMStyleSheetList : public mozilla::dom::StyleSheetList,
+ public nsStubDocumentObserver
+{
+public:
+ explicit nsDOMStyleSheetList(nsIDocument* aDocument);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDocumentObserver
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETADDED
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETREMOVED
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ virtual nsINode* GetParentObject() const override
+ {
+ return mDocument;
+ }
+
+ uint32_t Length() override;
+ mozilla::StyleSheet* IndexedGetter(uint32_t aIndex, bool& aFound) override;
+
+protected:
+ virtual ~nsDOMStyleSheetList();
+
+ int32_t mLength;
+ nsIDocument* mDocument;
+};
+
+class nsOnloadBlocker final : public nsIRequest
+{
+public:
+ nsOnloadBlocker() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+
+private:
+ ~nsOnloadBlocker() {}
+};
+
+class nsExternalResourceMap
+{
+public:
+ typedef nsIDocument::ExternalResourceLoad ExternalResourceLoad;
+ nsExternalResourceMap();
+
+ /**
+ * Request an external resource document. This does exactly what
+ * nsIDocument::RequestExternalResource is documented to do.
+ */
+ nsIDocument* RequestResource(nsIURI* aURI,
+ nsINode* aRequestingNode,
+ nsDocument* aDisplayDocument,
+ ExternalResourceLoad** aPendingLoad);
+
+ /**
+ * Enumerate the resource documents. See
+ * nsIDocument::EnumerateExternalResources.
+ */
+ void EnumerateResources(nsIDocument::nsSubDocEnumFunc aCallback, void* aData);
+
+ /**
+ * 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();
+ nsCOMPtr<nsIDocument> 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() {}
+
+ public:
+ explicit PendingLoad(nsDocument* 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, 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<nsDocument> mDisplayDocument;
+ nsCOMPtr<nsIStreamListener> mTargetListener;
+ nsCOMPtr<nsIURI> mURI;
+ };
+ friend class PendingLoad;
+
+ class LoadgroupCallbacks final : public nsIInterfaceRequestor
+ {
+ ~LoadgroupCallbacks() {}
+ 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)
+ DECL_SHIM(nsISecurityEventSink, NSISECURITYEVENTSINK)
+ DECL_SHIM(nsIApplicationCacheContainer, NSIAPPLICATIONCACHECONTAINER)
+#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,
+ nsIDocument* aDisplayDocument);
+
+ nsClassHashtable<nsURIHashKey, ExternalResource> mMap;
+ nsRefPtrHashtable<nsURIHashKey, PendingLoad> mPendingLoads;
+ bool mHaveShutDown;
+};
+
+// Base class for our document implementations.
+class nsDocument : public nsIDocument,
+ public nsIDOMDocument,
+ public nsIDOMDocumentXBL,
+ public nsSupportsWeakReference,
+ public nsIScriptObjectPrincipal,
+ public nsIRadioGroupContainer,
+ public nsIApplicationCacheContainer,
+ public nsStubMutationObserver,
+ public nsIObserver,
+ public nsIDOMXPathEvaluator
+{
+ friend class nsIDocument;
+
+public:
+ typedef mozilla::dom::Element Element;
+ using nsIDocument::GetElementsByTagName;
+ typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ NS_DECL_SIZEOF_EXCLUDING_THIS
+
+ virtual void Reset(nsIChannel *aChannel, nsILoadGroup *aLoadGroup) override;
+ virtual void ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
+ nsIPrincipal* aPrincipal) override;
+
+ // StartDocumentLoad is pure virtual so that subclasses must override it.
+ // The nsDocument StartDocumentLoad 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 = true,
+ nsIContentSink* aContentSink = nullptr) override = 0;
+
+ virtual void StopDocumentLoad() override;
+
+ virtual void NotifyPossibleTitleChange(bool aBoundTitleElement) override;
+
+ virtual void SetDocumentURI(nsIURI* aURI) override;
+
+ virtual void SetChromeXHRDocURI(nsIURI* aURI) override;
+
+ virtual void SetChromeXHRDocBaseURI(nsIURI* aURI) override;
+
+ virtual void ApplySettingsFromCSP(bool aSpeculative) override;
+
+ /**
+ * Set the principal responsible for this document.
+ */
+ virtual void SetPrincipal(nsIPrincipal *aPrincipal) override;
+
+ /**
+ * Get the Content-Type of this document.
+ */
+ // NS_IMETHOD GetContentType(nsAString& aContentType);
+ // Already declared in nsIDOMDocument
+
+ /**
+ * Set the Content-Type of this document.
+ */
+ virtual void SetContentType(const nsAString& aContentType) override;
+
+ virtual void SetBaseURI(nsIURI* aURI) override;
+
+ /**
+ * Get/Set the base target of a link in a document.
+ */
+ virtual void GetBaseTarget(nsAString &aBaseTarget) override;
+
+ /**
+ * Return a standard name for the document's character set. This will
+ * trigger a startDocumentLoad if necessary to answer the question.
+ */
+ virtual void SetDocumentCharacterSet(const nsACString& aCharSetID) override;
+
+ /**
+ * Add an observer that gets notified whenever the charset changes.
+ */
+ virtual nsresult AddCharSetObserver(nsIObserver* aObserver) override;
+
+ /**
+ * Remove a charset observer.
+ */
+ virtual void RemoveCharSetObserver(nsIObserver* aObserver) override;
+
+ virtual Element* AddIDTargetObserver(nsIAtom* aID, IDTargetObserver aObserver,
+ void* aData, bool aForImage) override;
+ virtual void RemoveIDTargetObserver(nsIAtom* aID, IDTargetObserver aObserver,
+ void* aData, bool aForImage) override;
+
+ /**
+ * Access HTTP header data (this may also get set from other sources, like
+ * HTML META tags).
+ */
+ virtual void GetHeaderData(nsIAtom* aHeaderField, nsAString& aData) const override;
+ virtual void SetHeaderData(nsIAtom* aheaderField,
+ const nsAString& aData) override;
+
+ /**
+ * 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).
+ */
+ virtual already_AddRefed<nsIPresShell> CreateShell(
+ nsPresContext* aContext,
+ nsViewManager* aViewManager,
+ mozilla::StyleSetHandle aStyleSet) override;
+ virtual void DeleteShell() override;
+
+ virtual nsresult GetAllowPlugins(bool* aAllowPlugins) override;
+
+ static bool IsElementAnimateEnabled(JSContext* aCx, JSObject* aObject);
+ static bool IsWebAnimationsEnabled(JSContext* aCx, JSObject* aObject);
+ virtual mozilla::dom::DocumentTimeline* Timeline() override;
+ virtual void GetAnimations(
+ nsTArray<RefPtr<mozilla::dom::Animation>>& aAnimations) override;
+ mozilla::LinkedList<mozilla::dom::DocumentTimeline>& Timelines() override
+ {
+ return mTimelines;
+ }
+
+ virtual nsresult SetSubDocumentFor(Element* aContent,
+ nsIDocument* aSubDoc) override;
+ virtual nsIDocument* GetSubDocumentFor(nsIContent* aContent) const override;
+ virtual Element* FindContentForSubDocument(nsIDocument *aDocument) const override;
+ virtual Element* GetRootElementInternal() const override;
+
+ virtual void EnsureOnDemandBuiltInUASheet(mozilla::StyleSheet* aSheet) override;
+
+ /**
+ * Get the (document) style sheets owned by this document.
+ * These are ordered, highest priority last
+ */
+ virtual int32_t GetNumberOfStyleSheets() const override;
+ virtual mozilla::StyleSheet* GetStyleSheetAt(int32_t aIndex) const override;
+ virtual int32_t GetIndexOfStyleSheet(
+ const mozilla::StyleSheet* aSheet) const override;
+ virtual void AddStyleSheet(mozilla::StyleSheet* aSheet) override;
+ virtual void RemoveStyleSheet(mozilla::StyleSheet* aSheet) override;
+
+ virtual void UpdateStyleSheets(
+ nsTArray<RefPtr<mozilla::StyleSheet>>& aOldSheets,
+ nsTArray<RefPtr<mozilla::StyleSheet>>& aNewSheets) override;
+ virtual void AddStyleSheetToStyleSets(mozilla::StyleSheet* aSheet);
+ virtual void RemoveStyleSheetFromStyleSets(mozilla::StyleSheet* aSheet);
+
+ virtual void InsertStyleSheetAt(mozilla::StyleSheet* aSheet,
+ int32_t aIndex) override;
+ virtual void SetStyleSheetApplicableState(mozilla::StyleSheet* aSheet,
+ bool aApplicable) override;
+
+ virtual nsresult LoadAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* aSheetURI) override;
+ virtual nsresult AddAdditionalStyleSheet(additionalSheetType aType,
+ mozilla::StyleSheet* aSheet) override;
+ virtual void RemoveAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* sheetURI) override;
+ virtual mozilla::StyleSheet* GetFirstAdditionalAuthorSheet() override;
+
+ virtual nsIChannel* GetChannel() const override {
+ return mChannel;
+ }
+
+ virtual nsIChannel* GetFailedChannel() const override {
+ return mFailedChannel;
+ }
+ virtual void SetFailedChannel(nsIChannel* aChannel) override {
+ mFailedChannel = aChannel;
+ }
+
+ virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aGlobalObject) override;
+
+ virtual void SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) override;
+
+ virtual nsIGlobalObject* GetScopeObject() const override;
+ void SetScopeObject(nsIGlobalObject* aGlobal) override;
+ /**
+ * Get the script loader for this document
+ */
+ virtual nsScriptLoader* ScriptLoader() override;
+
+ /**
+ * Add/Remove an element to the document's id and name hashes
+ */
+ virtual void AddToIdTable(Element* aElement, nsIAtom* aId) override;
+ virtual void RemoveFromIdTable(Element* aElement, nsIAtom* aId) override;
+ virtual void AddToNameTable(Element* aElement, nsIAtom* aName) override;
+ virtual void RemoveFromNameTable(Element* aElement, nsIAtom* aName) override;
+
+ /**
+ * Add a new observer of document change notifications. Whenever
+ * content is changed, appended, inserted or removed the observers are
+ * informed.
+ */
+ virtual void AddObserver(nsIDocumentObserver* aObserver) override;
+
+ /**
+ * Remove an observer of document change notifications. This will
+ * return false if the observer cannot be found.
+ */
+ virtual bool RemoveObserver(nsIDocumentObserver* aObserver) override;
+
+ // Observation hooks used to propagate notifications to document
+ // observers.
+ virtual void BeginUpdate(nsUpdateType aUpdateType) override;
+ virtual void EndUpdate(nsUpdateType aUpdateType) override;
+ virtual void BeginLoad() override;
+ virtual void EndLoad() override;
+
+ virtual void SetReadyStateInternal(ReadyState rs) override;
+
+ virtual void ContentStateChanged(nsIContent* aContent,
+ mozilla::EventStates aStateMask)
+ override;
+ virtual void DocumentStatesChanged(
+ mozilla::EventStates aStateMask) override;
+
+ virtual void StyleRuleChanged(mozilla::StyleSheet* aStyleSheet,
+ mozilla::css::Rule* aStyleRule) override;
+ virtual void StyleRuleAdded(mozilla::StyleSheet* aStyleSheet,
+ mozilla::css::Rule* aStyleRule) override;
+ virtual void StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet,
+ mozilla::css::Rule* aStyleRule) override;
+
+ virtual void FlushPendingNotifications(mozFlushType aType) override;
+ virtual void FlushExternalResources(mozFlushType aType) override;
+ virtual void SetXMLDeclaration(const char16_t *aVersion,
+ const char16_t *aEncoding,
+ const int32_t aStandalone) override;
+ virtual void GetXMLDeclaration(nsAString& aVersion,
+ nsAString& aEncoding,
+ nsAString& Standalone) override;
+ virtual bool IsScriptEnabled() override;
+
+ virtual void OnPageShow(bool aPersisted, mozilla::dom::EventTarget* aDispatchStartTarget) override;
+ virtual void OnPageHide(bool aPersisted, mozilla::dom::EventTarget* aDispatchStartTarget) override;
+
+ virtual void WillDispatchMutationEvent(nsINode* aTarget) override;
+ virtual void MutationEventDispatched(nsINode* aTarget) override;
+
+ // nsINode
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+ virtual nsIContent *GetChildAt(uint32_t aIndex) const override;
+ virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const override;
+ virtual int32_t IndexOf(const nsINode* aPossibleChild) const override;
+ virtual uint32_t GetChildCount() const override;
+ virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify) override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override
+ {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // nsIRadioGroupContainer
+ NS_IMETHOD WalkRadioGroup(const nsAString& aName,
+ nsIRadioVisitor* aVisitor,
+ bool aFlushContent) override;
+ virtual void
+ SetCurrentRadioButton(const nsAString& aName,
+ mozilla::dom::HTMLInputElement* aRadio) override;
+ virtual mozilla::dom::HTMLInputElement*
+ GetCurrentRadioButton(const nsAString& aName) override;
+ NS_IMETHOD
+ GetNextRadioButton(const nsAString& aName,
+ const bool aPrevious,
+ mozilla::dom::HTMLInputElement* aFocusedRadio,
+ mozilla::dom::HTMLInputElement** aRadioOut) override;
+ virtual void AddToRadioGroup(const nsAString& aName,
+ nsIFormControl* aRadio) override;
+ virtual void RemoveFromRadioGroup(const nsAString& aName,
+ nsIFormControl* aRadio) override;
+ virtual uint32_t GetRequiredRadioCount(const nsAString& aName) const override;
+ virtual void RadioRequiredWillChange(const nsAString& aName,
+ bool aRequiredAdded) override;
+ virtual bool GetValueMissingState(const nsAString& aName) const override;
+ virtual void SetValueMissingState(const nsAString& aName, bool aValue) override;
+
+ // for radio group
+ nsRadioGroupStruct* GetRadioGroup(const nsAString& aName) const;
+ nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
+
+ virtual nsViewportInfo GetViewportInfo(const mozilla::ScreenIntSize& aDisplaySize) override;
+
+ void ReportUseCounters();
+
+ virtual void AddIntersectionObserver(
+ mozilla::dom::DOMIntersectionObserver* aObserver) override;
+ virtual void RemoveIntersectionObserver(
+ mozilla::dom::DOMIntersectionObserver* aObserver) override;
+ virtual void UpdateIntersectionObservations() override;
+ virtual void ScheduleIntersectionObserverNotification() override;
+ virtual void NotifyIntersectionObservers() override;
+
+ virtual void NotifyLayerManagerRecreated() override;
+
+
+private:
+ void AddOnDemandBuiltInUASheet(mozilla::StyleSheet* aSheet);
+ nsRadioGroupStruct* GetRadioGroupInternal(const nsAString& aName) const;
+ void SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages);
+
+public:
+ // nsIDOMNode
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE_OVERRIDABLE
+
+ // nsIDOMDocument
+ NS_DECL_NSIDOMDOCUMENT
+
+ // nsIDOMDocumentXBL
+ NS_DECL_NSIDOMDOCUMENTXBL
+
+ // nsIDOMEventTarget
+ virtual nsresult PreHandleEvent(
+ mozilla::EventChainPreVisitor& aVisitor) override;
+ virtual mozilla::EventListenerManager*
+ GetOrCreateListenerManager() override;
+ virtual mozilla::EventListenerManager*
+ GetExistingListenerManager() const override;
+
+ // nsIScriptObjectPrincipal
+ virtual nsIPrincipal* GetPrincipal() override;
+
+ // nsIApplicationCacheContainer
+ NS_DECL_NSIAPPLICATIONCACHECONTAINER
+
+ // nsIObserver
+ NS_DECL_NSIOBSERVER
+
+ NS_DECL_NSIDOMXPATHEVALUATOR
+
+ virtual nsresult Init();
+
+ virtual already_AddRefed<Element> CreateElem(const nsAString& aName,
+ nsIAtom* aPrefix,
+ int32_t aNamespaceID,
+ const nsAString* aIs = nullptr) override;
+
+ virtual void Sanitize() override;
+
+ virtual void EnumerateSubDocuments(nsSubDocEnumFunc aCallback,
+ void *aData) override;
+
+ virtual bool CanSavePresentation(nsIRequest *aNewRequest) override;
+ virtual void Destroy() override;
+ virtual void RemovedFromDocShell() override;
+ virtual already_AddRefed<nsILayoutHistoryState> GetLayoutHistoryState() const override;
+
+ virtual void BlockOnload() override;
+ virtual void UnblockOnload(bool aFireSync) override;
+
+ virtual void AddStyleRelevantLink(mozilla::dom::Link* aLink) override;
+ virtual void ForgetLink(mozilla::dom::Link* aLink) override;
+
+ virtual void ClearBoxObjectFor(nsIContent* aContent) override;
+
+ virtual already_AddRefed<mozilla::dom::BoxObject>
+ GetBoxObjectFor(mozilla::dom::Element* aElement,
+ mozilla::ErrorResult& aRv) override;
+
+ virtual Element*
+ GetAnonymousElementByAttribute(nsIContent* aElement,
+ nsIAtom* aAttrName,
+ const nsAString& aAttrValue) const override;
+
+ virtual Element* ElementFromPointHelper(float aX, float aY,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout) override;
+
+ virtual void ElementsFromPointHelper(float aX, float aY,
+ uint32_t aFlags,
+ nsTArray<RefPtr<mozilla::dom::Element>>& aElements) override;
+
+ virtual nsresult NodesFromRectHelper(float aX, float aY,
+ float aTopSize, float aRightSize,
+ float aBottomSize, float aLeftSize,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout,
+ nsIDOMNodeList** aReturn) override;
+
+ virtual void FlushSkinBindings() override;
+
+ virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) override;
+ virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) override;
+ virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) override;
+ virtual nsIDocument*
+ RequestExternalResource(nsIURI* aURI,
+ nsINode* aRequestingNode,
+ ExternalResourceLoad** aPendingLoad) override;
+ virtual void
+ EnumerateExternalResources(nsSubDocEnumFunc aCallback, void* aData) override;
+
+ // Returns our (lazily-initialized) animation controller.
+ // If HasAnimationController is true, this is guaranteed to return non-null.
+ nsSMILAnimationController* GetAnimationController() override;
+
+ virtual mozilla::PendingAnimationTracker*
+ GetPendingAnimationTracker() final override
+ {
+ return mPendingAnimationTracker;
+ }
+
+ virtual mozilla::PendingAnimationTracker*
+ GetOrCreatePendingAnimationTracker() override;
+
+ virtual void SuppressEventHandling(SuppressionType aWhat,
+ uint32_t aIncrease) override;
+
+ virtual void UnsuppressEventHandlingAndFireEvents(SuppressionType aWhat,
+ bool aFireEvents) override;
+
+ void DecreaseEventSuppression() {
+ MOZ_ASSERT(mEventsSuppressed);
+ --mEventsSuppressed;
+ UpdateFrameRequestCallbackSchedulingState();
+ }
+
+ void ResumeAnimations() {
+ MOZ_ASSERT(mAnimationsPaused);
+ --mAnimationsPaused;
+ UpdateFrameRequestCallbackSchedulingState();
+ }
+
+ virtual nsIDocument* GetTemplateContentsOwner() override;
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDocument,
+ nsIDocument)
+
+ void DoNotifyPossibleTitleChange();
+
+ nsExternalResourceMap& ExternalResourceMap()
+ {
+ return mExternalResourceMap;
+ }
+
+ void SetLoadedAsData(bool aLoadedAsData) { mLoadedAsData = aLoadedAsData; }
+ void SetLoadedAsInteractiveData(bool aLoadedAsInteractiveData)
+ {
+ mLoadedAsInteractiveData = aLoadedAsInteractiveData;
+ }
+
+ nsresult CloneDocHelper(nsDocument* clone) const;
+
+ void MaybeInitializeFinalizeFrameLoaders();
+
+ void MaybeEndOutermostXBLUpdate();
+
+ virtual void PreloadPictureOpened() override;
+ virtual void PreloadPictureClosed() override;
+
+ virtual void
+ PreloadPictureImageSource(const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr,
+ const nsAString& aTypeAttr,
+ const nsAString& aMediaAttr) override;
+
+ virtual already_AddRefed<nsIURI>
+ ResolvePreloadImage(nsIURI *aBaseURI,
+ const nsAString& aSrcAttr,
+ const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr) override;
+
+ virtual void MaybePreLoadImage(nsIURI* uri,
+ const nsAString &aCrossOriginAttr,
+ ReferrerPolicy aReferrerPolicy) override;
+ virtual void ForgetImagePreload(nsIURI* aURI) override;
+
+ virtual void MaybePreconnect(nsIURI* uri,
+ mozilla::CORSMode aCORSMode) override;
+
+ virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
+ const nsAString& aCrossOriginAttr,
+ ReferrerPolicy aReferrerPolicy,
+ const nsAString& aIntegrity) override;
+
+ virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
+ RefPtr<mozilla::StyleSheet>* aSheet) override;
+
+ virtual nsISupports* GetCurrentContentSink() override;
+
+ virtual mozilla::EventStates GetDocumentState() override;
+
+ // Only BlockOnload should call this!
+ void AsyncBlockOnload();
+
+ virtual void SetScrollToRef(nsIURI *aDocumentURI) override;
+ virtual void ScrollToRef() override;
+ virtual void ResetScrolledToRefAlready() override;
+ virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) override;
+
+ virtual Element *GetElementById(const nsAString& aElementId) override;
+ virtual const nsTArray<Element*>* GetAllElementsForId(const nsAString& aElementId) const override;
+
+ virtual Element *LookupImageElement(const nsAString& aElementId) override;
+ virtual void MozSetImageElement(const nsAString& aImageElementId,
+ Element* aElement) override;
+
+ // AddPlugin adds a plugin-related element to mPlugins when the element is
+ // added to the tree.
+ virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin) override;
+ // RemovePlugin removes a plugin-related element to mPlugins when the
+ // element is removed from the tree.
+ virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin) override;
+ // GetPlugins returns the plugin-related elements from
+ // the frame and any subframes.
+ virtual void GetPlugins(nsTArray<nsIObjectLoadingContent*>& aPlugins) override;
+
+ // Adds an element to mResponsiveContent when the element is
+ // added to the tree.
+ virtual nsresult AddResponsiveContent(nsIContent* aContent) override;
+ // Removes an element from mResponsiveContent when the element is
+ // removed from the tree.
+ virtual void RemoveResponsiveContent(nsIContent* aContent) override;
+ // Notifies any responsive content added by AddResponsiveContent upon media
+ // features values changing.
+ virtual void NotifyMediaFeatureValuesChanged() override;
+
+ virtual nsresult GetStateObject(nsIVariant** aResult) override;
+
+ virtual nsDOMNavigationTiming* GetNavigationTiming() const override;
+ virtual nsresult SetNavigationTiming(nsDOMNavigationTiming* aTiming) override;
+
+ virtual Element* FindImageMap(const nsAString& aNormalizedMapName) override;
+
+ virtual nsTArray<Element*> GetFullscreenStack() const override;
+ virtual void AsyncRequestFullScreen(
+ mozilla::UniquePtr<FullscreenRequest>&& aRequest) override;
+ virtual void RestorePreviousFullScreenState() override;
+ virtual bool IsFullscreenLeaf() override;
+ virtual nsresult
+ RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement) override;
+
+ virtual nsresult RemoteFrameFullscreenReverted() override;
+ virtual nsIDocument* GetFullscreenRoot() override;
+ virtual void SetFullscreenRoot(nsIDocument* aRoot) override;
+
+ // Returns the size of the mBlockedTrackingNodes array. (nsIDocument.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).
+ //
+ // A script can subsequently use GetBlockedTrackingNodes()
+ // 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
+ // mBlockedTrackingNodesArray but they are not removed when those nodes
+ // are removed from the tree or even garbage collected.
+ long BlockedTrackingNodeCount() const;
+
+ //
+ // Returns strong references to mBlockedTrackingNodes. (nsIDocument.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> BlockedTrackingNodes() const;
+
+ static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject);
+
+ // Do the "fullscreen element ready check" from the fullscreen spec.
+ // It returns true if the given element is allowed to go into fullscreen.
+ bool FullscreenElementReadyCheck(Element* aElement, bool aWasCallerChrome);
+
+ // This is called asynchronously by nsIDocument::AsyncRequestFullScreen()
+ // to move this document into full-screen mode if allowed.
+ void RequestFullScreen(mozilla::UniquePtr<FullscreenRequest>&& aRequest);
+
+ // Removes all elements from the full-screen stack, removing full-scren
+ // styles from the top element in the stack.
+ void CleanupFullscreenState();
+
+ // Pushes aElement onto the full-screen stack, and removes full-screen styles
+ // from the former full-screen stack top, and its ancestors, and applies the
+ // styles to aElement. aElement becomes the new "full-screen element".
+ bool FullScreenStackPush(Element* aElement);
+
+ // Remove the top element from the full-screen stack. Removes the full-screen
+ // styles from the former top element, and applies them to the new top
+ // element, if there is one.
+ void FullScreenStackPop();
+
+ // Returns the top element from the full-screen stack.
+ Element* FullScreenStackTop();
+
+ // DOM-exposed fullscreen API
+ bool FullscreenEnabled() override;
+ Element* GetFullscreenElement() override;
+
+ void RequestPointerLock(Element* aElement) override;
+ bool SetPointerLock(Element* aElement, int aCursorStyle);
+ static void UnlockPointer(nsIDocument* aDoc = nullptr);
+
+ void SetCurrentOrientation(mozilla::dom::OrientationType aType,
+ uint16_t aAngle) override;
+ uint16_t CurrentOrientationAngle() const override;
+ mozilla::dom::OrientationType CurrentOrientationType() const override;
+ void SetOrientationPendingPromise(mozilla::dom::Promise* aPromise) override;
+ mozilla::dom::Promise* GetOrientationPendingPromise() const override;
+
+ // This method may fire a DOM event; if it does so it will happen
+ // synchronously.
+ void UpdateVisibilityState();
+ // Posts an event to call UpdateVisibilityState
+ virtual void PostVisibilityUpdateEvent() override;
+
+ // Since we wouldn't automatically play media from non-visited page, we need
+ // to notify window when the page was first visited.
+ void MaybeActiveMediaComponents();
+
+ virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const override;
+ // DocAddSizeOfIncludingThis is inherited from nsIDocument.
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+ // WebIDL bits
+ virtual mozilla::dom::DOMImplementation*
+ GetImplementation(mozilla::ErrorResult& rv) override;
+ virtual void
+ RegisterElement(JSContext* aCx, const nsAString& aName,
+ const mozilla::dom::ElementRegistrationOptions& aOptions,
+ JS::MutableHandle<JSObject*> aRetval,
+ mozilla::ErrorResult& rv) override;
+ virtual mozilla::dom::StyleSheetList* StyleSheets() override;
+ virtual void SetSelectedStyleSheetSet(const nsAString& aSheetSet) override;
+ virtual void GetLastStyleSheetSet(nsString& aSheetSet) override;
+ virtual mozilla::dom::DOMStringList* StyleSheetSets() override;
+ virtual void EnableStyleSheetsForSet(const nsAString& aSheetSet) override;
+ virtual already_AddRefed<Element> CreateElement(const nsAString& aTagName,
+ const mozilla::dom::ElementCreationOptionsOrString& aOptions,
+ ErrorResult& rv) override;
+ virtual already_AddRefed<Element> CreateElementNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ const mozilla::dom::ElementCreationOptionsOrString& aOptions,
+ mozilla::ErrorResult& rv) override;
+
+ virtual nsIDocument* MasterDocument() override
+ {
+ return mMasterDocument ? mMasterDocument.get()
+ : this;
+ }
+
+ virtual void SetMasterDocument(nsIDocument* master) override
+ {
+ MOZ_ASSERT(master);
+ mMasterDocument = master;
+ }
+
+ virtual bool IsMasterDocument() override
+ {
+ return !mMasterDocument;
+ }
+
+ virtual mozilla::dom::ImportManager* ImportManager() override
+ {
+ if (mImportManager) {
+ MOZ_ASSERT(!mMasterDocument, "Only the master document has ImportManager set");
+ return mImportManager.get();
+ }
+
+ if (mMasterDocument) {
+ return mMasterDocument->ImportManager();
+ }
+
+ // ImportManager is created lazily.
+ // If the manager is not yet set it has to be the
+ // master document and this is the first import in it.
+ // Let's create a new manager.
+ mImportManager = new mozilla::dom::ImportManager();
+ return mImportManager.get();
+ }
+
+ virtual bool HasSubImportLink(nsINode* aLink) override
+ {
+ return mSubImportLinks.Contains(aLink);
+ }
+
+ virtual uint32_t IndexOfSubImportLink(nsINode* aLink) override
+ {
+ return mSubImportLinks.IndexOf(aLink);
+ }
+
+ virtual void AddSubImportLink(nsINode* aLink) override
+ {
+ mSubImportLinks.AppendElement(aLink);
+ }
+
+ virtual nsINode* GetSubImportLink(uint32_t aIdx) override
+ {
+ return aIdx < mSubImportLinks.Length() ? mSubImportLinks[aIdx].get()
+ : nullptr;
+ }
+
+ virtual void UnblockDOMContentLoaded() override;
+
+protected:
+ friend class nsNodeUtils;
+ friend class nsDocumentOnStack;
+
+ void IncreaseStackRefCnt()
+ {
+ ++mStackRefCnt;
+ }
+
+ void DecreaseStackRefCnt()
+ {
+ if (--mStackRefCnt == 0 && mNeedsReleaseAfterStackRefCntRelease) {
+ mNeedsReleaseAfterStackRefCntRelease = false;
+ NS_RELEASE_THIS();
+ }
+ }
+
+ /**
+ * 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();
+
+ void DispatchContentLoadedEvents();
+
+ void RetrieveRelevantHeaders(nsIChannel *aChannel);
+
+ void TryChannelCharset(nsIChannel *aChannel,
+ int32_t& aCharsetSource,
+ nsACString& aCharset,
+ nsHtml5TreeOpExecutor* aExecutor);
+
+ // 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();
+
+ // Refreshes the hrefs of all the links in the document.
+ void RefreshLinkHrefs();
+
+ nsIContent* GetFirstBaseNodeWithHref();
+ nsresult SetFirstBaseNodeWithHref(nsIContent *node);
+
+ /**
+ * 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();
+
+public:
+ // Get our title
+ virtual void GetTitle(nsString& aTitle) override;
+ // Set our title
+ virtual void SetTitle(const nsAString& aTitle, mozilla::ErrorResult& rv) override;
+
+ bool mIsTopLevelContentDocument: 1;
+ bool mIsContentDocument: 1;
+
+ bool IsTopLevelContentDocument();
+ void SetIsTopLevelContentDocument(bool aIsTopLevelContentDocument);
+
+ bool IsContentDocument() const;
+ void SetIsContentDocument(bool aIsContentDocument);
+
+ js::ExpandoAndGeneration mExpandoAndGeneration;
+
+ bool ContainsEMEContent();
+
+ bool ContainsMSEContent();
+
+protected:
+ already_AddRefed<nsIPresShell> doCreateShell(nsPresContext* aContext,
+ nsViewManager* aViewManager,
+ mozilla::StyleSetHandle aStyleSet);
+
+ void RemoveDocStyleSheetsFromStyleSets();
+ void RemoveStyleSheetsFromStyleSets(
+ const nsTArray<RefPtr<mozilla::StyleSheet>>& aSheets,
+ mozilla::SheetType aType);
+ void ResetStylesheetsToURI(nsIURI* aURI);
+ void FillStyleSet(mozilla::StyleSetHandle aStyleSet);
+
+ // Return whether all the presshells for this document are safe to flush
+ bool IsSafeToFlush() const;
+
+ void DispatchPageTransition(mozilla::dom::EventTarget* aDispatchTarget,
+ const nsAString& aType,
+ bool aPersisted);
+
+ virtual nsPIDOMWindowOuter* GetWindowInternal() const override;
+ virtual nsIScriptGlobalObject* GetScriptHandlingObjectInternal() const override;
+ virtual bool InternalAllowXULXBL() override;
+
+ void UpdateScreenOrientation();
+
+#define NS_DOCUMENT_NOTIFY_OBSERVERS(func_, params_) \
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, nsIDocumentObserver, \
+ func_, params_);
+
+#ifdef DEBUG
+ void VerifyRootContentState();
+#endif
+
+ explicit nsDocument(const char* aContentType);
+ virtual ~nsDocument();
+
+ void EnsureOnloadBlocker();
+
+ void NotifyStyleSheetApplicableStateChanged();
+
+ // 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(const FullscreenRequest& aRequest);
+
+ nsTArray<nsIObserver*> mCharSetObservers;
+
+ PLDHashTable *mSubDocuments;
+
+ // Array of owning references to all children
+ nsAttrAndChildArray mChildren;
+
+ // Pointer to our parser if we're currently in the process of being
+ // parsed into.
+ nsCOMPtr<nsIParser> mParser;
+
+ // 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;
+
+ nsTArray<RefPtr<mozilla::StyleSheet>> mStyleSheets;
+ nsTArray<RefPtr<mozilla::StyleSheet>> mOnDemandBuiltInUASheets;
+ nsTArray<RefPtr<mozilla::StyleSheet>> mAdditionalSheets[AdditionalSheetTypeCount];
+
+ // Array of observers
+ nsTObserverArray<nsIDocumentObserver*> mObservers;
+
+ // Array of intersection observers
+ nsTArray<RefPtr<mozilla::dom::DOMIntersectionObserver>> mIntersectionObservers;
+
+ // Tracker for animations that are waiting to start.
+ // nullptr until GetOrCreatePendingAnimationTracker is called.
+ RefPtr<mozilla::PendingAnimationTracker> mPendingAnimationTracker;
+
+ // 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;
+
+ // Stack of full-screen elements. When we request full-screen we push the
+ // full-screen element onto this stack, and when we cancel full-screen we
+ // pop one off this stack, restoring the previous full-screen state
+ nsTArray<nsWeakPtr> mFullScreenStack;
+
+ // 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;
+
+private:
+ static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
+
+ /**
+ * Check if the passed custom element name, aOptions.mIs, is a registered
+ * custom element type or not, then return the custom element name for future
+ * usage.
+ *
+ * If there is no existing custom element definition for this name, throw a
+ * NotFoundError.
+ */
+ const nsString* CheckCustomElementName(
+ const mozilla::dom::ElementCreationOptions& aOptions,
+ const nsAString& aLocalName,
+ uint32_t aNamespaceID,
+ ErrorResult& rv);
+
+public:
+ virtual already_AddRefed<mozilla::dom::CustomElementRegistry>
+ GetCustomElementRegistry() override;
+
+ // Check whether web components are enabled for the global of aObject.
+ static bool IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject);
+ // Check whether web components are enabled for the global of the document
+ // this nodeinfo comes from.
+ static bool IsWebComponentsEnabled(mozilla::dom::NodeInfo* aNodeInfo);
+ // Check whether web components are enabled for the given window.
+ static bool IsWebComponentsEnabled(nsPIDOMWindowInner* aWindow);
+
+ RefPtr<mozilla::EventListenerManager> mListenerManager;
+ RefPtr<mozilla::dom::StyleSheetList> mDOMStyleSheets;
+ RefPtr<nsDOMStyleSheetSetList> mStyleSheetSetList;
+ RefPtr<nsScriptLoader> mScriptLoader;
+ nsDocHeaderData* mHeaderData;
+ /* 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<nsIdentifierMapEntry> mIdentifierMap;
+
+ nsClassHashtable<nsStringHashKey, nsRadioGroupStruct> mRadioGroups;
+
+ // Recorded time of change to 'loading' state.
+ mozilla::TimeStamp mLoadingTimeStamp;
+
+ // True if the document has been detached from its content viewer.
+ bool mIsGoingAway:1;
+ // True if the document is being destroyed.
+ bool mInDestructor:1;
+
+ // True if this document has ever had an HTML or SVG <title> element
+ // bound to it
+ bool mMayHaveTitleElement:1;
+
+ bool mHasWarnedAboutBoxObjects:1;
+
+ bool mDelayFrameLoaderInitialization:1;
+
+ bool mSynchronousDOMContentLoaded:1;
+
+ bool mInXBLUpdate:1;
+
+ // Whether we're currently under a FlushPendingNotifications call to
+ // our presshell. This is used to handle flush reentry correctly.
+ bool mInFlush: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;
+
+ friend class nsCallRequestFullScreen;
+
+ // ScreenOrientation "pending promise" as described by
+ // http://www.w3.org/TR/screen-orientation/
+ RefPtr<mozilla::dom::Promise> mOrientationPendingPromise;
+
+ uint16_t mCurrentOrientationAngle;
+ mozilla::dom::OrientationType mCurrentOrientationType;
+
+ // Keeps track of whether we have a pending
+ // 'style-sheet-applicable-state-changed' notification.
+ bool mSSApplicableStateNotificationPending:1;
+
+ // Whether we have reported 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 mReportedUseCounters:1;
+
+ // Whether we have filled our pres shell's style set with the document's
+ // additional sheets and sheets from the nsStyleSheetService.
+ bool mStyleSetFilled:1;
+
+ uint8_t mPendingFullscreenRequests;
+
+ uint8_t mXMLDeclarationBits;
+
+ nsInterfaceHashtable<nsPtrHashKey<nsIContent>, nsPIBoxObject> *mBoxObjectTable;
+
+ // A document "without a browsing context" that owns the content of
+ // HTMLTemplateElement.
+ nsCOMPtr<nsIDocument> mTemplateContentsOwner;
+
+ // Our update nesting level
+ uint32_t mUpdateNestLevel;
+
+ // The application cache that this document is associated with, if
+ // any. This can change during the lifetime of the document.
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+
+ nsCOMPtr<nsIContent> mFirstBaseNodeWithHref;
+
+ mozilla::EventStates mDocumentState;
+ mozilla::EventStates mGotDocumentState;
+
+ RefPtr<nsDOMNavigationTiming> mTiming;
+private:
+ friend class nsUnblockOnloadEvent;
+ // Recomputes the visibility state but doesn't set the new value.
+ mozilla::dom::VisibilityState GetVisibilityState() const;
+ void NotifyStyleSheetAdded(mozilla::StyleSheet* aSheet, bool aDocumentSheet);
+ void NotifyStyleSheetRemoved(mozilla::StyleSheet* aSheet, bool aDocumentSheet);
+
+ void PostUnblockOnloadEvent();
+ void DoUnblockOnload();
+
+ nsresult CheckFrameOptions();
+ nsresult InitCSP(nsIChannel* aChannel);
+
+ /**
+ * 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;
+
+ // 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);
+
+ void ClearAllBoxObjects();
+
+ // Returns true if the scheme for the url for this document is "about"
+ bool IsAboutPage();
+
+ // These are not implemented and not supported.
+ nsDocument(const nsDocument& aOther);
+ nsDocument& operator=(const nsDocument& aOther);
+
+ // 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;
+
+ // Currently active onload blockers
+ uint32_t mOnloadBlockCount;
+ // Onload blockers which haven't been activated yet
+ uint32_t mAsyncOnloadBlockCount;
+ nsCOMPtr<nsIRequest> mOnloadBlocker;
+
+ // A hashtable of styled links keyed by address pointer.
+ nsTHashtable<nsPtrHashKey<mozilla::dom::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
+
+ // A set of responsive images keyed by address pointer.
+ nsTHashtable< nsPtrHashKey<nsIContent> > mResponsiveContent;
+
+ // Member to store out last-selected stylesheet set.
+ nsString mLastStyleSheetSet;
+
+ nsTArray<RefPtr<nsFrameLoader> > mInitializableFrameLoaders;
+ nsTArray<nsCOMPtr<nsIRunnable> > mFrameLoaderFinalizers;
+ RefPtr<nsRunnableMethod<nsDocument> > mFrameLoaderRunner;
+
+ nsCOMPtr<nsIRunnable> mMaybeEndOutermostXBLUpdateRunner;
+
+ nsRevocableEventPtr<nsRunnableMethod<nsDocument, void, false> >
+ mPendingTitleChangeEvent;
+
+ nsExternalResourceMap mExternalResourceMap;
+
+ // 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.
+ nsDataHashtable< nsURIHashKey, bool> mPreloadedPreconnects;
+
+ // Current depth of picture elements from parser
+ int32_t mPreloadPictureDepth;
+
+ // Set if we've found a URL for the current picture
+ nsString mPreloadPictureFoundSource;
+
+ RefPtr<mozilla::dom::DOMImplementation> mDOMImplementation;
+
+ RefPtr<nsContentList> mImageMaps;
+
+ nsCString mScrollToRef;
+ uint8_t mScrolledToRefAlready : 1;
+ uint8_t mChangeScrollPosWhenScrollingToRef : 1;
+
+ // Tracking for plugins in the document.
+ nsTHashtable< nsPtrHashKey<nsIObjectLoadingContent> > mPlugins;
+
+ RefPtr<mozilla::dom::DocumentTimeline> mDocumentTimeline;
+ mozilla::LinkedList<mozilla::dom::DocumentTimeline> mTimelines;
+
+ enum ViewportType {
+ DisplayWidthHeight,
+ Specified,
+ Unknown
+ };
+
+ ViewportType mViewportType;
+
+ // These member variables cache information about the viewport so we don't have to
+ // recalculate it each time.
+ bool mValidWidth, mValidHeight;
+ mozilla::LayoutDeviceToScreenScale mScaleMinFloat;
+ mozilla::LayoutDeviceToScreenScale mScaleMaxFloat;
+ mozilla::LayoutDeviceToScreenScale mScaleFloat;
+ mozilla::CSSToLayoutDeviceScale mPixelRatio;
+ bool mAutoSize, mAllowZoom, mAllowDoubleTapZoom, mValidScaleFloat, mValidMaxScale, mScaleStrEmpty, mWidthStrEmpty;
+ mozilla::CSSSize mViewportSize;
+
+ nsrefcnt mStackRefCnt;
+ bool mNeedsReleaseAfterStackRefCntRelease;
+
+ nsCOMPtr<nsIDocument> mMasterDocument;
+ RefPtr<mozilla::dom::ImportManager> mImportManager;
+ nsTArray<nsCOMPtr<nsINode> > mSubImportLinks;
+
+ // Set to true when the document is possibly controlled by the ServiceWorker.
+ // Used to prevent multiple requests to ServiceWorkerManager.
+ bool mMaybeServiceWorkerControlled;
+
+#ifdef DEBUG
+public:
+ bool mWillReparent;
+#endif
+};
+
+class nsDocumentOnStack
+{
+public:
+ explicit nsDocumentOnStack(nsDocument* aDoc) : mDoc(aDoc)
+ {
+ mDoc->IncreaseStackRefCnt();
+ }
+ ~nsDocumentOnStack()
+ {
+ mDoc->DecreaseStackRefCnt();
+ }
+private:
+ nsDocument* mDoc;
+};
+
+#endif /* nsDocument_h___ */
diff --git a/dom/base/nsDocumentEncoder.cpp b/dom/base/nsDocumentEncoder.cpp
new file mode 100644
index 000000000..0a3570962
--- /dev/null
+++ b/dom/base/nsDocumentEncoder.cpp
@@ -0,0 +1,2037 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Object that can be used to serialize selections, ranges, or nodes
+ * to strings in a gazillion different ways.
+ */
+
+#include "nsIDocumentEncoder.h"
+
+#include "nscore.h"
+#include "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsIDocument.h"
+#include "nsIHTMLDocument.h"
+#include "nsCOMPtr.h"
+#include "nsIContentSerializer.h"
+#include "nsIUnicodeEncoder.h"
+#include "nsIOutputStream.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMText.h"
+#include "nsIDOMCDATASection.h"
+#include "nsIDOMComment.h"
+#include "nsIDOMProcessingInstruction.h"
+#include "nsIDOMDocumentType.h"
+#include "nsIDOMNodeList.h"
+#include "nsRange.h"
+#include "nsIDOMRange.h"
+#include "nsIDOMDocument.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIParserService.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "mozilla/dom/Selection.h"
+#include "nsISelectionPrivate.h"
+#include "nsITransferable.h" // for kUnicodeMime
+#include "nsContentUtils.h"
+#include "nsNodeUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsReadableUtils.h"
+#include "nsTArray.h"
+#include "nsIFrame.h"
+#include "nsStringBuffer.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/ScopeExit.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsresult NS_NewDomSelection(nsISelection **aDomSelection);
+
+enum nsRangeIterationDirection {
+ kDirectionOut = -1,
+ kDirectionIn = 1
+};
+
+class nsDocumentEncoder : public nsIDocumentEncoder
+{
+public:
+ nsDocumentEncoder();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder)
+ NS_DECL_NSIDOCUMENTENCODER
+
+protected:
+ virtual ~nsDocumentEncoder();
+
+ void Initialize(bool aClearCachedSerializer = true);
+ nsresult SerializeNodeStart(nsINode* aNode, int32_t aStartOffset,
+ int32_t aEndOffset, nsAString& aStr,
+ nsINode* aOriginalNode = nullptr);
+ nsresult SerializeToStringRecursive(nsINode* aNode,
+ nsAString& aStr,
+ bool aDontSerializeRoot,
+ uint32_t aMaxLength = 0);
+ nsresult SerializeNodeEnd(nsINode* aNode, nsAString& aStr);
+ // This serializes the content of aNode.
+ nsresult SerializeToStringIterative(nsINode* aNode,
+ nsAString& aStr);
+ nsresult SerializeRangeToString(nsRange *aRange,
+ nsAString& aOutputString);
+ nsresult SerializeRangeNodes(nsRange* aRange,
+ nsINode* aNode,
+ nsAString& aString,
+ int32_t aDepth);
+ nsresult SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
+ nsAString& aString);
+ nsresult SerializeRangeContextEnd(nsAString& aString);
+
+ virtual int32_t
+ GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray)
+ {
+ return -1;
+ }
+
+ nsresult FlushText(nsAString& aString, bool aForce);
+
+ bool IsVisibleNode(nsINode* aNode)
+ {
+ NS_PRECONDITION(aNode, "");
+
+ if (mFlags & SkipInvisibleContent) {
+ // Treat the visibility of the ShadowRoot as if it were
+ // the host content.
+ nsCOMPtr<nsIContent> content;
+ ShadowRoot* shadowRoot = ShadowRoot::FromNode(aNode);
+ if (shadowRoot) {
+ content = shadowRoot->GetHost();
+ } else {
+ content = do_QueryInterface(aNode);
+ }
+
+ if (content) {
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ if (aNode->IsNodeOfType(nsINode::eTEXT)) {
+ // We have already checked that our parent is visible.
+ return true;
+ }
+ if (aNode->IsHTMLElement(nsGkAtoms::rp)) {
+ // Ruby parentheses are part of ruby structure, hence
+ // shouldn't be stripped out even if it is not displayed.
+ return true;
+ }
+ return false;
+ }
+ bool isVisible = frame->StyleVisibility()->IsVisible();
+ if (!isVisible && aNode->IsNodeOfType(nsINode::eTEXT))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ virtual bool IncludeInContext(nsINode *aNode);
+
+ nsCOMPtr<nsIDocument> mDocument;
+ nsCOMPtr<nsISelection> mSelection;
+ RefPtr<nsRange> mRange;
+ nsCOMPtr<nsINode> mNode;
+ nsCOMPtr<nsIOutputStream> mStream;
+ nsCOMPtr<nsIContentSerializer> mSerializer;
+ nsCOMPtr<nsIUnicodeEncoder> mUnicodeEncoder;
+ nsCOMPtr<nsINode> mCommonParent;
+ nsCOMPtr<nsIDocumentEncoderNodeFixup> mNodeFixup;
+
+ nsString mMimeType;
+ nsCString mCharset;
+ uint32_t mFlags;
+ uint32_t mWrapColumn;
+ uint32_t mStartDepth;
+ uint32_t mEndDepth;
+ int32_t mStartRootIndex;
+ int32_t mEndRootIndex;
+ AutoTArray<nsINode*, 8> mCommonAncestors;
+ AutoTArray<nsIContent*, 8> mStartNodes;
+ AutoTArray<int32_t, 8> mStartOffsets;
+ AutoTArray<nsIContent*, 8> mEndNodes;
+ AutoTArray<int32_t, 8> mEndOffsets;
+ AutoTArray<AutoTArray<nsINode*, 8>, 8> mRangeContexts;
+ bool mHaltRangeHint;
+ // Used when context has already been serialized for
+ // table cell selections (where parent is <tr>)
+ bool mDisableContextSerialize;
+ bool mIsCopying; // Set to true only while copying
+ bool mNodeIsContainer;
+ nsStringBuffer* mCachedBuffer;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocumentEncoder)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocumentEncoder)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsDocumentEncoder,
+ mDocument, mSelection, mRange, mNode, mCommonParent)
+
+nsDocumentEncoder::nsDocumentEncoder() : mCachedBuffer(nullptr)
+{
+ Initialize();
+ mMimeType.AssignLiteral("text/plain");
+}
+
+void nsDocumentEncoder::Initialize(bool aClearCachedSerializer)
+{
+ mFlags = 0;
+ mWrapColumn = 72;
+ mStartDepth = 0;
+ mEndDepth = 0;
+ mStartRootIndex = 0;
+ mEndRootIndex = 0;
+ mHaltRangeHint = false;
+ mDisableContextSerialize = false;
+ mNodeIsContainer = false;
+ if (aClearCachedSerializer) {
+ mSerializer = nullptr;
+ }
+}
+
+nsDocumentEncoder::~nsDocumentEncoder()
+{
+ if (mCachedBuffer) {
+ mCachedBuffer->Release();
+ }
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::Init(nsIDOMDocument* aDocument,
+ const nsAString& aMimeType,
+ uint32_t aFlags)
+{
+ if (!aDocument)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ return NativeInit(doc, aMimeType, aFlags);
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::NativeInit(nsIDocument* aDocument,
+ const nsAString& aMimeType,
+ uint32_t aFlags)
+{
+ if (!aDocument)
+ return NS_ERROR_INVALID_ARG;
+
+ Initialize(!mMimeType.Equals(aMimeType));
+
+ mDocument = aDocument;
+
+ mMimeType = aMimeType;
+
+ mFlags = aFlags;
+ mIsCopying = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetWrapColumn(uint32_t aWC)
+{
+ mWrapColumn = aWC;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetSelection(nsISelection* aSelection)
+{
+ mSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetRange(nsIDOMRange* aRange)
+{
+ mRange = static_cast<nsRange*>(aRange);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetNode(nsIDOMNode* aNode)
+{
+ mNodeIsContainer = false;
+ mNode = do_QueryInterface(aNode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetNativeNode(nsINode* aNode)
+{
+ mNodeIsContainer = false;
+ mNode = aNode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetContainerNode(nsIDOMNode *aContainer)
+{
+ mNodeIsContainer = true;
+ mNode = do_QueryInterface(aContainer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetNativeContainerNode(nsINode* aContainer)
+{
+ mNodeIsContainer = true;
+ mNode = aContainer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetCharset(const nsACString& aCharset)
+{
+ mCharset = aCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::GetMimeType(nsAString& aMimeType)
+{
+ aMimeType = mMimeType;
+ return NS_OK;
+}
+
+
+bool
+nsDocumentEncoder::IncludeInContext(nsINode *aNode)
+{
+ return false;
+}
+
+nsresult
+nsDocumentEncoder::SerializeNodeStart(nsINode* aNode,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr,
+ nsINode* aOriginalNode)
+{
+ if (!IsVisibleNode(aNode))
+ return NS_OK;
+
+ nsINode* node = nullptr;
+ nsCOMPtr<nsINode> fixedNodeKungfuDeathGrip;
+
+ // Caller didn't do fixup, so we'll do it ourselves
+ if (!aOriginalNode) {
+ aOriginalNode = aNode;
+ if (mNodeFixup) {
+ bool dummy;
+ nsCOMPtr<nsIDOMNode> domNodeIn = do_QueryInterface(aNode);
+ nsCOMPtr<nsIDOMNode> domNodeOut;
+ mNodeFixup->FixupNode(domNodeIn, &dummy, getter_AddRefs(domNodeOut));
+ fixedNodeKungfuDeathGrip = do_QueryInterface(domNodeOut);
+ node = fixedNodeKungfuDeathGrip;
+ }
+ }
+
+ // Either there was no fixed-up node,
+ // or the caller did fixup themselves and aNode is already fixed
+ if (!node)
+ node = aNode;
+
+ if (node->IsElement()) {
+ if ((mFlags & (nsIDocumentEncoder::OutputPreformatted |
+ nsIDocumentEncoder::OutputDropInvisibleBreak)) &&
+ nsLayoutUtils::IsInvisibleBreak(node)) {
+ return NS_OK;
+ }
+ Element* originalElement =
+ aOriginalNode && aOriginalNode->IsElement() ?
+ aOriginalNode->AsElement() : nullptr;
+ mSerializer->AppendElementStart(node->AsElement(),
+ originalElement, aStr);
+ return NS_OK;
+ }
+
+ switch (node->NodeType()) {
+ case nsIDOMNode::TEXT_NODE:
+ {
+ mSerializer->AppendText(static_cast<nsIContent*>(node),
+ aStartOffset, aEndOffset, aStr);
+ break;
+ }
+ case nsIDOMNode::CDATA_SECTION_NODE:
+ {
+ mSerializer->AppendCDATASection(static_cast<nsIContent*>(node),
+ aStartOffset, aEndOffset, aStr);
+ break;
+ }
+ case nsIDOMNode::PROCESSING_INSTRUCTION_NODE:
+ {
+ mSerializer->AppendProcessingInstruction(static_cast<nsIContent*>(node),
+ aStartOffset, aEndOffset, aStr);
+ break;
+ }
+ case nsIDOMNode::COMMENT_NODE:
+ {
+ mSerializer->AppendComment(static_cast<nsIContent*>(node),
+ aStartOffset, aEndOffset, aStr);
+ break;
+ }
+ case nsIDOMNode::DOCUMENT_TYPE_NODE:
+ {
+ mSerializer->AppendDoctype(static_cast<nsIContent*>(node), aStr);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDocumentEncoder::SerializeNodeEnd(nsINode* aNode,
+ nsAString& aStr)
+{
+ if (!IsVisibleNode(aNode))
+ return NS_OK;
+
+ if (aNode->IsElement()) {
+ mSerializer->AppendElementEnd(aNode->AsElement(), aStr);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode,
+ nsAString& aStr,
+ bool aDontSerializeRoot,
+ uint32_t aMaxLength)
+{
+ if (aMaxLength > 0 && aStr.Length() >= aMaxLength) {
+ return NS_OK;
+ }
+
+ if (!IsVisibleNode(aNode))
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+ bool serializeClonedChildren = false;
+ nsINode* maybeFixedNode = nullptr;
+
+ // Keep the node from FixupNode alive.
+ nsCOMPtr<nsINode> fixedNodeKungfuDeathGrip;
+ if (mNodeFixup) {
+ nsCOMPtr<nsIDOMNode> domNodeIn = do_QueryInterface(aNode);
+ nsCOMPtr<nsIDOMNode> domNodeOut;
+ mNodeFixup->FixupNode(domNodeIn, &serializeClonedChildren, getter_AddRefs(domNodeOut));
+ fixedNodeKungfuDeathGrip = do_QueryInterface(domNodeOut);
+ maybeFixedNode = fixedNodeKungfuDeathGrip;
+ }
+
+ if (!maybeFixedNode)
+ maybeFixedNode = aNode;
+
+ if ((mFlags & SkipInvisibleContent) &&
+ !(mFlags & OutputNonTextContentAsPlaceholder)) {
+ if (aNode->IsNodeOfType(nsINode::eCONTENT)) {
+ nsIFrame* frame = static_cast<nsIContent*>(aNode)->GetPrimaryFrame();
+ if (frame) {
+ bool isSelectable;
+ frame->IsSelectable(&isSelectable, nullptr);
+ if (!isSelectable){
+ aDontSerializeRoot = true;
+ }
+ }
+ }
+ }
+
+ if (!aDontSerializeRoot) {
+ int32_t endOffset = -1;
+ if (aMaxLength > 0) {
+ MOZ_ASSERT(aMaxLength >= aStr.Length());
+ endOffset = aMaxLength - aStr.Length();
+ }
+ rv = SerializeNodeStart(maybeFixedNode, 0, endOffset, aStr, aNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsINode* node = serializeClonedChildren ? maybeFixedNode : aNode;
+
+ for (nsINode* child = nsNodeUtils::GetFirstChildOfTemplateOrNode(node);
+ child;
+ child = child->GetNextSibling()) {
+ rv = SerializeToStringRecursive(child, aStr, false, aMaxLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aDontSerializeRoot) {
+ rv = SerializeNodeEnd(node, aStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return FlushText(aStr, false);
+}
+
+nsresult
+nsDocumentEncoder::SerializeToStringIterative(nsINode* aNode,
+ nsAString& aStr)
+{
+ nsresult rv;
+
+ nsINode* node = nsNodeUtils::GetFirstChildOfTemplateOrNode(aNode);
+ while (node) {
+ nsINode* current = node;
+ rv = SerializeNodeStart(current, 0, -1, aStr, current);
+ NS_ENSURE_SUCCESS(rv, rv);
+ node = nsNodeUtils::GetFirstChildOfTemplateOrNode(current);
+ while (!node && current && current != aNode) {
+ rv = SerializeNodeEnd(current, aStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Check if we have siblings.
+ node = current->GetNextSibling();
+ if (!node) {
+ // Perhaps parent node has siblings.
+ 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 && current != aNode &&
+ current->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+ DocumentFragment* frag = static_cast<DocumentFragment*>(current);
+ nsIContent* host = frag->GetHost();
+ if (host && host->IsHTMLElement(nsGkAtoms::_template)) {
+ current = host;
+ }
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+ConvertAndWrite(const nsAString& aString,
+ nsIOutputStream* aStream,
+ nsIUnicodeEncoder* aEncoder)
+{
+ NS_ENSURE_ARG_POINTER(aStream);
+ NS_ENSURE_ARG_POINTER(aEncoder);
+ nsresult rv;
+ int32_t charLength, startCharLength;
+ const nsPromiseFlatString& flat = PromiseFlatString(aString);
+ const char16_t* unicodeBuf = flat.get();
+ int32_t unicodeLength = aString.Length();
+ int32_t startLength = unicodeLength;
+
+ rv = aEncoder->GetMaxLength(unicodeBuf, unicodeLength, &charLength);
+ startCharLength = charLength;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!charLength) {
+ // Nothing to write. Besides, a length 0 string has an immutable buffer, so
+ // attempts to null-terminate it will crash.
+ return NS_OK;
+ }
+
+ nsAutoCString charXferString;
+ if (!charXferString.SetLength(charLength, fallible))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ char* charXferBuf = charXferString.BeginWriting();
+ nsresult convert_rv = NS_OK;
+
+ do {
+ unicodeLength = startLength;
+ charLength = startCharLength;
+
+ convert_rv = aEncoder->Convert(unicodeBuf, &unicodeLength, charXferBuf, &charLength);
+ NS_ENSURE_SUCCESS(convert_rv, convert_rv);
+
+ // Make sure charXferBuf is null-terminated before we call
+ // Write().
+
+ charXferBuf[charLength] = '\0';
+
+ uint32_t written;
+ rv = aStream->Write(charXferBuf, charLength, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the converter couldn't convert a chraacer we replace the
+ // character with a characre entity.
+ if (convert_rv == NS_ERROR_UENC_NOMAPPING) {
+ // Finishes the conversion.
+ // The converter has the possibility to write some extra data and flush its final state.
+ char finish_buf[33];
+ charLength = sizeof(finish_buf) - 1;
+ rv = aEncoder->Finish(finish_buf, &charLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure finish_buf is null-terminated before we call
+ // Write().
+
+ finish_buf[charLength] = '\0';
+
+ rv = aStream->Write(finish_buf, charLength, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString entString("&#");
+ if (NS_IS_HIGH_SURROGATE(unicodeBuf[unicodeLength - 1]) &&
+ unicodeLength < startLength && NS_IS_LOW_SURROGATE(unicodeBuf[unicodeLength])) {
+ entString.AppendInt(SURROGATE_TO_UCS4(unicodeBuf[unicodeLength - 1],
+ unicodeBuf[unicodeLength]));
+ unicodeLength += 1;
+ }
+ else
+ entString.AppendInt(unicodeBuf[unicodeLength - 1]);
+ entString.Append(';');
+
+ // Since entString is an nsAutoCString we know entString.get()
+ // returns a null-terminated string, so no need for extra
+ // null-termination before calling Write() here.
+
+ rv = aStream->Write(entString.get(), entString.Length(), &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ unicodeBuf += unicodeLength;
+ startLength -= unicodeLength;
+ }
+ } while (convert_rv == NS_ERROR_UENC_NOMAPPING);
+
+ return rv;
+}
+
+nsresult
+nsDocumentEncoder::FlushText(nsAString& aString, bool aForce)
+{
+ if (!mStream)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ if (aString.Length() > 1024 || aForce) {
+ rv = ConvertAndWrite(aString, mStream, mUnicodeEncoder);
+
+ aString.Truncate();
+ }
+
+ return rv;
+}
+
+#if 0 // This code is really fast at serializing a range, but unfortunately
+ // there are problems with it so we don't use it now, maybe later...
+static nsresult ChildAt(nsIDOMNode* aNode, int32_t aIndex, nsIDOMNode*& aChild)
+{
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
+
+ aChild = nullptr;
+
+ NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
+
+ nsIContent *child = content->GetChildAt(aIndex);
+
+ if (child)
+ return CallQueryInterface(child, &aChild);
+
+ return NS_OK;
+}
+
+static int32_t IndexOf(nsIDOMNode* aParent, nsIDOMNode* aChild)
+{
+ nsCOMPtr<nsIContent> parent(do_QueryInterface(aParent));
+ nsCOMPtr<nsIContent> child(do_QueryInterface(aChild));
+
+ if (!parent)
+ return -1;
+
+ return parent->IndexOf(child);
+}
+
+static inline int32_t GetIndex(nsTArray<int32_t>& aIndexArray)
+{
+ int32_t count = aIndexArray.Length();
+
+ if (count) {
+ return aIndexArray.ElementAt(count - 1);
+ }
+
+ return 0;
+}
+
+static nsresult GetNextNode(nsIDOMNode* aNode, nsTArray<int32_t>& aIndexArray,
+ nsIDOMNode*& aNextNode,
+ nsRangeIterationDirection& aDirection)
+{
+ bool hasChildren;
+
+ aNextNode = nullptr;
+
+ aNode->HasChildNodes(&hasChildren);
+
+ if (hasChildren && aDirection == kDirectionIn) {
+ ChildAt(aNode, 0, aNextNode);
+ NS_ENSURE_TRUE(aNextNode, NS_ERROR_FAILURE);
+
+ aIndexArray.AppendElement(0);
+
+ aDirection = kDirectionIn;
+ } else if (aDirection == kDirectionIn) {
+ aNextNode = aNode;
+
+ NS_ADDREF(aNextNode);
+
+ aDirection = kDirectionOut;
+ } else {
+ nsCOMPtr<nsIDOMNode> parent;
+
+ aNode->GetParentNode(getter_AddRefs(parent));
+ NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
+
+ int32_t count = aIndexArray.Length();
+
+ if (count) {
+ int32_t indx = aIndexArray.ElementAt(count - 1);
+
+ ChildAt(parent, indx + 1, aNextNode);
+
+ if (aNextNode)
+ aIndexArray.ElementAt(count - 1) = indx + 1;
+ else
+ aIndexArray.RemoveElementAt(count - 1);
+ } else {
+ int32_t indx = IndexOf(parent, aNode);
+
+ if (indx >= 0) {
+ ChildAt(parent, indx + 1, aNextNode);
+
+ if (aNextNode)
+ aIndexArray.AppendElement(indx + 1);
+ }
+ }
+
+ if (aNextNode) {
+ aDirection = kDirectionIn;
+ } else {
+ aDirection = kDirectionOut;
+
+ aNextNode = parent;
+
+ NS_ADDREF(aNextNode);
+ }
+ }
+
+ return NS_OK;
+}
+#endif
+
+static bool IsTextNode(nsINode *aNode)
+{
+ return aNode && aNode->IsNodeOfType(nsINode::eTEXT);
+}
+
+nsresult
+nsDocumentEncoder::SerializeRangeNodes(nsRange* aRange,
+ nsINode* aNode,
+ nsAString& aString,
+ int32_t aDepth)
+{
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
+ NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
+
+ if (!IsVisibleNode(aNode))
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ // get start and end nodes for this recursion level
+ nsCOMPtr<nsIContent> startNode, endNode;
+ {
+ int32_t start = mStartRootIndex - aDepth;
+ if (start >= 0 && (uint32_t)start <= mStartNodes.Length())
+ startNode = mStartNodes[start];
+
+ int32_t end = mEndRootIndex - aDepth;
+ if (end >= 0 && (uint32_t)end <= mEndNodes.Length())
+ endNode = mEndNodes[end];
+ }
+
+ if (startNode != content && endNode != content)
+ {
+ // node is completely contained in range. Serialize the whole subtree
+ // rooted by this node.
+ rv = SerializeToStringRecursive(aNode, aString, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ // due to implementation it is impossible for text node to be both start and end of
+ // range. We would have handled that case without getting here.
+ //XXXsmaug What does this all mean?
+ if (IsTextNode(aNode))
+ {
+ if (startNode == content)
+ {
+ int32_t startOffset = aRange->StartOffset();
+ rv = SerializeNodeStart(aNode, startOffset, -1, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ int32_t endOffset = aRange->EndOffset();
+ rv = SerializeNodeStart(aNode, 0, endOffset, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ else
+ {
+ if (aNode != mCommonParent)
+ {
+ if (IncludeInContext(aNode))
+ {
+ // halt the incrementing of mStartDepth/mEndDepth. This is
+ // so paste client will include this node in paste.
+ mHaltRangeHint = true;
+ }
+ if ((startNode == content) && !mHaltRangeHint) mStartDepth++;
+ if ((endNode == content) && !mHaltRangeHint) mEndDepth++;
+
+ // serialize the start of this node
+ rv = SerializeNodeStart(aNode, 0, -1, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // do some calculations that will tell us which children of this
+ // node are in the range.
+ nsIContent* childAsNode = nullptr;
+ int32_t startOffset = 0, endOffset = -1;
+ if (startNode == content && mStartRootIndex >= aDepth)
+ startOffset = mStartOffsets[mStartRootIndex - aDepth];
+ if (endNode == content && mEndRootIndex >= aDepth)
+ endOffset = mEndOffsets[mEndRootIndex - aDepth];
+ // generated content will cause offset values of -1 to be returned.
+ int32_t j;
+ uint32_t childCount = content->GetChildCount();
+
+ if (startOffset == -1) startOffset = 0;
+ if (endOffset == -1) endOffset = childCount;
+ else
+ {
+ // if we are at the "tip" of the selection, endOffset is fine.
+ // otherwise, we need to add one. This is because of the semantics
+ // of the offset list created by GetAncestorsAndOffsets(). The
+ // intermediate points on the list use the endOffset of the
+ // location of the ancestor, rather than just past it. So we need
+ // to add one here in order to include it in the children we serialize.
+ if (aNode != aRange->GetEndParent())
+ {
+ endOffset++;
+ }
+ }
+ // serialize the children of this node that are in the range
+ for (j=startOffset; j<endOffset; j++)
+ {
+ childAsNode = content->GetChildAt(j);
+
+ if ((j==startOffset) || (j==endOffset-1))
+ rv = SerializeRangeNodes(aRange, childAsNode, aString, aDepth+1);
+ else
+ rv = SerializeToStringRecursive(childAsNode, aString, false);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // serialize the end of this node
+ if (aNode != mCommonParent)
+ {
+ rv = SerializeNodeEnd(aNode, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsDocumentEncoder::SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
+ nsAString& aString)
+{
+ if (mDisableContextSerialize) {
+ return NS_OK;
+ }
+
+ AutoTArray<nsINode*, 8>* serializedContext = mRangeContexts.AppendElement();
+
+ int32_t i = aAncestorArray.Length(), j;
+ nsresult rv = NS_OK;
+
+ // currently only for table-related elements; see Bug 137450
+ j = GetImmediateContextCount(aAncestorArray);
+
+ while (i > 0) {
+ nsINode *node = aAncestorArray.ElementAt(--i);
+
+ if (!node)
+ break;
+
+ // Either a general inclusion or as immediate context
+ if (IncludeInContext(node) || i < j) {
+ rv = SerializeNodeStart(node, 0, -1, aString);
+ serializedContext->AppendElement(node);
+ if (NS_FAILED(rv))
+ break;
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsDocumentEncoder::SerializeRangeContextEnd(nsAString& aString)
+{
+ if (mDisableContextSerialize) {
+ return NS_OK;
+ }
+
+ MOZ_RELEASE_ASSERT(!mRangeContexts.IsEmpty(), "Tried to end context without starting one.");
+ AutoTArray<nsINode*, 8>& serializedContext = mRangeContexts.LastElement();
+
+ nsresult rv = NS_OK;
+ for (nsINode* node : Reversed(serializedContext)) {
+ rv = SerializeNodeEnd(node, aString);
+
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ mRangeContexts.RemoveElementAt(mRangeContexts.Length() - 1);
+ return rv;
+}
+
+nsresult
+nsDocumentEncoder::SerializeRangeToString(nsRange *aRange,
+ nsAString& aOutputString)
+{
+ if (!aRange || aRange->Collapsed())
+ return NS_OK;
+
+ mCommonParent = aRange->GetCommonAncestor();
+
+ if (!mCommonParent)
+ return NS_OK;
+
+ nsINode* startParent = aRange->GetStartParent();
+ NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE);
+ int32_t startOffset = aRange->StartOffset();
+
+ nsINode* endParent = aRange->GetEndParent();
+ NS_ENSURE_TRUE(endParent, NS_ERROR_FAILURE);
+ int32_t endOffset = aRange->EndOffset();
+
+ mStartDepth = mEndDepth = 0;
+ mCommonAncestors.Clear();
+ mStartNodes.Clear();
+ mStartOffsets.Clear();
+ mEndNodes.Clear();
+ mEndOffsets.Clear();
+
+ nsContentUtils::GetAncestors(mCommonParent, mCommonAncestors);
+ nsCOMPtr<nsIDOMNode> sp = do_QueryInterface(startParent);
+ nsContentUtils::GetAncestorsAndOffsets(sp, startOffset,
+ &mStartNodes, &mStartOffsets);
+ nsCOMPtr<nsIDOMNode> ep = do_QueryInterface(endParent);
+ nsContentUtils::GetAncestorsAndOffsets(ep, endOffset,
+ &mEndNodes, &mEndOffsets);
+
+ nsCOMPtr<nsIContent> commonContent = do_QueryInterface(mCommonParent);
+ mStartRootIndex = mStartNodes.IndexOf(commonContent);
+ mEndRootIndex = mEndNodes.IndexOf(commonContent);
+
+ nsresult rv = NS_OK;
+
+ rv = SerializeRangeContextStart(mCommonAncestors, aOutputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((startParent == endParent) && IsTextNode(startParent))
+ {
+ if (mFlags & SkipInvisibleContent) {
+ // Check that the parent is visible if we don't a frame.
+ // IsVisibleNode() will do it when there's a frame.
+ nsCOMPtr<nsIContent> content = do_QueryInterface(startParent);
+ if (content && !content->GetPrimaryFrame()) {
+ nsIContent* parent = content->GetParent();
+ if (!parent || !IsVisibleNode(parent))
+ return NS_OK;
+ }
+ }
+ rv = SerializeNodeStart(startParent, startOffset, endOffset, aOutputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = SerializeRangeNodes(aRange, mCommonParent, aOutputString, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = SerializeRangeContextEnd(aOutputString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::EncodeToString(nsAString& aOutputString)
+{
+ return EncodeToStringWithMaxLength(0, aOutputString);
+}
+
+static bool ParentIsTR(nsIContent* aContent) {
+ mozilla::dom::Element* parent = aContent->GetParentElement();
+ if (!parent) {
+ return false;
+ }
+ return parent->IsHTMLElement(nsGkAtoms::tr);
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
+ nsAString& aOutputString)
+{
+ MOZ_ASSERT(mRangeContexts.IsEmpty(), "Re-entrant call to nsDocumentEncoder.");
+ auto rangeContextGuard = MakeScopeExit([&] {
+ mRangeContexts.Clear();
+ });
+
+ if (!mDocument)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aOutputString.Truncate();
+
+ nsString output;
+ static const size_t bufferSize = 2048;
+ if (!mCachedBuffer) {
+ mCachedBuffer = nsStringBuffer::Alloc(bufferSize).take();
+ if (NS_WARN_IF(!mCachedBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ NS_ASSERTION(!mCachedBuffer->IsReadonly(),
+ "DocumentEncoder shouldn't keep reference to non-readonly buffer!");
+ static_cast<char16_t*>(mCachedBuffer->Data())[0] = char16_t(0);
+ mCachedBuffer->ToString(0, output, true);
+ // output owns the buffer now!
+ mCachedBuffer = nullptr;
+
+
+ if (!mSerializer) {
+ nsAutoCString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX);
+ AppendUTF16toUTF8(mMimeType, progId);
+
+ mSerializer = do_CreateInstance(progId.get());
+ NS_ENSURE_TRUE(mSerializer, NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ nsresult rv = NS_OK;
+
+ bool rewriteEncodingDeclaration = !(mSelection || mRange || mNode) && !(mFlags & OutputDontRewriteEncodingDeclaration);
+ mSerializer->Init(mFlags, mWrapColumn, mCharset.get(), mIsCopying, rewriteEncodingDeclaration);
+
+ if (mSelection) {
+ nsCOMPtr<nsIDOMRange> range;
+ int32_t i, count = 0;
+
+ rv = mSelection->GetRangeCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDOMNode> node, prevNode;
+ uint32_t firstRangeStartDepth = 0;
+ for (i = 0; i < count; i++) {
+ mSelection->GetRangeAt(i, getter_AddRefs(range));
+
+ // Bug 236546: newlines not added when copying table cells into clipboard
+ // Each selected cell shows up as a range containing a row with a single cell
+ // get the row, compare it to previous row and emit </tr><tr> as needed
+ // Bug 137450: Problem copying/pasting a table from a web page to Excel.
+ // Each separate block of <tr></tr> produced above will be wrapped by the
+ // immediate context. This assumes that you can't select cells that are
+ // multiple selections from two tables simultaneously.
+ range->GetStartContainer(getter_AddRefs(node));
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+ if (node != prevNode) {
+ nsCOMPtr<nsINode> p;
+ if (prevNode) {
+ p = do_QueryInterface(prevNode);
+ rv = SerializeNodeEnd(p, output);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr<nsIContent> content = do_QueryInterface(node);
+ if (content && content->IsHTMLElement(nsGkAtoms::tr) && !ParentIsTR(content)) {
+ nsINode* n = content;
+ if (!prevNode) {
+ // Went from a non-<tr> to a <tr>
+ mCommonAncestors.Clear();
+ nsContentUtils::GetAncestors(n->GetParentNode(), mCommonAncestors);
+ rv = SerializeRangeContextStart(mCommonAncestors, output);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Don't let SerializeRangeToString serialize the context again
+ mDisableContextSerialize = true;
+ }
+
+ rv = SerializeNodeStart(n, 0, -1, output);
+ NS_ENSURE_SUCCESS(rv, rv);
+ prevNode = node;
+ } else if (prevNode) {
+ // Went from a <tr> to a non-<tr>
+ mDisableContextSerialize = false;
+ rv = SerializeRangeContextEnd(output);
+ NS_ENSURE_SUCCESS(rv, rv);
+ prevNode = nullptr;
+ }
+ }
+
+ nsRange* r = static_cast<nsRange*>(range.get());
+ rv = SerializeRangeToString(r, output);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (i == 0) {
+ firstRangeStartDepth = mStartDepth;
+ }
+ }
+ mStartDepth = firstRangeStartDepth;
+
+ if (prevNode) {
+ nsCOMPtr<nsINode> p = do_QueryInterface(prevNode);
+ rv = SerializeNodeEnd(p, output);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDisableContextSerialize = false;
+ rv = SerializeRangeContextEnd(output);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Just to be safe
+ mDisableContextSerialize = false;
+
+ mSelection = nullptr;
+ } else if (mRange) {
+ rv = SerializeRangeToString(mRange, output);
+
+ mRange = nullptr;
+ } else if (mNode) {
+ if (!mNodeFixup && !(mFlags & SkipInvisibleContent) && !mStream &&
+ mNodeIsContainer) {
+ rv = SerializeToStringIterative(mNode, output);
+ } else {
+ rv = SerializeToStringRecursive(mNode, output, mNodeIsContainer);
+ }
+ mNode = nullptr;
+ } else {
+ rv = mSerializer->AppendDocumentStart(mDocument, output);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = SerializeToStringRecursive(mDocument, output, false, aMaxLength);
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mSerializer->Flush(output);
+
+ mCachedBuffer = nsStringBuffer::FromString(output);
+ // We have to be careful how we set aOutputString, because we don't
+ // want it to end up sharing mCachedBuffer if we plan to reuse it.
+ bool setOutput = false;
+ // Try to cache the buffer.
+ if (mCachedBuffer) {
+ if (mCachedBuffer->StorageSize() == bufferSize &&
+ !mCachedBuffer->IsReadonly()) {
+ mCachedBuffer->AddRef();
+ } else {
+ if (NS_SUCCEEDED(rv)) {
+ mCachedBuffer->ToString(output.Length(), aOutputString);
+ setOutput = true;
+ }
+ mCachedBuffer = nullptr;
+ }
+ }
+
+ if (!setOutput && NS_SUCCEEDED(rv)) {
+ aOutputString.Append(output.get(), output.Length());
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::EncodeToStream(nsIOutputStream* aStream)
+{
+ MOZ_ASSERT(mRangeContexts.IsEmpty(), "Re-entrant call to nsDocumentEncoder.");
+ auto rangeContextGuard = MakeScopeExit([&] {
+ mRangeContexts.Clear();
+ });
+ nsresult rv = NS_OK;
+
+ if (!mDocument)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(mCharset, encoding)) {
+ return NS_ERROR_UCONV_NOCONV;
+ }
+ mUnicodeEncoder = EncodingUtils::EncoderForEncoding(encoding);
+
+ if (mMimeType.LowerCaseEqualsLiteral("text/plain")) {
+ rv = mUnicodeEncoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nullptr, '?');
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mStream = aStream;
+
+ nsAutoString buf;
+
+ rv = EncodeToString(buf);
+
+ // Force a flush of the last chunk of data.
+ FlushText(buf, true);
+
+ mStream = nullptr;
+ mUnicodeEncoder = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::EncodeToStringWithContext(nsAString& aContextString,
+ nsAString& aInfoString,
+ nsAString& aEncodedString)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocumentEncoder::SetNodeFixup(nsIDocumentEncoderNodeFixup *aFixup)
+{
+ mNodeFixup = aFixup;
+ return NS_OK;
+}
+
+
+nsresult NS_NewTextEncoder(nsIDocumentEncoder** aResult); // make mac compiler happy
+
+nsresult
+NS_NewTextEncoder(nsIDocumentEncoder** aResult)
+{
+ *aResult = new nsDocumentEncoder;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+class nsHTMLCopyEncoder : public nsDocumentEncoder
+{
+public:
+
+ nsHTMLCopyEncoder();
+ virtual ~nsHTMLCopyEncoder();
+
+ NS_IMETHOD Init(nsIDOMDocument* aDocument, const nsAString& aMimeType, uint32_t aFlags);
+
+ // overridden methods from nsDocumentEncoder
+ NS_IMETHOD SetSelection(nsISelection* aSelection);
+ NS_IMETHOD EncodeToStringWithContext(nsAString& aContextString,
+ nsAString& aInfoString,
+ nsAString& aEncodedString);
+ NS_IMETHOD EncodeToString(nsAString& aOutputString);
+
+protected:
+
+ enum Endpoint
+ {
+ kStart,
+ kEnd
+ };
+
+ nsresult PromoteRange(nsIDOMRange *inRange);
+ nsresult PromoteAncestorChain(nsCOMPtr<nsIDOMNode> *ioNode,
+ int32_t *ioStartOffset,
+ int32_t *ioEndOffset);
+ nsresult GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset,
+ nsCOMPtr<nsIDOMNode> *outNode, int32_t *outOffset, nsIDOMNode *aCommon);
+ nsCOMPtr<nsIDOMNode> GetChildAt(nsIDOMNode *aParent, int32_t aOffset);
+ bool IsMozBR(nsIDOMNode* aNode);
+ nsresult GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr<nsIDOMNode> *outParent, int32_t *outOffset);
+ bool IsRoot(nsIDOMNode* aNode);
+ bool IsFirstNode(nsIDOMNode *aNode);
+ bool IsLastNode(nsIDOMNode *aNode);
+ bool IsEmptyTextContent(nsIDOMNode* aNode);
+ virtual bool IncludeInContext(nsINode *aNode);
+ virtual int32_t
+ GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray);
+
+ bool mIsTextWidget;
+};
+
+nsHTMLCopyEncoder::nsHTMLCopyEncoder()
+{
+ mIsTextWidget = false;
+}
+
+nsHTMLCopyEncoder::~nsHTMLCopyEncoder()
+{
+}
+
+NS_IMETHODIMP
+nsHTMLCopyEncoder::Init(nsIDOMDocument* aDocument,
+ const nsAString& aMimeType,
+ uint32_t aFlags)
+{
+ if (!aDocument)
+ return NS_ERROR_INVALID_ARG;
+
+ mIsTextWidget = false;
+ Initialize();
+
+ mIsCopying = true;
+ mDocument = do_QueryInterface(aDocument);
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
+
+ // Hack, hack! Traditionally, the caller passes text/unicode, which is
+ // treated as "guess text/html or text/plain" in this context. (It has a
+ // different meaning in other contexts. Sigh.) From now on, "text/plain"
+ // means forcing text/plain instead of guessing.
+ if (aMimeType.EqualsLiteral("text/plain")) {
+ mMimeType.AssignLiteral("text/plain");
+ } else {
+ mMimeType.AssignLiteral("text/html");
+ }
+
+ // Make all links absolute when copying
+ // (see related bugs #57296, #41924, #58646, #32768)
+ mFlags = aFlags | OutputAbsoluteLinks;
+
+ if (!mDocument->IsScriptEnabled())
+ mFlags |= OutputNoScriptContent;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLCopyEncoder::SetSelection(nsISelection* aSelection)
+{
+ // check for text widgets: we need to recognize these so that
+ // we don't tweak the selection to be outside of the magic
+ // div that ender-lite text widgets are embedded in.
+
+ if (!aSelection)
+ return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIDOMRange> range;
+ nsCOMPtr<nsIDOMNode> commonParent;
+ Selection* selection = aSelection->AsSelection();
+ uint32_t rangeCount = selection->RangeCount();
+
+ // if selection is uninitialized return
+ if (!rangeCount)
+ return NS_ERROR_FAILURE;
+
+ // we'll just use the common parent of the first range. Implicit assumption
+ // here that multi-range selections are table cell selections, in which case
+ // the common parent is somewhere in the table and we don't really care where.
+ nsresult rv = aSelection->GetRangeAt(0, getter_AddRefs(range));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!range)
+ return NS_ERROR_NULL_POINTER;
+ range->GetCommonAncestorContainer(getter_AddRefs(commonParent));
+
+ for (nsCOMPtr<nsIContent> selContent(do_QueryInterface(commonParent));
+ selContent;
+ selContent = selContent->GetParent())
+ {
+ // checking for selection inside a plaintext form widget
+ if (selContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea))
+ {
+ mIsTextWidget = true;
+ break;
+ }
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+ else if (selContent->IsHTMLElement(nsGkAtoms::body)) {
+ // Currently, setting mIsTextWidget to 'true' will result in the selection
+ // being encoded/copied as pre-formatted plain text.
+ // This is fine for copying pre-formatted plain text with Firefox, it is
+ // already not correct for copying pre-formatted "rich" text (bold, colour)
+ // with Firefox. As long as the serialisers aren't fixed, copying
+ // pre-formatted text in Firefox is broken. If we set mIsTextWidget,
+ // pre-formatted plain text is copied, but pre-formatted "rich" text loses
+ // the "rich" formatting. If we don't set mIsTextWidget, "rich" text
+ // attributes aren't lost, but white-space is lost.
+ // So far the story for Firefox.
+ //
+ // Thunderbird has two *conflicting* requirements.
+ // Case 1:
+ // When selecting and copying text, even pre-formatted text, as a quote
+ // to be placed into a reply, we *always* expect HTML to be copied.
+ // Case 2:
+ // When copying text in a so-called "plain text" message, that is
+ // one where the body carries style "white-space:pre-wrap", the text should
+ // be copied as pre-formatted plain text.
+ //
+ // Therefore the following code checks for "pre-wrap" on the body.
+ // This is a terrible hack.
+ //
+ // The proper fix would be this:
+ // For case 1:
+ // Communicate the fact that HTML is required to EncodeToString(),
+ // bug 1141786.
+ // For case 2:
+ // Wait for Firefox to get fixed to detect pre-formatting correctly,
+ // bug 1174452.
+ nsAutoString styleVal;
+ if (selContent->GetAttr(kNameSpaceID_None, nsGkAtoms::style, styleVal) &&
+ styleVal.Find(NS_LITERAL_STRING("pre-wrap")) != kNotFound) {
+ mIsTextWidget = true;
+ break;
+ }
+ }
+#endif
+ }
+
+ // normalize selection if we are not in a widget
+ if (mIsTextWidget)
+ {
+ mSelection = aSelection;
+ mMimeType.AssignLiteral("text/plain");
+ return NS_OK;
+ }
+
+ // XXX We should try to get rid of the Selection object here.
+ // XXX bug 1245883
+
+ // also consider ourselves in a text widget if we can't find an html document
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
+ if (!(htmlDoc && mDocument->IsHTMLDocument())) {
+ mIsTextWidget = true;
+ mSelection = aSelection;
+ // mMimeType is set to text/plain when encoding starts.
+ return NS_OK;
+ }
+
+ // there's no Clone() for selection! fix...
+ //nsresult rv = aSelection->Clone(getter_AddRefs(mSelection);
+ //NS_ENSURE_SUCCESS(rv, rv);
+ NS_NewDomSelection(getter_AddRefs(mSelection));
+ NS_ENSURE_TRUE(mSelection, NS_ERROR_FAILURE);
+
+ // loop thru the ranges in the selection
+ for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
+ range = selection->GetRangeAt(rangeIdx);
+ NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIDOMRange> myRange;
+ range->CloneRange(getter_AddRefs(myRange));
+ NS_ENSURE_TRUE(myRange, NS_ERROR_FAILURE);
+
+ // adjust range to include any ancestors who's children are entirely selected
+ rv = PromoteRange(myRange);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ErrorResult result;
+ nsRange* r = static_cast<nsRange*>(myRange.get());
+ mSelection->AsSelection()->AddRangeInternal(*r, mDocument, result);
+ rv = result.StealNSResult();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLCopyEncoder::EncodeToString(nsAString& aOutputString)
+{
+ if (mIsTextWidget) {
+ mMimeType.AssignLiteral("text/plain");
+ }
+ return nsDocumentEncoder::EncodeToString(aOutputString);
+}
+
+NS_IMETHODIMP
+nsHTMLCopyEncoder::EncodeToStringWithContext(nsAString& aContextString,
+ nsAString& aInfoString,
+ nsAString& aEncodedString)
+{
+ nsresult rv = EncodeToString(aEncodedString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // do not encode any context info or range hints if we are in a text widget.
+ if (mIsTextWidget) return NS_OK;
+
+ // now encode common ancestors into aContextString. Note that the common ancestors
+ // will be for the last range in the selection in the case of multirange selections.
+ // encoding ancestors every range in a multirange selection in a way that could be
+ // understood by the paste code would be a lot more work to do. As a practical matter,
+ // selections are single range, and the ones that aren't are table cell selections
+ // where all the cells are in the same table.
+
+ // leaf of ancestors might be text node. If so discard it.
+ int32_t count = mCommonAncestors.Length();
+ int32_t i;
+ nsCOMPtr<nsINode> node;
+ if (count > 0)
+ node = mCommonAncestors.ElementAt(0);
+
+ if (node && IsTextNode(node))
+ {
+ mCommonAncestors.RemoveElementAt(0);
+ // don't forget to adjust range depth info
+ if (mStartDepth) mStartDepth--;
+ if (mEndDepth) mEndDepth--;
+ // and the count
+ count--;
+ }
+
+ i = count;
+ while (i > 0)
+ {
+ node = mCommonAncestors.ElementAt(--i);
+ SerializeNodeStart(node, 0, -1, aContextString);
+ }
+ //i = 0; guaranteed by above
+ while (i < count)
+ {
+ node = mCommonAncestors.ElementAt(i++);
+ SerializeNodeEnd(node, aContextString);
+ }
+
+ // encode range info : the start and end depth of the selection, where the depth is
+ // distance down in the parent hierarchy. Later we will need to add leading/trailing
+ // whitespace info to this.
+ nsAutoString infoString;
+ infoString.AppendInt(mStartDepth);
+ infoString.Append(char16_t(','));
+ infoString.AppendInt(mEndDepth);
+ aInfoString = infoString;
+
+ return NS_OK;
+}
+
+
+bool
+nsHTMLCopyEncoder::IncludeInContext(nsINode *aNode)
+{
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
+
+ if (!content)
+ return false;
+
+ return content->IsAnyOfHTMLElements(nsGkAtoms::b,
+ nsGkAtoms::i,
+ nsGkAtoms::u,
+ nsGkAtoms::a,
+ nsGkAtoms::tt,
+ nsGkAtoms::s,
+ nsGkAtoms::big,
+ nsGkAtoms::small,
+ nsGkAtoms::strike,
+ nsGkAtoms::em,
+ nsGkAtoms::strong,
+ nsGkAtoms::dfn,
+ nsGkAtoms::code,
+ nsGkAtoms::cite,
+ nsGkAtoms::var,
+ nsGkAtoms::abbr,
+ nsGkAtoms::font,
+ nsGkAtoms::script,
+ nsGkAtoms::span,
+ nsGkAtoms::pre,
+ nsGkAtoms::h1,
+ nsGkAtoms::h2,
+ nsGkAtoms::h3,
+ nsGkAtoms::h4,
+ nsGkAtoms::h5,
+ nsGkAtoms::h6);
+}
+
+
+nsresult
+nsHTMLCopyEncoder::PromoteRange(nsIDOMRange *inRange)
+{
+ if (!inRange) return NS_ERROR_NULL_POINTER;
+ nsresult rv;
+ nsCOMPtr<nsIDOMNode> startNode, endNode, common;
+ int32_t startOffset, endOffset;
+
+ rv = inRange->GetCommonAncestorContainer(getter_AddRefs(common));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = inRange->GetStartContainer(getter_AddRefs(startNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = inRange->GetStartOffset(&startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = inRange->GetEndContainer(getter_AddRefs(endNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = inRange->GetEndOffset(&endOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDOMNode> opStartNode;
+ nsCOMPtr<nsIDOMNode> opEndNode;
+ int32_t opStartOffset, opEndOffset;
+
+ // examine range endpoints.
+ rv = GetPromotedPoint( kStart, startNode, startOffset, address_of(opStartNode), &opStartOffset, common);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetPromotedPoint( kEnd, endNode, endOffset, address_of(opEndNode), &opEndOffset, common);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if both range endpoints are at the common ancestor, check for possible inclusion of ancestors
+ if ( (opStartNode == common) && (opEndNode == common) )
+ {
+ rv = PromoteAncestorChain(address_of(opStartNode), &opStartOffset, &opEndOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ opEndNode = opStartNode;
+ }
+
+ // set the range to the new values
+ rv = inRange->SetStart(opStartNode, opStartOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = inRange->SetEnd(opEndNode, opEndOffset);
+ return rv;
+}
+
+
+// PromoteAncestorChain will promote a range represented by [{*ioNode,*ioStartOffset} , {*ioNode,*ioEndOffset}]
+// The promotion is different from that found in getPromotedPoint: it will only promote one endpoint if it can
+// promote the other. Thus, instead of having a startnode/endNode, there is just the one ioNode.
+nsresult
+nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr<nsIDOMNode> *ioNode,
+ int32_t *ioStartOffset,
+ int32_t *ioEndOffset)
+{
+ if (!ioNode || !ioStartOffset || !ioEndOffset) return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+ bool done = false;
+
+ nsCOMPtr<nsIDOMNode> frontNode, endNode, parent;
+ int32_t frontOffset, endOffset;
+
+ //save the editable state of the ioNode, so we don't promote an ancestor if it has different editable state
+ nsCOMPtr<nsINode> node = do_QueryInterface(*ioNode);
+ bool isEditable = node->IsEditable();
+
+ // loop for as long as we can promote both endpoints
+ while (!done)
+ {
+ rv = (*ioNode)->GetParentNode(getter_AddRefs(parent));
+ if ((NS_FAILED(rv)) || !parent)
+ done = true;
+ else
+ {
+ // passing parent as last param to GetPromotedPoint() allows it to promote only one level
+ // up the hierarchy.
+ rv = GetPromotedPoint( kStart, *ioNode, *ioStartOffset, address_of(frontNode), &frontOffset, parent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // then we make the same attempt with the endpoint
+ rv = GetPromotedPoint( kEnd, *ioNode, *ioEndOffset, address_of(endNode), &endOffset, parent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINode> frontINode = do_QueryInterface(frontNode);
+ // if both endpoints were promoted one level and isEditable is the same as the original node,
+ // keep looping - otherwise we are done.
+ if ( (frontNode != parent) || (endNode != parent) || (frontINode->IsEditable() != isEditable) )
+ done = true;
+ else
+ {
+ *ioNode = frontNode;
+ *ioStartOffset = frontOffset;
+ *ioEndOffset = endOffset;
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsHTMLCopyEncoder::GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset,
+ nsCOMPtr<nsIDOMNode> *outNode, int32_t *outOffset, nsIDOMNode *common)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIDOMNode> node = aNode;
+ nsCOMPtr<nsIDOMNode> parent = aNode;
+ int32_t offset = aOffset;
+ bool bResetPromotion = false;
+
+ // default values
+ *outNode = node;
+ *outOffset = offset;
+
+ if (common == node)
+ return NS_OK;
+
+ if (aWhere == kStart)
+ {
+ // some special casing for text nodes
+ nsCOMPtr<nsINode> t = do_QueryInterface(aNode);
+ if (IsTextNode(t))
+ {
+ // if not at beginning of text node, we are done
+ if (offset > 0)
+ {
+ // unless everything before us in just whitespace. NOTE: we need a more
+ // general solution that truly detects all cases of non-significant
+ // whitesace with no false alarms.
+ nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
+ nsAutoString text;
+ nodeAsText->SubstringData(0, offset, text);
+ text.CompressWhitespace();
+ if (!text.IsEmpty())
+ return NS_OK;
+ bResetPromotion = true;
+ }
+ // else
+ rv = GetNodeLocation(aNode, address_of(parent), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ node = GetChildAt(parent,offset);
+ }
+ if (!node) node = parent;
+
+ // finding the real start for this point. look up the tree for as long as we are the
+ // first node in the container, and as long as we haven't hit the body node.
+ if (!IsRoot(node) && (parent != common))
+ {
+ rv = GetNodeLocation(node, address_of(parent), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (offset == -1) return NS_OK; // we hit generated content; STOP
+ nsIParserService *parserService = nsContentUtils::GetParserService();
+ if (!parserService)
+ return NS_ERROR_OUT_OF_MEMORY;
+ while ((IsFirstNode(node)) && (!IsRoot(parent)) && (parent != common))
+ {
+ if (bResetPromotion)
+ {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(parent);
+ if (content && content->IsHTMLElement())
+ {
+ bool isBlock = false;
+ parserService->IsBlock(parserService->HTMLAtomTagToId(
+ content->NodeInfo()->NameAtom()), isBlock);
+ if (isBlock)
+ {
+ bResetPromotion = false;
+ }
+ }
+ }
+
+ node = parent;
+ rv = GetNodeLocation(node, address_of(parent), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (offset == -1) // we hit generated content; STOP
+ {
+ // back up a bit
+ parent = node;
+ offset = 0;
+ break;
+ }
+ }
+ if (bResetPromotion)
+ {
+ *outNode = aNode;
+ *outOffset = aOffset;
+ }
+ else
+ {
+ *outNode = parent;
+ *outOffset = offset;
+ }
+ return rv;
+ }
+ }
+
+ if (aWhere == kEnd)
+ {
+ // some special casing for text nodes
+ nsCOMPtr<nsINode> n = do_QueryInterface(aNode);
+ if (IsTextNode(n))
+ {
+ // if not at end of text node, we are done
+ uint32_t len = n->Length();
+ if (offset < (int32_t)len)
+ {
+ // unless everything after us in just whitespace. NOTE: we need a more
+ // general solution that truly detects all cases of non-significant
+ // whitespace with no false alarms.
+ nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
+ nsAutoString text;
+ nodeAsText->SubstringData(offset, len-offset, text);
+ text.CompressWhitespace();
+ if (!text.IsEmpty())
+ return NS_OK;
+ bResetPromotion = true;
+ }
+ rv = GetNodeLocation(aNode, address_of(parent), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ if (offset) offset--; // we want node _before_ offset
+ node = GetChildAt(parent,offset);
+ }
+ if (!node) node = parent;
+
+ // finding the real end for this point. look up the tree for as long as we are the
+ // last node in the container, and as long as we haven't hit the body node.
+ if (!IsRoot(node) && (parent != common))
+ {
+ rv = GetNodeLocation(node, address_of(parent), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (offset == -1) return NS_OK; // we hit generated content; STOP
+ nsIParserService *parserService = nsContentUtils::GetParserService();
+ if (!parserService)
+ return NS_ERROR_OUT_OF_MEMORY;
+ while ((IsLastNode(node)) && (!IsRoot(parent)) && (parent != common))
+ {
+ if (bResetPromotion)
+ {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(parent);
+ if (content && content->IsHTMLElement())
+ {
+ bool isBlock = false;
+ parserService->IsBlock(parserService->HTMLAtomTagToId(
+ content->NodeInfo()->NameAtom()), isBlock);
+ if (isBlock)
+ {
+ bResetPromotion = false;
+ }
+ }
+ }
+
+ node = parent;
+ rv = GetNodeLocation(node, address_of(parent), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (offset == -1) // we hit generated content; STOP
+ {
+ // back up a bit
+ parent = node;
+ offset = 0;
+ break;
+ }
+ }
+ if (bResetPromotion)
+ {
+ *outNode = aNode;
+ *outOffset = aOffset;
+ }
+ else
+ {
+ *outNode = parent;
+ offset++; // add one since this in an endpoint - want to be AFTER node.
+ *outOffset = offset;
+ }
+ return rv;
+ }
+ }
+
+ return rv;
+}
+
+nsCOMPtr<nsIDOMNode>
+nsHTMLCopyEncoder::GetChildAt(nsIDOMNode *aParent, int32_t aOffset)
+{
+ nsCOMPtr<nsIDOMNode> resultNode;
+
+ if (!aParent)
+ return resultNode;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aParent);
+ NS_PRECONDITION(content, "null content in nsHTMLCopyEncoder::GetChildAt");
+
+ resultNode = do_QueryInterface(content->GetChildAt(aOffset));
+
+ return resultNode;
+}
+
+bool
+nsHTMLCopyEncoder::IsMozBR(nsIDOMNode* aNode)
+{
+ MOZ_ASSERT(aNode);
+ nsCOMPtr<Element> element = do_QueryInterface(aNode);
+ return element &&
+ element->IsHTMLElement(nsGkAtoms::br) &&
+ element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ NS_LITERAL_STRING("_moz"), eIgnoreCase);
+}
+
+nsresult
+nsHTMLCopyEncoder::GetNodeLocation(nsIDOMNode *inChild,
+ nsCOMPtr<nsIDOMNode> *outParent,
+ int32_t *outOffset)
+{
+ NS_ASSERTION((inChild && outParent && outOffset), "bad args");
+ nsresult result = NS_ERROR_NULL_POINTER;
+ if (inChild && outParent && outOffset)
+ {
+ result = inChild->GetParentNode(getter_AddRefs(*outParent));
+ if ((NS_SUCCEEDED(result)) && (*outParent))
+ {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(*outParent);
+ nsCOMPtr<nsIContent> cChild = do_QueryInterface(inChild);
+ if (!cChild || !content)
+ return NS_ERROR_NULL_POINTER;
+
+ *outOffset = content->IndexOf(cChild);
+ }
+ }
+ return result;
+}
+
+bool
+nsHTMLCopyEncoder::IsRoot(nsIDOMNode* aNode)
+{
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
+ if (!content) {
+ return false;
+ }
+
+ if (mIsTextWidget) {
+ return content->IsHTMLElement(nsGkAtoms::div);
+ }
+
+ return content->IsAnyOfHTMLElements(nsGkAtoms::body,
+ nsGkAtoms::td,
+ nsGkAtoms::th);
+}
+
+bool
+nsHTMLCopyEncoder::IsFirstNode(nsIDOMNode *aNode)
+{
+ nsCOMPtr<nsIDOMNode> parent;
+ int32_t offset, j=0;
+ nsresult rv = GetNodeLocation(aNode, address_of(parent), &offset);
+ if (NS_FAILED(rv))
+ {
+ NS_NOTREACHED("failure in IsFirstNode");
+ return false;
+ }
+ if (offset == 0) // easy case, we are first dom child
+ return true;
+ if (!parent)
+ return true;
+
+ // need to check if any nodes before us are really visible.
+ // Mike wrote something for me along these lines in nsSelectionController,
+ // but I don't think it's ready for use yet - revisit.
+ // HACK: for now, simply consider all whitespace text nodes to be
+ // invisible formatting nodes.
+ nsCOMPtr<nsIDOMNodeList> childList;
+ nsCOMPtr<nsIDOMNode> child;
+
+ rv = parent->GetChildNodes(getter_AddRefs(childList));
+ if (NS_FAILED(rv) || !childList)
+ {
+ NS_NOTREACHED("failure in IsFirstNode");
+ return true;
+ }
+ while (j < offset)
+ {
+ childList->Item(j, getter_AddRefs(child));
+ if (!IsEmptyTextContent(child))
+ return false;
+ j++;
+ }
+ return true;
+}
+
+
+bool
+nsHTMLCopyEncoder::IsLastNode(nsIDOMNode *aNode)
+{
+ nsCOMPtr<nsIDOMNode> parent;
+ int32_t offset,j;
+ nsresult rv = GetNodeLocation(aNode, address_of(parent), &offset);
+ if (NS_FAILED(rv))
+ {
+ NS_NOTREACHED("failure in IsLastNode");
+ return false;
+ }
+ nsCOMPtr<nsINode> parentNode = do_QueryInterface(parent);
+ if (!parentNode) {
+ return true;
+ }
+
+ uint32_t numChildren = parentNode->Length();
+ if (offset+1 == (int32_t)numChildren) // easy case, we are last dom child
+ return true;
+ // need to check if any nodes after us are really visible.
+ // Mike wrote something for me along these lines in nsSelectionController,
+ // but I don't think it's ready for use yet - revisit.
+ // HACK: for now, simply consider all whitespace text nodes to be
+ // invisible formatting nodes.
+ j = (int32_t)numChildren-1;
+ nsCOMPtr<nsIDOMNodeList>childList;
+ nsCOMPtr<nsIDOMNode> child;
+ rv = parent->GetChildNodes(getter_AddRefs(childList));
+ if (NS_FAILED(rv) || !childList)
+ {
+ NS_NOTREACHED("failure in IsLastNode");
+ return true;
+ }
+ while (j > offset)
+ {
+ childList->Item(j, getter_AddRefs(child));
+ j--;
+ if (IsMozBR(child)) // we ignore trailing moz BRs.
+ continue;
+ if (!IsEmptyTextContent(child))
+ return false;
+ }
+ return true;
+}
+
+bool
+nsHTMLCopyEncoder::IsEmptyTextContent(nsIDOMNode* aNode)
+{
+ nsCOMPtr<nsIContent> cont = do_QueryInterface(aNode);
+ return cont && cont->TextIsOnlyWhitespace();
+}
+
+nsresult NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult); // make mac compiler happy
+
+nsresult
+NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult)
+{
+ *aResult = new nsHTMLCopyEncoder;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+int32_t
+nsHTMLCopyEncoder::GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray)
+{
+ int32_t i = aAncestorArray.Length(), j = 0;
+ while (j < i) {
+ nsINode *node = aAncestorArray.ElementAt(j);
+ if (!node) {
+ break;
+ }
+ nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+ if (!content ||
+ !content->IsAnyOfHTMLElements(nsGkAtoms::tr,
+ nsGkAtoms::thead,
+ nsGkAtoms::tbody,
+ nsGkAtoms::tfoot,
+ nsGkAtoms::table)) {
+ break;
+ }
+ ++j;
+ }
+ return j;
+}
diff --git a/dom/base/nsDocumentWarningList.h b/dom/base/nsDocumentWarningList.h
new file mode 100644
index 000000000..6572e6238
--- /dev/null
+++ b/dom/base/nsDocumentWarningList.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDocument.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)
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
new file mode 100644
index 000000000..fb350fa12
--- /dev/null
+++ b/dom/base/nsFocusManager.cpp
@@ -0,0 +1,3659 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/TabParent.h"
+
+#include "nsFocusManager.h"
+
+#include "AccessibleCaretEventHub.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsIDocument.h"
+#include "nsIEditor.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMRange.h"
+#include "nsIHTMLDocument.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIFormControl.h"
+#include "nsLayoutUtils.h"
+#include "nsIPresShell.h"
+#include "nsFrameTraversal.h"
+#include "nsIWebNavigation.h"
+#include "nsCaret.h"
+#include "nsIBaseWindow.h"
+#include "nsIXULWindow.h"
+#include "nsViewManager.h"
+#include "nsFrameSelection.h"
+#include "mozilla/dom/Selection.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIPrincipal.h"
+#include "nsIObserverService.h"
+#include "nsIObjectFrame.h"
+#include "nsBindingManager.h"
+#include "nsStyleCoord.h"
+#include "TabChild.h"
+#include "nsFrameLoader.h"
+#include "nsNumberControlFrame.h"
+
+#include "mozilla/ContentEvents.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include <algorithm>
+
+#ifdef MOZ_XUL
+#include "nsIDOMXULTextboxElement.h"
+#include "nsIDOMXULMenuListElement.h"
+#endif
+
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
+#ifndef XP_MACOSX
+#include "nsIScriptError.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(NS_LITERAL_CSTRING("(none)")); \
+ 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,
+ nsIPresShell* aPresShell,
+ nsIDocument* 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)
+ {
+ }
+
+ nsCOMPtr<nsIPresShell> mPresShell;
+ nsCOMPtr<nsIDocument> 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, 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(nsFocusManager,
+ mActiveWindow,
+ mFocusedWindow,
+ mFocusedContent,
+ mFirstBlurEvent,
+ mFirstFocusEvent,
+ mWindowBeingLowered,
+ mDelayedBlurFocusEvents,
+ mMouseButtonEventHandlingDocument)
+
+nsFocusManager* nsFocusManager::sInstance = nullptr;
+bool nsFocusManager::sMouseFocusesFormControl = false;
+bool nsFocusManager::sTestMode = false;
+
+static const char* kObservedPrefs[] = {
+ "accessibility.browsewithcaret",
+ "accessibility.tabfocus_applies_to_xul",
+ "accessibility.mouse_focuses_formcontrol",
+ "focusmanager.testmode",
+ nullptr
+};
+
+nsFocusManager::nsFocusManager()
+{ }
+
+nsFocusManager::~nsFocusManager()
+{
+ Preferences::RemoveObservers(this, kObservedPrefs);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "xpcom-shutdown");
+ }
+}
+
+// static
+nsresult
+nsFocusManager::Init()
+{
+ nsFocusManager* fm = new nsFocusManager();
+ NS_ADDREF(fm);
+ sInstance = fm;
+
+ nsIContent::sTabFocusModelAppliesToXUL =
+ Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
+ nsIContent::sTabFocusModelAppliesToXUL);
+
+ sMouseFocusesFormControl =
+ Preferences::GetBool("accessibility.mouse_focuses_formcontrol", false);
+
+ sTestMode = Preferences::GetBool("focusmanager.testmode", false);
+
+ Preferences::AddWeakObservers(fm, kObservedPrefs);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(fm, "xpcom-shutdown", true);
+ }
+
+ return NS_OK;
+}
+
+// static
+void
+nsFocusManager::Shutdown()
+{
+ NS_IF_RELEASE(sInstance);
+}
+
+NS_IMETHODIMP
+nsFocusManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsDependentString data(aData);
+ if (data.EqualsLiteral("accessibility.browsewithcaret")) {
+ UpdateCaretForCaretBrowsingMode();
+ }
+ else if (data.EqualsLiteral("accessibility.tabfocus_applies_to_xul")) {
+ nsIContent::sTabFocusModelAppliesToXUL =
+ Preferences::GetBool("accessibility.tabfocus_applies_to_xul",
+ nsIContent::sTabFocusModelAppliesToXUL);
+ }
+ else if (data.EqualsLiteral("accessibility.mouse_focuses_formcontrol")) {
+ sMouseFocusesFormControl =
+ Preferences::GetBool("accessibility.mouse_focuses_formcontrol",
+ false);
+ }
+ else if (data.EqualsLiteral("focusmanager.testmode")) {
+ sTestMode = Preferences::GetBool("focusmanager.testmode", false);
+ }
+ } else if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ mActiveWindow = nullptr;
+ mFocusedWindow = nullptr;
+ mFocusedContent = nullptr;
+ mFirstBlurEvent = nullptr;
+ mFirstFocusEvent = nullptr;
+ mWindowBeingLowered = nullptr;
+ mDelayedBlurFocusEvents.Clear();
+ mMouseButtonEventHandlingDocument = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// given a frame content node, retrieve the nsIDOMWindow displayed in it
+static nsPIDOMWindowOuter*
+GetContentWindow(nsIContent* aContent)
+{
+ nsIDocument* doc = aContent->GetComposedDoc();
+ if (doc) {
+ nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
+ if (subdoc)
+ return subdoc->GetWindow();
+ }
+
+ return nullptr;
+}
+
+// get the current window for the given content node
+static nsPIDOMWindowOuter*
+GetCurrentWindow(nsIContent* aContent)
+{
+ nsIDocument* doc = aContent->GetComposedDoc();
+ return doc ? doc->GetWindow() : nullptr;
+}
+
+// static
+nsIContent*
+nsFocusManager::GetFocusedDescendant(nsPIDOMWindowOuter* aWindow, bool aDeep,
+ nsPIDOMWindowOuter** aFocusedWindow)
+{
+ NS_ENSURE_TRUE(aWindow, nullptr);
+
+ *aFocusedWindow = nullptr;
+
+ nsIContent* currentContent = nullptr;
+ nsPIDOMWindowOuter* window = aWindow;
+ while (window) {
+ *aFocusedWindow = window;
+ currentContent = window->GetFocusedNode();
+ if (!currentContent || !aDeep)
+ break;
+
+ window = GetContentWindow(currentContent);
+ }
+
+ NS_IF_ADDREF(*aFocusedWindow);
+
+ return currentContent;
+}
+
+// static
+nsIContent*
+nsFocusManager::GetRedirectedFocus(nsIContent* aContent)
+{
+ // For input number, redirect focus to our anonymous text control.
+ if (aContent->IsHTMLElement(nsGkAtoms::input)) {
+ bool typeIsNumber =
+ static_cast<dom::HTMLInputElement*>(aContent)->GetType() ==
+ NS_FORM_INPUT_NUMBER;
+
+ if (typeIsNumber) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(aContent->GetPrimaryFrame());
+
+ if (numberControlFrame) {
+ HTMLInputElement* textControl =
+ numberControlFrame->GetAnonTextControl();
+ return static_cast<nsIContent*>(textControl);
+ }
+ }
+ }
+
+#ifdef MOZ_XUL
+ if (aContent->IsXULElement()) {
+ nsCOMPtr<nsIDOMNode> inputField;
+
+ nsCOMPtr<nsIDOMXULTextBoxElement> textbox = do_QueryInterface(aContent);
+ if (textbox) {
+ textbox->GetInputField(getter_AddRefs(inputField));
+ }
+ else {
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aContent);
+ if (menulist) {
+ menulist->GetInputField(getter_AddRefs(inputField));
+ }
+ else if (aContent->IsXULElement(nsGkAtoms::scale)) {
+ nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
+ if (!doc)
+ return nullptr;
+
+ nsINodeList* children = doc->BindingManager()->GetAnonymousNodesFor(aContent);
+ if (children) {
+ nsIContent* child = children->Item(0);
+ if (child && child->IsXULElement(nsGkAtoms::slider))
+ return child;
+ }
+ }
+ }
+
+ if (inputField) {
+ nsCOMPtr<nsIContent> retval = do_QueryInterface(inputField);
+ return retval;
+ }
+ }
+#endif
+
+ return nullptr;
+}
+
+// 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;
+ }
+ return InputContextAction::CAUSE_UNKNOWN;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetActiveWindow(mozIDOMWindowProxy** aWindow)
+{
+ NS_IF_ADDREF(*aWindow = mActiveWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::SetActiveWindow(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_STATE(aWindow);
+
+ // only top-level windows can be made active
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aWindow);
+ NS_ENSURE_TRUE(piWindow == piWindow->GetPrivateRoot(), NS_ERROR_INVALID_ARG);
+
+ RaiseWindow(piWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow)
+{
+ NS_IF_ADDREF(*aFocusedWindow = mFocusedWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFocusManager::SetFocusedWindow(mozIDOMWindowProxy* aWindowToFocus)
+{
+ LOGFOCUS(("<<SetFocusedWindow begin>>"));
+
+ nsCOMPtr<nsPIDOMWindowOuter> windowToFocus = nsPIDOMWindowOuter::From(aWindowToFocus);
+ NS_ENSURE_TRUE(windowToFocus, NS_ERROR_FAILURE);
+
+ windowToFocus = windowToFocus->GetOuterWindow();
+
+ nsCOMPtr<Element> frameElement = windowToFocus->GetFrameElementInternal();
+ if (frameElement) {
+ // pass false for aFocusChanged so that the caret does not get updated
+ // and scrolling does not occur.
+ 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->GetFocusedNode();
+ if (content) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> childWindow = GetContentWindow(content))
+ ClearFocus(windowToFocus);
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = windowToFocus->GetPrivateRoot();
+ if (rootWindow)
+ RaiseWindow(rootWindow);
+
+ LOGFOCUS(("<<SetFocusedWindow end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedElement(nsIDOMElement** aFocusedElement)
+{
+ if (mFocusedContent)
+ CallQueryInterface(mFocusedContent, aFocusedElement);
+ else
+ *aFocusedElement = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetLastFocusMethod(mozIDOMWindowProxy* aWindow, uint32_t* aLastFocusMethod)
+{
+ // the focus method is stored on the inner window
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (aWindow) {
+ window = nsPIDOMWindowOuter::From(aWindow);
+ }
+ if (!window)
+ window = mFocusedWindow;
+
+ *aLastFocusMethod = window ? window->GetFocusMethod() : 0;
+
+ NS_ASSERTION((*aLastFocusMethod & FOCUSMETHOD_MASK) == *aLastFocusMethod,
+ "invalid focus method");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::SetFocus(nsIDOMElement* aElement, uint32_t aFlags)
+{
+ LOGFOCUS(("<<SetFocus begin>>"));
+
+ nsCOMPtr<nsIContent> newFocus = do_QueryInterface(aElement);
+ NS_ENSURE_ARG(newFocus);
+
+ SetFocusInner(newFocus, aFlags, true, true);
+
+ LOGFOCUS(("<<SetFocus end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::ElementIsFocusable(nsIDOMElement* aElement, uint32_t aFlags,
+ bool* aIsFocusable)
+{
+ NS_ENSURE_TRUE(aElement, NS_ERROR_INVALID_ARG);
+
+ nsCOMPtr<nsIContent> aContent = do_QueryInterface(aElement);
+
+ *aIsFocusable = CheckIfFocusable(aContent, aFlags) != nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::MoveFocus(mozIDOMWindowProxy* aWindow, nsIDOMElement* aStartElement,
+ uint32_t aType, uint32_t aFlags, nsIDOMElement** aElement)
+{
+ *aElement = nullptr;
+
+ LOGFOCUS(("<<MoveFocus begin Type: %d Flags: %x>>", aType, aFlags));
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug) && mFocusedWindow) {
+ nsIDocument* doc = mFocusedWindow->GetExtantDoc();
+ if (doc && doc->GetDocumentURI()) {
+ LOGFOCUS((" Focused Window: %p %s",
+ mFocusedWindow.get(),
+ doc->GetDocumentURI()->GetSpecOrDefault().get()));
+ }
+ }
+
+ LOGCONTENT(" Current Focus: %s", mFocusedContent.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 & FOCUSMETHOD_MASK) == 0) {
+ aFlags |= FLAG_BYMOVEFOCUS;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ nsCOMPtr<nsIContent> startContent;
+ if (aStartElement) {
+ startContent = do_QueryInterface(aStartElement);
+ NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG);
+
+ window = GetCurrentWindow(startContent);
+ }
+ else {
+ window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+ }
+
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
+ nsCOMPtr<nsIContent> newFocus;
+ nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal,
+ 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) {
+ // 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(newFocus, aFlags, aType != MOVEFOCUS_CARET, true);
+ CallQueryInterface(newFocus, aElement);
+ }
+ 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, mFocusedWindow)) {
+ bool isAncestor = (window != mFocusedWindow);
+ if (Blur(window, nullptr, isAncestor, true)) {
+ // 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);
+ }
+ }
+ else {
+ window->SetFocusedNode(nullptr);
+ }
+
+ LOGFOCUS(("<<ClearFocus end>>"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::GetFocusedElementForWindow(mozIDOMWindowProxy* aWindow,
+ bool aDeep,
+ mozIDOMWindowProxy** aFocusedWindow,
+ nsIDOMElement** aElement)
+{
+ *aElement = nullptr;
+ if (aFocusedWindow)
+ *aFocusedWindow = nullptr;
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsCOMPtr<nsIContent> focusedContent =
+ GetFocusedDescendant(window, aDeep, getter_AddRefs(focusedWindow));
+ if (focusedContent)
+ CallQueryInterface(focusedContent, 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;
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ nsCOMPtr<nsIContent> content = window->GetFocusedNode();
+ if (content)
+ MoveCaretToFocus(presShell, content);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::WindowRaised(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Raised [Currently: %p %p]", aWindow, mActiveWindow.get(), mFocusedWindow.get()));
+ nsIDocument* 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 (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();
+ return NS_OK;
+ }
+
+ // lower the existing window, if any. This shouldn't happen usually.
+ if (mActiveWindow)
+ WindowLowered(mActiveWindow);
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = window->GetDocShell();
+ // If there's no docShellAsItem, this window must have been closed,
+ // in that case there is no tree owner.
+ NS_ENSURE_TRUE(docShellAsItem, NS_OK);
+
+ // set this as the active window
+ mActiveWindow = window;
+
+ // 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 NS_ERROR_FAILURE;
+ }
+
+ if (!sTestMode) {
+ baseWindow->SetVisibility(true);
+ }
+ }
+
+ // If this is a parent or single process window, send the activate event.
+ // Events for child process windows will be sent when ParentActivated
+ // is called.
+ if (XRE_IsParentProcess()) {
+ ActivateOrDeactivate(window, true);
+ }
+
+ // retrieve the last focused element within the window that was raised
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
+ nsCOMPtr<nsIContent> currentFocus =
+ GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
+
+ NS_ASSERTION(currentWindow, "window raised with no window current");
+ if (!currentWindow)
+ return NS_OK;
+
+ // If there is no nsIXULWindow, then this is an embedded or child process window.
+ // Pass false for aWindowRaised so that commands get updated.
+ nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(baseWindow));
+ Focus(currentWindow, currentFocus, 0, true, false, xulWin != nullptr, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::WindowLowered(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ 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()));
+ nsIDocument* 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 (mActiveWindow != window)
+ return NS_OK;
+
+ // clear the mouse capture as the active window has changed
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+
+ // 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) {
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (presShell) {
+ RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection();
+ frameSelection->SetDragState(false);
+ }
+ }
+ }
+
+ // If this is a parent or single process window, send the deactivate event.
+ // Events for child process windows will be sent when ParentActivated
+ // is called.
+ 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 = mActiveWindow;
+ mActiveWindow = nullptr;
+
+ if (mFocusedWindow)
+ Blur(nullptr, nullptr, true, true);
+
+ mWindowBeingLowered = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+nsFocusManager::ContentRemoved(nsIDocument* 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 ancestor
+ // of the currently focused element, reset the focus within that window.
+ nsIContent* content = window->GetFocusedNode();
+ if (content && nsContentUtils::ContentIsDescendantOf(content, aContent)) {
+ bool shouldShowFocusRing = window->ShouldShowFocusRing();
+ window->SetFocusedNode(nullptr);
+
+ // if this window is currently focused, clear the global focused
+ // element as well, but don't fire any events.
+ if (window == mFocusedWindow) {
+ mFocusedContent = nullptr;
+ } else {
+ // 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.
+ nsIDocument* subdoc = aDocument->GetSubDocumentFor(content);
+ if (subdoc) {
+ nsCOMPtr<nsIDocShell> docShell = subdoc->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> childWindow = docShell->GetWindow();
+ if (childWindow && IsSameOrAncestor(childWindow, mFocusedWindow)) {
+ ClearFocus(mActiveWindow);
+ }
+ }
+ }
+ }
+
+ // Notify the editor in case we removed its ancestor limiter.
+ if (content->IsEditable()) {
+ nsCOMPtr<nsIDocShell> docShell = aDocument->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsIEditor> editor;
+ docShell->GetEditor(getter_AddRefs(editor));
+ if (editor) {
+ nsCOMPtr<nsISelection> s;
+ editor->GetSelection(getter_AddRefs(s));
+ nsCOMPtr<nsISelectionPrivate> selection = do_QueryInterface(s);
+ if (selection) {
+ nsCOMPtr<nsIContent> limiter;
+ selection->GetAncestorLimiter(getter_AddRefs(limiter));
+ if (limiter == content) {
+ editor->FinalizeSelection();
+ }
+ }
+ }
+ }
+ }
+
+ NotifyFocusStateChange(content, shouldShowFocusRing, false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus)
+{
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ 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()));
+ nsIDocument* 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 (nsIDocShell* docShell = window->GetDocShell()) {
+ if (nsCOMPtr<nsITabChild> child = docShell->GetTabChild()) {
+ bool active = static_cast<TabChild*>(child.get())->ParentIsActive();
+ ActivateOrDeactivate(window, active);
+ }
+ }
+
+ if (mFocusedWindow != window)
+ return NS_OK;
+
+ if (aNeedsFocus) {
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow;
+ nsCOMPtr<nsIContent> currentFocus =
+ GetFocusedDescendant(window, true, getter_AddRefs(currentWindow));
+ if (currentWindow)
+ Focus(currentWindow, currentFocus, 0, true, false, false, true);
+ }
+ 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();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow)
+{
+ // 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.
+
+ NS_ENSURE_TRUE(aWindow, NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ LOGFOCUS(("Window %p Hidden [Currently: %p %p]", window.get(), mActiveWindow.get(), mFocusedWindow.get()));
+ nsAutoCString spec;
+ nsIDocument* 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 NS_OK;
+
+ // 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.
+
+ nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent.forget();
+
+ nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
+ nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
+
+ if (oldFocusedContent && oldFocusedContent->IsInComposedDoc()) {
+ NotifyFocusStateChange(oldFocusedContent,
+ mFocusedWindow->ShouldShowFocusRing(),
+ false);
+ window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+
+ if (presShell) {
+ SendFocusOrBlurEvent(eBlur, presShell,
+ oldFocusedContent->GetComposedDoc(),
+ oldFocusedContent, 1, false);
+ }
+ }
+
+ nsPresContext* focusedPresContext =
+ presShell ? presShell->GetPresContext() : nullptr;
+ IMEStateManager::OnChangeFocus(focusedPresContext, nullptr,
+ GetFocusMoveActionCause(0));
+ if (presShell) {
+ SetCaretVisible(presShell, false, nullptr);
+ }
+
+ // 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.
+ nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
+ 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.
+ NS_ASSERTION(mFocusedWindow->IsOuterWindow(), "outer window expected");
+ if (mActiveWindow == mFocusedWindow || mActiveWindow == window)
+ WindowLowered(mActiveWindow);
+ else
+ ClearFocus(mActiveWindow);
+ return NS_OK;
+ }
+
+ // 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->GetParent(getter_AddRefs(parentDsti));
+ if (parentDsti) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> parentWindow = parentDsti->GetWindow())
+ parentWindow->SetFocusedNode(nullptr);
+ }
+ }
+
+ SetFocusedWindowInternal(window);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::FireDelayedEvents(nsIDocument* aDocument)
+{
+ NS_ENSURE_ARG(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 nsDocument.cpp:FireOrClearDelayedEvents.
+ mDelayedBlurFocusEvents.RemoveElementAt(i);
+ --i;
+ } else if (!aDocument->EventHandlingSuppressed()) {
+ EventMessage message = mDelayedBlurFocusEvents[i].mEventMessage;
+ nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
+ nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
+ nsCOMPtr<EventTarget> relatedTarget = mDelayedBlurFocusEvents[i].mRelatedTarget;
+ mDelayedBlurFocusEvents.RemoveElementAt(i);
+ SendFocusOrBlurEvent(message, presShell, aDocument, target, 0,
+ false, false, relatedTarget);
+ --i;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::FocusPlugin(nsIContent* aContent)
+{
+ NS_ENSURE_ARG(aContent);
+ SetFocusInner(aContent, 0, true, false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFocusManager::ParentActivated(mozIDOMWindowProxy* aWindow, bool aActive)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_INVALID_ARG);
+
+ ActivateOrDeactivate(window, aActive);
+ return NS_OK;
+}
+
+/* static */
+void
+nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
+ bool aWindowShouldShowFocusRing,
+ bool aGettingFocus)
+{
+ if (!aContent->IsElement()) {
+ return;
+ }
+ EventStates eventState = NS_EVENT_STATE_FOCUS;
+ if (aWindowShouldShowFocusRing) {
+ eventState |= NS_EVENT_STATE_FOCUSRING;
+ }
+
+ if (aGettingFocus) {
+ aContent->AsElement()->AddStates(eventState);
+ } else {
+ aContent->AsElement()->RemoveStates(eventState);
+ }
+
+ for (Element* element = aContent->AsElement(); element;
+ element = element->GetParentElementCrossingShadowRoot()) {
+ if (aGettingFocus) {
+ element->AddStates(NS_EVENT_STATE_FOCUS_WITHIN);
+ } else {
+ element->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN);
+ }
+ }
+}
+
+// static
+void
+nsFocusManager::EnsureCurrentWidgetFocused()
+{
+ 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) {
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (presShell) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(false);
+ }
+ }
+ }
+}
+
+bool
+ActivateOrDeactivateChild(TabParent* aParent, void* aArg)
+{
+ bool active = static_cast<bool>(aArg);
+ Unused << aParent->SendParentActivated(active);
+ return false;
+}
+
+void
+nsFocusManager::ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive)
+{
+ if (!aWindow) {
+ return;
+ }
+
+ // Inform the DOM window that it has activated or deactivated, so that
+ // the active attribute is updated on the window.
+ aWindow->ActivateOrDeactivate(aActive);
+
+ // Send the activate event.
+ if (aWindow->GetExtantDoc()) {
+ nsContentUtils::DispatchEventOnlyToChrome(aWindow->GetExtantDoc(),
+ aWindow->GetCurrentInnerWindow(),
+ aActive ?
+ NS_LITERAL_STRING("activate") :
+ NS_LITERAL_STRING("deactivate"),
+ true, true, nullptr);
+ }
+
+ // Look for any remote child frames, iterate over them and send the activation notification.
+ nsContentUtils::CallOnAllRemoteChildren(aWindow, ActivateOrDeactivateChild,
+ (void *)aActive);
+}
+
+void
+nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
+ bool aFocusChanged, bool aAdjustWidget)
+{
+ // if the element is not focusable, just return and leave the focus as is
+ nsCOMPtr<nsIContent> contentToFocus = CheckIfFocusable(aNewContent, aFlags);
+ if (!contentToFocus)
+ return;
+
+ // 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(contentToFocus);
+ if (subWindow) {
+ contentToFocus = GetFocusedDescendant(subWindow, true, 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(contentToFocus);
+
+ // 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 || (newWindow == mFocusedWindow && contentToFocus == mFocusedContent))
+ return;
+
+ // 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;
+
+ bool beingDestroyed;
+ docShell->IsBeingDestroyed(&beingDestroyed);
+ if (beingDestroyed)
+ return;
+
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ docShell->GetParent(getter_AddRefs(parentDsti));
+ docShell = do_QueryInterface(parentDsti);
+ }
+
+ // if the new element is in the same window as the currently focused element
+ bool isElementInFocusedWindow = (mFocusedWindow == newWindow);
+
+ if (!isElementInFocusedWindow && mFocusedWindow && newWindow &&
+ nsContentUtils::IsHandlingKeyBoardEvent()) {
+ nsCOMPtr<nsIScriptObjectPrincipal> focused =
+ do_QueryInterface(mFocusedWindow);
+ nsCOMPtr<nsIScriptObjectPrincipal> newFocus =
+ do_QueryInterface(newWindow);
+ nsIPrincipal* focusedPrincipal = focused->GetPrincipal();
+ nsIPrincipal* newPrincipal = newFocus->GetPrincipal();
+ if (!focusedPrincipal || !newPrincipal) {
+ return;
+ }
+ bool subsumes = false;
+ focusedPrincipal->Subsumes(newPrincipal, &subsumes);
+ if (!subsumes && !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ NS_WARNING("Not allowed to focus the new window!");
+ return;
+ }
+ }
+
+ // 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.
+ bool isElementInActiveWindow = false;
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti = newWindow->GetDocShell();
+ nsCOMPtr<nsPIDOMWindowOuter> newRootWindow;
+ if (dsti) {
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ dsti->GetRootTreeItem(getter_AddRefs(root));
+ newRootWindow = root ? root->GetWindow() : nullptr;
+
+ isElementInActiveWindow = (mActiveWindow && newRootWindow == mActiveWindow);
+ }
+
+ // 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 (contentToFocus &&
+ nsContentUtils::
+ GetRootDocument(contentToFocus->OwnerDoc())->GetFullscreenElement() &&
+ nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM"),
+ contentToFocus->OwnerDoc(),
+ nsContentUtils::eDOM_PROPERTIES,
+ "FocusedWindowedPluginWhileFullscreen");
+ nsIDocument::AsyncExitFullscreen(contentToFocus->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, mFocusedWindow);
+
+ // 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);
+
+ // When the following conditions are true:
+ // * an element has focus
+ // * isn't called by trusted event (i.e., called by untrusted event or by js)
+ // * the focus is moved to another document's element
+ // we need to check the permission.
+ if (sendFocusEvent && mFocusedContent && !nsContentUtils::LegacyIsCallerNativeCode() &&
+ mFocusedContent->OwnerDoc() != aNewContent->OwnerDoc()) {
+ // If the caller cannot access the current focused node, the caller should
+ // not be able to steal focus from it. E.g., When the current focused node
+ // is in chrome, any web contents should not be able to steal the focus.
+ nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mFocusedContent));
+ sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
+ if (!sendFocusEvent && mMouseButtonEventHandlingDocument) {
+ // However, while mouse button event is handling, the handling document's
+ // script should be able to steal focus.
+ domNode = do_QueryInterface(mMouseButtonEventHandlingDocument);
+ sendFocusEvent = nsContentUtils::CanCallerAccess(domNode);
+ }
+ }
+
+ LOGCONTENT("Shift Focus: %s", contentToFocus.get());
+ LOGFOCUS((" Flags: %x Current Window: %p New Window: %p Current Element: %p",
+ aFlags, mFocusedWindow.get(), newWindow.get(), mFocusedContent.get()));
+ LOGFOCUS((" In Active Window: %d In Focused Window: %d SendFocus: %d",
+ isElementInActiveWindow, isElementInFocusedWindow, sendFocusEvent));
+
+ if (sendFocusEvent) {
+ nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent;
+ // return if blurring fails or the focus changes during the blur
+ if (mFocusedWindow) {
+ // if the focus is being moved to another element in the same document,
+ // or to a descendant, pass the existing window to Blur so that the
+ // current node in the existing window is cleared. If moving to a
+ // window elsewhere, we want to maintain the current node in the
+ // window but still blur it.
+ bool currentIsSameOrAncestor = IsSameOrAncestor(mFocusedWindow, newWindow);
+ // 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.
+ nsCOMPtr<nsPIDOMWindowOuter> commonAncestor;
+ if (!isElementInFocusedWindow)
+ commonAncestor = GetCommonAncestor(newWindow, mFocusedWindow);
+
+ if (!Blur(currentIsSameOrAncestor ? mFocusedWindow.get() : nullptr,
+ commonAncestor, !isElementInFocusedWindow, aAdjustWidget,
+ contentToFocus))
+ return;
+ }
+
+ Focus(newWindow, contentToFocus, aFlags, !isElementInFocusedWindow,
+ aFocusChanged, false, aAdjustWidget, oldFocusedContent);
+ }
+ 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(newWindow, true);
+
+ // set the focus node and method as needed
+ uint32_t focusMethod = aFocusChanged ? aFlags & FOCUSMETHODANDRING_MASK :
+ newWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
+ newWindow->SetFocusedNode(contentToFocus, focusMethod);
+ if (aFocusChanged) {
+ nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (presShell && presShell->DidInitialize())
+ ScrollIntoView(presShell, contentToFocus, aFlags);
+ }
+
+ // update the commands even when inactive so that the attributes for that
+ // window are up to date.
+ if (allowFrameSwitch)
+ newWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+
+ if (aFlags & FLAG_RAISE)
+ RaiseWindow(newRootWindow);
+ }
+}
+
+bool
+nsFocusManager::IsSameOrAncestor(nsPIDOMWindowOuter* aPossibleAncestor,
+ nsPIDOMWindowOuter* aWindow)
+{
+ if (!aWindow || !aPossibleAncestor) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> ancestordsti = aPossibleAncestor->GetDocShell();
+ nsCOMPtr<nsIDocShellTreeItem> dsti = aWindow->GetDocShell();
+ while (dsti) {
+ if (dsti == ancestordsti)
+ return true;
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ dsti->GetParent(getter_AddRefs(parentDsti));
+ dsti.swap(parentDsti);
+ }
+
+ return false;
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsFocusManager::GetCommonAncestor(nsPIDOMWindowOuter* aWindow1,
+ nsPIDOMWindowOuter* aWindow2)
+{
+ NS_ENSURE_TRUE(aWindow1 && aWindow2, nullptr);
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti1 = aWindow1->GetDocShell();
+ NS_ENSURE_TRUE(dsti1, nullptr);
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti2 = aWindow2->GetDocShell();
+ NS_ENSURE_TRUE(dsti2, nullptr);
+
+ AutoTArray<nsIDocShellTreeItem*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(dsti1);
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti1;
+ dsti1->GetParent(getter_AddRefs(parentDsti1));
+ dsti1.swap(parentDsti1);
+ } while (dsti1);
+ do {
+ parents2.AppendElement(dsti2);
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti2;
+ dsti2->GetParent(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;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = parent ? parent->GetWindow() : nullptr;
+ return window.forget();
+}
+
+void
+nsFocusManager::AdjustWindowFocus(nsPIDOMWindowOuter* aWindow,
+ bool aCheckPermission)
+{
+ bool isVisible = IsWindowVisible(aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window(aWindow);
+ while (window) {
+ // get the containing <iframe> or equivalent element so that it can be
+ // focused below.
+ nsCOMPtr<Element> frameElement = window->GetFrameElementInternal();
+
+ nsCOMPtr<nsIDocShellTreeItem> dsti = window->GetDocShell();
+ if (!dsti)
+ return;
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ dsti->GetParent(getter_AddRefs(parentDsti));
+ if (!parentDsti) {
+ return;
+ }
+
+ window = parentDsti->GetWindow();
+ if (window) {
+ // if the parent window is visible but aWindow 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) != isVisible)
+ 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;
+ }
+
+ window->SetFocusedNode(frameElement);
+ }
+ }
+}
+
+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)
+{
+ NS_PRECONDITION(aContent, "aContent must not be NULL");
+ NS_PRECONDITION(aContent->IsInComposedDoc(), "aContent must be in a document");
+
+ // If aContent is in designMode, the root element is not focusable.
+ // NOTE: in designMode, most elements are not focusable, just the document is
+ // focusable.
+ // Also, if aContent is not editable but it isn't in designMode, it's not
+ // focusable.
+ // And in userfocusignored context nothing is focusable.
+ nsIDocument* doc = aContent->GetComposedDoc();
+ NS_ASSERTION(doc, "aContent must have current document");
+ return aContent == doc->GetRootElement() &&
+ (doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable() ||
+ nsContentUtils::IsUserFocusIgnored(aContent));
+}
+
+nsIContent*
+nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags)
+{
+ if (!aContent)
+ return nullptr;
+
+ // this is a special case for some XUL elements or input number, where an
+ // anonymous child is actually focusable and not the element itself.
+ nsCOMPtr<nsIContent> redirectedFocus = GetRedirectedFocus(aContent);
+ if (redirectedFocus)
+ return CheckIfFocusable(redirectedFocus, aFlags);
+
+ nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
+ // can't focus elements that are not in documents
+ if (!doc) {
+ LOGCONTENT("Cannot focus %s because content not in document", aContent)
+ return nullptr;
+ }
+
+ // Make sure that our frames are up to date
+ doc->FlushPendingNotifications(Flush_Layout);
+
+ nsIPresShell *shell = doc->GetShell();
+ if (!shell)
+ return nullptr;
+
+ // the root content can always be focused,
+ // except in userfocusignored context.
+ if (aContent == doc->GetRootElement())
+ return nsContentUtils::IsUserFocusIgnored(aContent) ? nullptr : aContent;
+
+ // cannot focus content in print preview mode. Only the root can be focused.
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext && presContext->Type() == nsPresContext::eContext_PrintPreview) {
+ LOGCONTENT("Cannot focus %s while in print preview", aContent)
+ return nullptr;
+ }
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ LOGCONTENT("Cannot focus %s as it has no frame", aContent)
+ return nullptr;
+ }
+
+ if (aContent->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() &&
+ aContent->IsFocusable() ? aContent : nullptr;
+ }
+
+ // if this is a child frame content node, check if it is visible and
+ // call the content node's IsFocusable method instead of the frame's
+ // IsFocusable method. This skips checking the style system and ensures that
+ // offscreen browsers can still be focused.
+ nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
+ if (subdoc && IsWindowVisible(subdoc->GetWindow())) {
+ const nsStyleUserInterface* ui = frame->StyleUserInterface();
+ int32_t tabIndex = (ui->mUserFocus == StyleUserFocus::Ignore ||
+ ui->mUserFocus == StyleUserFocus::None) ? -1 : 0;
+ return aContent->IsFocusable(&tabIndex, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
+ }
+
+ return frame->IsFocusable(nullptr, aFlags & FLAG_BYMOUSE) ? aContent : nullptr;
+}
+
+bool
+nsFocusManager::Blur(nsPIDOMWindowOuter* aWindowToClear,
+ nsPIDOMWindowOuter* aAncestorWindowToFocus,
+ bool aIsLeavingDocument,
+ bool aAdjustWidgets,
+ nsIContent* aContentToFocus)
+{
+ LOGFOCUS(("<<Blur begin>>"));
+
+ // hold a reference to the focused content, which may be null
+ nsCOMPtr<nsIContent> content = mFocusedContent;
+ if (content) {
+ if (!content->IsInComposedDoc()) {
+ mFocusedContent = nullptr;
+ return true;
+ }
+ if (content == mFirstBlurEvent)
+ return true;
+ }
+
+ // hold a reference to the focused window
+ nsCOMPtr<nsPIDOMWindowOuter> window = mFocusedWindow;
+ if (!window) {
+ mFocusedContent = nullptr;
+ return true;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ if (!docShell) {
+ mFocusedContent = nullptr;
+ return true;
+ }
+
+ // Keep a ref to presShell since dispatching the DOM event may cause
+ // the document to be destroyed.
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (!presShell) {
+ mFocusedContent = nullptr;
+ return true;
+ }
+
+ bool clearFirstBlurEvent = false;
+ if (!mFirstBlurEvent) {
+ mFirstBlurEvent = content;
+ clearFirstBlurEvent = true;
+ }
+
+ nsPresContext* focusedPresContext =
+ mActiveWindow ? 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.
+ mFocusedContent = nullptr;
+ bool shouldShowFocusRing = window->ShouldShowFocusRing();
+ if (aWindowToClear)
+ aWindowToClear->SetFocusedNode(nullptr);
+
+ LOGCONTENT("Element %s has been blurred", content.get());
+
+ // Don't fire blur event on the root content which isn't editable.
+ bool sendBlurEvent =
+ content && content->IsInComposedDoc() && !IsNonFocusableRoot(content);
+ if (content) {
+ if (sendBlurEvent) {
+ NotifyFocusStateChange(content, shouldShowFocusRing, false);
+ }
+
+ // if an object/plug-in/remote browser is being blurred, move the system focus
+ // to the parent window, otherwise events will still get fired at the plugin.
+ // But don't do this if we are blurring due to the window being lowered,
+ // otherwise, the parent window can get raised again.
+ if (mActiveWindow) {
+ nsIFrame* contentFrame = content->GetPrimaryFrame();
+ nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
+ if (aAdjustWidgets && objectFrame && !sTestMode) {
+ if (XRE_IsContentProcess()) {
+ // set focus to the top level window via the chrome process.
+ nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild();
+ if (tabChild) {
+ static_cast<TabChild*>(tabChild.get())->SendDispatchFocusToTopLevelWindow();
+ }
+ } else {
+ // note that the presshell's widget is being retrieved here, not the one
+ // for the object frame.
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget) {
+ // set focus to the top level window but don't raise it.
+ widget->SetFocus(false);
+ }
+ }
+ }
+ }
+ }
+
+ // if the object being blurred is a remote browser, deactivate remote content
+ if (TabParent* remote = TabParent::GetFrom(content)) {
+ remote->Deactivate();
+ LOGFOCUS(("Remote browser deactivated"));
+ }
+ }
+
+ 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 (mActiveWindow)
+ window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+
+ SendFocusOrBlurEvent(eBlur, presShell,
+ content->GetComposedDoc(), content, 1,
+ false, false, aContentToFocus);
+ }
+
+ // if we are leaving the document or the window was lowered, make the caret
+ // invisible.
+ if (aIsLeavingDocument || !mActiveWindow) {
+ SetCaretVisible(presShell, false, nullptr);
+ }
+
+ RefPtr<AccessibleCaretEventHub> eventHub = presShell->GetAccessibleCaretEventHub();
+ if (eventHub) {
+ eventHub->NotifyBlur(aIsLeavingDocument || !mActiveWindow);
+ }
+
+ // at this point, it is expected that this window will be still be
+ // focused, but the focused content 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 (mFocusedWindow != window ||
+ (mFocusedContent != 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 aAncestorWindowToFocus is thought to be
+ // focused at this point.
+ if (aAncestorWindowToFocus)
+ aAncestorWindowToFocus->SetFocusedNode(nullptr, 0, true);
+
+ SetFocusedWindowInternal(nullptr);
+ mFocusedContent = nullptr;
+
+ // pass 1 for the focus method when calling SendFocusOrBlurEvent just so
+ // that the check is made for suppressed documents. Check to ensure that
+ // the document isn't null in case someone closed it during the blur above
+ nsIDocument* doc = window->GetExtantDoc();
+ if (doc)
+ SendFocusOrBlurEvent(eBlur, presShell, doc, doc, 1, false);
+ if (mFocusedWindow == nullptr)
+ SendFocusOrBlurEvent(eBlur, presShell, doc,
+ window->GetCurrentInnerWindow(), 1, false);
+
+ // check if a different window was focused
+ result = (mFocusedWindow == nullptr && mActiveWindow);
+ }
+ else if (mActiveWindow) {
+ // 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);
+ }
+
+ if (clearFirstBlurEvent)
+ mFirstBlurEvent = nullptr;
+
+ return result;
+}
+
+void
+nsFocusManager::Focus(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aContent,
+ uint32_t aFlags,
+ bool aIsNewDocument,
+ bool aFocusChanged,
+ bool aWindowRaised,
+ bool aAdjustWidgets,
+ nsIContent* aContentLostFocus)
+{
+ LOGFOCUS(("<<Focus begin>>"));
+
+ if (!aWindow)
+ return;
+
+ if (aContent && (aContent == mFirstFocusEvent || aContent == 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;
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (!presShell)
+ 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 & FOCUSMETHODANDRING_MASK :
+ aWindow->GetFocusMethod() | (aFlags & FLAG_SHOWRING);
+
+ 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 (CheckIfFocusable(aContent, aFlags)) {
+ aWindow->SetFocusedNode(aContent, focusMethod);
+ if (aFocusChanged)
+ ScrollIntoView(presShell, aContent, aFlags);
+ }
+ return;
+ }
+
+ bool clearFirstFocusEvent = false;
+ if (!mFirstFocusEvent) {
+ mFirstFocusEvent = aContent;
+ clearFirstFocusEvent = true;
+ }
+
+ LOGCONTENT("Element %s has been focused", aContent);
+
+ if (MOZ_LOG_TEST(gFocusLog, LogLevel::Debug)) {
+ nsIDocument* docm = aWindow->GetExtantDoc();
+ if (docm) {
+ LOGCONTENT(" from %s", docm->GetRootElement());
+ }
+ LOGFOCUS((" [Newdoc: %d FocusChanged: %d Raised: %d Flags: %x]",
+ aIsNewDocument, aFocusChanged, aWindowRaised, aFlags));
+ }
+
+ 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.
+ AdjustWindowFocus(aWindow, false);
+ }
+
+ // indicate that the window has taken focus.
+ if (aWindow->TakeFocus(true, focusMethod))
+ aIsNewDocument = true;
+
+ SetFocusedWindowInternal(aWindow);
+
+ // Update the system focus by focusing the root widget. But avoid this
+ // if 1) aAdjustWidgets is false or 2) aContent is a plugin that has its
+ // own widget and is either already focused or is about to be focused.
+ nsCOMPtr<nsIWidget> objectFrameWidget;
+ if (aContent) {
+ nsIFrame* contentFrame = aContent->GetPrimaryFrame();
+ nsIObjectFrame* objectFrame = do_QueryFrame(contentFrame);
+ if (objectFrame)
+ objectFrameWidget = objectFrame->GetWidget();
+ }
+ if (aAdjustWidgets && !objectFrameWidget && !sTestMode) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(false);
+ }
+ }
+
+ // if switching to a new document, first fire the focus event on the
+ // document and then the window.
+ if (aIsNewDocument) {
+ nsIDocument* doc = aWindow->GetExtantDoc();
+ // The focus change should be notified to IMEStateManager from here if
+ // the focused content is a designMode editor since any content won't
+ // receive focus event.
+ if (doc && doc->HasFlag(NODE_IS_EDITABLE)) {
+ IMEStateManager::OnChangeFocus(presShell->GetPresContext(), nullptr,
+ GetFocusMoveActionCause(aFlags));
+ }
+ if (doc)
+ SendFocusOrBlurEvent(eFocus, presShell, doc,
+ doc, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
+ if (mFocusedWindow == aWindow && mFocusedContent == nullptr)
+ SendFocusOrBlurEvent(eFocus, presShell, doc,
+ aWindow->GetCurrentInnerWindow(),
+ aFlags & FOCUSMETHOD_MASK, aWindowRaised);
+ }
+
+ // check to ensure that the element is still focusable, and that nothing
+ // else was focused during the events above.
+ if (CheckIfFocusable(aContent, aFlags) &&
+ mFocusedWindow == aWindow && mFocusedContent == nullptr) {
+ mFocusedContent = aContent;
+
+ nsIContent* focusedNode = aWindow->GetFocusedNode();
+ bool isRefocus = focusedNode && focusedNode->IsEqualNode(aContent);
+
+ aWindow->SetFocusedNode(aContent, focusMethod);
+
+ bool sendFocusEvent =
+ aContent && aContent->IsInComposedDoc() && !IsNonFocusableRoot(aContent);
+ nsPresContext* presContext = presShell->GetPresContext();
+ if (sendFocusEvent) {
+ // if the focused element changed, scroll it into view
+ if (aFocusChanged)
+ ScrollIntoView(presShell, aContent, aFlags);
+
+ NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true);
+
+ // if this is an object/plug-in/remote browser, focus its widget. Note that we might
+ // no longer be in the same document, due to the events we fired above when
+ // aIsNewDocument.
+ if (presShell->GetDocument() == aContent->GetComposedDoc()) {
+ if (aAdjustWidgets && objectFrameWidget && !sTestMode)
+ objectFrameWidget->SetFocus(false);
+
+ // if the object being focused is a remote browser, activate remote content
+ if (TabParent* remote = TabParent::GetFrom(aContent)) {
+ remote->Activate();
+ LOGFOCUS(("Remote browser activated"));
+ }
+ }
+
+ IMEStateManager::OnChangeFocus(presContext, aContent,
+ 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(NS_LITERAL_STRING("focus"), nullptr, 0);
+
+ SendFocusOrBlurEvent(eFocus, presShell,
+ aContent->GetComposedDoc(),
+ aContent, aFlags & FOCUSMETHOD_MASK,
+ aWindowRaised, isRefocus, aContentLostFocus);
+ } else {
+ IMEStateManager::OnChangeFocus(presContext, nullptr,
+ GetFocusMoveActionCause(aFlags));
+ if (!aWindowRaised) {
+ aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
+ }
+ }
+ }
+ else {
+ // If the window focus event (fired above when aIsNewDocument) caused
+ // the plugin not to be focusable, update the system focus by focusing
+ // the root widget.
+ if (aAdjustWidgets && objectFrameWidget &&
+ mFocusedWindow == aWindow && mFocusedContent == nullptr &&
+ !sTestMode) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(false);
+ }
+ }
+
+ if (!mFocusedContent) {
+ // When there is no focused content, IMEStateManager needs to adjust IME
+ // enabled state with the document.
+ nsPresContext* presContext = presShell->GetPresContext();
+ IMEStateManager::OnChangeFocus(presContext, nullptr,
+ GetFocusMoveActionCause(aFlags));
+ }
+
+ if (!aWindowRaised)
+ aWindow->UpdateCommands(NS_LITERAL_STRING("focus"), 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 (mFocusedContent == aContent)
+ UpdateCaret(aFocusChanged && !(aFlags & FLAG_BYMOUSE), aIsNewDocument,
+ mFocusedContent);
+
+ if (clearFirstFocusEvent)
+ mFirstFocusEvent = nullptr;
+}
+
+class FocusBlurEvent : public Runnable
+{
+public:
+ FocusBlurEvent(nsISupports* aTarget, EventMessage aEventMessage,
+ nsPresContext* aContext, bool aWindowRaised,
+ bool aIsRefocus, EventTarget* aRelatedTarget)
+ : mTarget(aTarget)
+ , mContext(aContext)
+ , mEventMessage(aEventMessage)
+ , mWindowRaised(aWindowRaised)
+ , mIsRefocus(aIsRefocus)
+ , mRelatedTarget(aRelatedTarget)
+ {
+ }
+
+ 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);
+ }
+
+ nsCOMPtr<nsISupports> mTarget;
+ 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)
+ : mTarget(aTarget)
+ , mContext(aContext)
+ , mEventMessage(aEventMessage)
+ , mOriginalFocusedWindow(aOriginalFocusedWindow)
+ , mOriginalFocusedContent(aOriginalFocusedContent)
+ , mRelatedTarget(aRelatedTarget)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIContent> originalWindowFocus = mOriginalFocusedWindow ?
+ mOriginalFocusedWindow->GetFocusedNode() :
+ 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;
+ }
+
+ nsCOMPtr<nsISupports> mTarget;
+ RefPtr<nsPresContext> mContext;
+ EventMessage mEventMessage;
+ nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow;
+ nsCOMPtr<nsIContent> mOriginalFocusedContent;
+ nsCOMPtr<EventTarget> mRelatedTarget;
+};
+
+static nsIDocument*
+GetDocumentHelper(EventTarget* aTarget)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aTarget);
+ if (!node) {
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aTarget);
+ return win ? win->GetExtantDoc() : nullptr;
+ }
+
+ return node->OwnerDoc();
+}
+
+void nsFocusManager::SendFocusInOrOutEvent(EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsISupports* aTarget,
+ nsPIDOMWindowOuter* aCurrentFocusedWindow,
+ nsIContent* aCurrentFocusedContent,
+ EventTarget* aRelatedTarget)
+{
+ NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
+ "Wrong event type for SendFocusInOrOutEvent");
+
+ nsContentUtils::AddScriptRunner(
+ new FocusInOutEvent(
+ aTarget,
+ aEventMessage,
+ aPresShell->GetPresContext(),
+ aCurrentFocusedWindow,
+ aCurrentFocusedContent,
+ aRelatedTarget));
+}
+
+void
+nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsIDocument* aDocument,
+ nsISupports* aTarget,
+ uint32_t aFocusMethod,
+ bool aWindowRaised,
+ bool aIsRefocus,
+ EventTarget* aRelatedTarget)
+{
+ NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur,
+ "Wrong event type for SendFocusOrBlurEvent");
+
+ nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
+ nsCOMPtr<nsIDocument> eventTargetDoc = GetDocumentHelper(eventTarget);
+ nsCOMPtr<nsIDocument> relatedTargetDoc = GetDocumentHelper(aRelatedTarget);
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow;
+ nsCOMPtr<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget);
+ nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(aTarget);
+ nsCOMPtr<nsIContent> currentFocusedContent = currentWindow ?
+ currentWindow->GetFocusedNode() : nullptr;
+
+ // set aRelatedTarget to null if it's not in the same document as eventTarget
+ if (eventTargetDoc != relatedTargetDoc) {
+ aRelatedTarget = nullptr;
+ }
+
+ bool dontDispatchEvent =
+ eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc);
+
+ // for focus events, if this event was from a mouse or key and event
+ // handling on the document is suppressed, queue the event and fire it
+ // later. For blur events, a non-zero value would be set for aFocusMethod.
+ if (aFocusMethod && !dontDispatchEvent &&
+ aDocument && aDocument->EventHandlingSuppressed()) {
+ // aFlags is always 0 when aWindowRaised is true so this won't be called
+ // on a window raise.
+ NS_ASSERTION(!aWindowRaised, "aWindowRaised should not be set");
+
+ for (uint32_t i = mDelayedBlurFocusEvents.Length(); i > 0; --i) {
+ // if this event was already queued, remove it and append it to the end
+ if (mDelayedBlurFocusEvents[i - 1].mEventMessage == aEventMessage &&
+ mDelayedBlurFocusEvents[i - 1].mPresShell == aPresShell &&
+ mDelayedBlurFocusEvents[i - 1].mDocument == aDocument &&
+ mDelayedBlurFocusEvents[i - 1].mTarget == eventTarget &&
+ mDelayedBlurFocusEvents[i - 1].mRelatedTarget == aRelatedTarget) {
+ mDelayedBlurFocusEvents.RemoveElementAt(i - 1);
+ }
+ }
+
+ mDelayedBlurFocusEvents.AppendElement(
+ nsDelayedBlurOrFocusEvent(aEventMessage, aPresShell,
+ aDocument, eventTarget, aRelatedTarget));
+ return;
+ }
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = GetAccService();
+ if (accService) {
+ if (aEventMessage == eFocus) {
+ accService->NotifyOfDOMFocus(aTarget);
+ } else {
+ accService->NotifyOfDOMBlur(aTarget);
+ }
+ }
+#endif
+
+ if (!dontDispatchEvent) {
+ 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;
+ SendFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
+ currentWindow, currentFocusedContent, aRelatedTarget);
+ }
+ }
+}
+
+void
+nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
+ nsIContent* aContent,
+ uint32_t aFlags)
+{
+ // if the noscroll flag isn't set, scroll the newly focused element into view
+ if (!(aFlags & FLAG_NOSCROLL))
+ aPresShell->ScrollContentIntoView(aContent,
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+}
+
+
+void
+nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow)
+{
+ // don't raise windows that are already raised or are in the process of
+ // being lowered
+ if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
+ return;
+
+ if (sTestMode) {
+ // In test mode, emulate the existing window being lowered and the new
+ // window being raised.
+ if (mActiveWindow)
+ WindowLowered(mActiveWindow);
+ WindowRaised(aWindow);
+ return;
+ }
+
+#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, true, getter_AddRefs(childWindow));
+ if (!childWindow)
+ childWindow = aWindow;
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (!docShell)
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (!presShell)
+ return;
+
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(true);
+ }
+#else
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin =
+ do_QueryInterface(aWindow->GetDocShell());
+ if (treeOwnerAsWin) {
+ nsCOMPtr<nsIWidget> widget;
+ treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
+ if (widget)
+ widget->SetFocus(true);
+ }
+#endif
+}
+
+void
+nsFocusManager::UpdateCaretForCaretBrowsingMode()
+{
+ UpdateCaret(false, true, mFocusedContent);
+}
+
+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();
+ nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(focusedDocShell);
+ if (!dsti)
+ return;
+
+ if (dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ return; // Never browse with caret in chrome
+ }
+
+ bool browseWithCaret =
+ Preferences::GetBool("accessibility.browsewithcaret");
+
+ nsCOMPtr<nsIPresShell> 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) {
+ nsCOMPtr<nsIHTMLDocument> doc =
+ do_QueryInterface(presShell->GetDocument());
+
+ bool isContentEditableDoc =
+ doc && doc->GetEditingState() == nsIHTMLDocument::eContentEditable;
+
+ bool isFocusEditable =
+ aContent && aContent->HasFlag(NODE_IS_EDITABLE);
+ if (!isContentEditableDoc || isFocusEditable)
+ return;
+ }
+
+ if (!isEditable && aMoveCaretToFocus)
+ MoveCaretToFocus(presShell, aContent);
+
+ 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,
+ NS_LITERAL_STRING("true"),
+ eCaseMatters);
+ }
+
+ SetCaretVisible(presShell, browseWithCaret, aContent);
+}
+
+void
+nsFocusManager::MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent)
+{
+ // domDoc is a document interface we can create a range with
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(aPresShell->GetDocument());
+ if (domDoc) {
+ RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
+ nsCOMPtr<nsISelection> domSelection =
+ frameSelection->GetSelection(SelectionType::eNormal);
+ if (domSelection) {
+ nsCOMPtr<nsIDOMNode> currentFocusNode(do_QueryInterface(aContent));
+ // First clear the selection. This way, if there is no currently focused
+ // content, the selection will just be cleared.
+ domSelection->RemoveAllRanges();
+ if (currentFocusNode) {
+ nsCOMPtr<nsIDOMRange> newRange;
+ nsresult rv = domDoc->CreateRange(getter_AddRefs(newRange));
+ if (NS_SUCCEEDED(rv)) {
+ // Set the range to the start of the currently focused node
+ // Make sure it's collapsed
+ newRange->SelectNodeContents(currentFocusNode);
+ nsCOMPtr<nsIDOMNode> firstChild;
+ currentFocusNode->GetFirstChild(getter_AddRefs(firstChild));
+ if (!firstChild ||
+ aContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
+ // 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(currentFocusNode);
+ newRange->SetEndBefore(currentFocusNode);
+ }
+ domSelection->AddRange(newRange);
+ domSelection->CollapseToStart();
+ }
+ }
+ }
+ }
+}
+
+nsresult
+nsFocusManager::SetCaretVisible(nsIPresShell* 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)) {
+ nsISelection* domSelection =
+ docFrameSelection->GetSelection(SelectionType::eNormal);
+ if (domSelection) {
+ nsCOMPtr<nsISelectionController> selCon(do_QueryInterface(aPresShell));
+ if (!selCon) {
+ return NS_ERROR_FAILURE;
+ }
+ // First, hide the caret to prevent attempting to show it in SetCaretDOMSelection
+ selCon->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.
+
+ selCon->SetCaretReadOnly(false);
+ selCon->SetCaretEnabled(aVisible);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFocusManager::GetSelectionLocation(nsIDocument* aDocument,
+ nsIPresShell* aPresShell,
+ nsIContent **aStartContent,
+ nsIContent **aEndContent)
+{
+ *aStartContent = *aEndContent = nullptr;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ NS_ASSERTION(presContext, "mPresContent is null!!");
+
+ RefPtr<nsFrameSelection> frameSelection = aPresShell->FrameSelection();
+
+ nsCOMPtr<nsISelection> domSelection;
+ if (frameSelection) {
+ domSelection = frameSelection->GetSelection(SelectionType::eNormal);
+ }
+
+ nsCOMPtr<nsIDOMNode> startNode, endNode;
+ bool isCollapsed = false;
+ nsCOMPtr<nsIContent> startContent, endContent;
+ int32_t startOffset = 0;
+ if (domSelection) {
+ domSelection->GetIsCollapsed(&isCollapsed);
+ nsCOMPtr<nsIDOMRange> domRange;
+ rv = domSelection->GetRangeAt(0, getter_AddRefs(domRange));
+ if (domRange) {
+ domRange->GetStartContainer(getter_AddRefs(startNode));
+ domRange->GetEndContainer(getter_AddRefs(endNode));
+ domRange->GetStartOffset(&startOffset);
+
+ nsIContent *childContent = nullptr;
+
+ startContent = do_QueryInterface(startNode);
+ if (startContent && startContent->IsElement()) {
+ NS_ASSERTION(startOffset >= 0, "Start offset cannot be negative");
+ childContent = startContent->GetChildAt(startOffset);
+ if (childContent) {
+ startContent = childContent;
+ }
+ }
+
+ endContent = do_QueryInterface(endNode);
+ if (endContent && endContent->IsElement()) {
+ int32_t endOffset = 0;
+ domRange->GetEndOffset(&endOffset);
+ NS_ASSERTION(endOffset >= 0, "End offset cannot be negative");
+ childContent = endContent->GetChildAt(endOffset);
+ if (childContent) {
+ endContent = childContent;
+ }
+ }
+ }
+ }
+ else {
+ rv = 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() == nsIDOMNode::TEXT_NODE) {
+ nsAutoString nodeValue;
+ startContent->AppendTextTo(nodeValue);
+
+ bool isFormControl =
+ startContent->IsNodeOfType(nsINode::eHTML_FORM_CONTROL);
+
+ if (nodeValue.Length() == (uint32_t)startOffset && !isFormControl &&
+ 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 rv;
+}
+
+nsresult
+nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aStartContent,
+ int32_t aType, bool aNoParentTraversal,
+ nsIContent** aNextContent)
+{
+ *aNextContent = nullptr;
+
+ // True if we are navigating by document (F6/Shift+F6) or false if we are
+ // navigating by element (Tab/Shift+Tab).
+ bool forDocumentNavigation = false;
+
+ // 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, true, 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->GetFocusedNode();
+ }
+ }
+
+ nsCOMPtr<nsIDocument> doc;
+ if (startContent)
+ doc = startContent->GetComposedDoc();
+ else
+ doc = aWindow->GetExtantDoc();
+ if (!doc)
+ return NS_OK;
+
+ LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
+ &nsIContent::sTabFocusModel);
+
+ // These types are for document navigation using F6.
+ if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC ||
+ aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC) {
+ forDocumentNavigation = true;
+ }
+
+ // 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;
+ }
+ }
+
+ nsIContent* rootContent = doc->GetRootElement();
+ NS_ENSURE_TRUE(rootContent, NS_OK);
+
+ nsIPresShell *presShell = doc->GetShell();
+ NS_ENSURE_TRUE(presShell, NS_OK);
+
+ if (aType == MOVEFOCUS_FIRST) {
+ if (!aStartContent)
+ startContent = rootContent;
+ return GetNextTabbableContent(presShell, startContent,
+ nullptr, startContent,
+ true, 1, false, false, aNextContent);
+ }
+ if (aType == MOVEFOCUS_LAST) {
+ if (!aStartContent)
+ startContent = rootContent;
+ return GetNextTabbableContent(presShell, startContent,
+ nullptr, startContent,
+ false, 0, false, false, 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)
+ frame->IsFocusable(&tabIndex, 0);
+ 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 != rootContent)
+ 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,
+ nsGkAtoms::menuPopupFrame);
+ }
+
+ if (popupFrame && !forDocumentNavigation) {
+ // Don't navigate outside of a popup, so pretend that the
+ // root content is the popup itself
+ rootContent = popupFrame->GetContent();
+ NS_ASSERTION(rootContent, "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 == rootContent) {
+ doNavigation = false;
+ } else {
+ nsIDocument* doc = startContent->GetComposedDoc();
+ if (startContent ==
+ nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
+ doNavigation = false;
+ }
+ }
+ }
+ }
+ else {
+#ifdef MOZ_XUL
+ 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(ePopupTypePanel);
+ }
+#endif
+ 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) {
+ rootContent = startContent;
+ }
+
+ 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 rootContent, then there is no selection
+ if (startContent == rootContent) {
+ 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 = rootContent;
+ 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 && doc->IsXULDocument()) {
+ nsAutoString retarget;
+
+ if (rootContent->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
+ // ContentIsDescendantOf check will return true in this case.
+ if (retargetElement && (retargetElement == startContent ||
+ (!retargetElement->Contains(startContent) &&
+ nsContentUtils::ContentIsDescendantOf(startContent, retargetElement)))) {
+ startContent = rootContent;
+ }
+ }
+ }
+
+ 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;
+ 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;
+ nsresult rv = GetNextTabbableContent(presShell, rootContent,
+ skipOriginalContentCheck ? nullptr : originalStartContent,
+ startContent, forward,
+ tabIndex, ignoreTabIndex,
+ forDocumentNavigation,
+ 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 != rootContent) {
+ startContent = rootContent;
+ tabIndex = forward ? 1 : 0;
+ continue;
+ }
+ return NS_OK;
+ }
+ }
+
+ doNavigation = true;
+ skipOriginalContentCheck = forDocumentNavigation;
+ ignoreTabIndex = false;
+
+ if (aNoParentTraversal) {
+ if (startContent == rootContent)
+ return NS_OK;
+
+ startContent = rootContent;
+ 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);
+
+ rootContent = doc->GetRootElement();
+ presShell = doc->GetShell();
+
+ // 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;
+ }
+
+ frame->IsFocusable(&tabIndex, 0);
+ 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,
+ nsGkAtoms::menuPopupFrame);
+ if (popupFrame) {
+ rootContent = popupFrame->GetContent();
+ NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
+ }
+ }
+ }
+ else {
+ // 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 content.
+ if (tookFocus) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
+ if (window->GetFocusedNode() == mFocusedContent)
+ Blur(mFocusedWindow, nullptr, true, true);
+ else
+ window->SetFocusedNode(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.
+ nsIContent* root = GetRootForFocus(piWindow, doc, true, true);
+ return FocusFirst(root, 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 = rootContent;
+ 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;
+}
+
+nsresult
+nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
+ nsIContent* aRootContent,
+ nsIContent* aOriginalStartContent,
+ nsIContent* aStartContent,
+ bool aForward,
+ int32_t aCurrentTabIndex,
+ bool aIgnoreTabIndex,
+ bool aForDocumentNavigation,
+ nsIContent** aResultContent)
+{
+ *aResultContent = nullptr;
+
+ nsCOMPtr<nsIContent> startContent = aStartContent;
+ if (!startContent)
+ return NS_OK;
+
+ LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent);
+ LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex));
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+
+ bool getNextFrame = true;
+ nsCOMPtr<nsIContent> iterStartContent = aStartContent;
+ while (1) {
+ nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
+ // if there is no frame, look for another content node that has a frame
+ if (!startFrame) {
+ // 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();
+ // we've already skipped over the initial focused content, so we
+ // don't want to traverse frames.
+ getNextFrame = false;
+ if (iterStartContent)
+ continue;
+
+ // otherwise, as a last attempt, just look at the root content
+ iterStartContent = aRootContent;
+ continue;
+ }
+
+ // 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.
+ nsCOMPtr<nsIFrameEnumerator> frameTraversal;
+ nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
+ presContext, startFrame,
+ 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();
+ }
+ }
+ 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.
+ if (aForward)
+ frameTraversal->Next();
+ else
+ frameTraversal->Prev();
+ }
+
+ // Walk frames to find something tabbable matching mCurrentTabIndex
+ nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+ while (frame) {
+ nsIContent* currentContent = frame->GetContent();
+
+ // 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 = aStartContent;
+ 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.
+ rv = GetNextTabbableContent(aPresShell, currentContent,
+ nullptr, currentContent,
+ true, 1, false, false,
+ aResultContent);
+ if (NS_SUCCEEDED(rv) && *aResultContent) {
+ return rv;
+ }
+ }
+ }
+ }
+
+ // 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:
+ // < 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
+ int32_t tabIndex;
+ frame->IsFocusable(&tabIndex, 0);
+
+ 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->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, 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.
+ TabParent* remote = TabParent::GetFrom(currentContent);
+ if (remote) {
+ remote->NavigateByKey(aForward, aForDocumentNavigation);
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Next, for document navigation, check if this a non-remote child document.
+ bool checkSubDocument = true;
+ if (aForDocumentNavigation) {
+ nsIContent* docRoot = GetRootForChildDocument(currentContent);
+ if (docRoot) {
+ // 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 the checkSubDocument block below to iterate into
+ // the frameset's frames and locate the first focusable frame.
+ if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) {
+ return FocusFirst(docRoot, aResultContent);
+ }
+ } else {
+ // Set checkSubDocument to false, as this was neither a frame
+ // type element or a child document that was focusable.
+ checkSubDocument = false;
+ }
+ }
+
+ 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.
+ nsIDocument* doc = currentContent->GetComposedDoc();
+ NS_ASSERTION(doc, "content not in document");
+ nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
+ if (subdoc && !subdoc->EventHandlingSuppressed()) {
+ if (aForward) {
+ // when tabbing forward into a frame, return the root
+ // frame so that the canvas becomes focused.
+ nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow();
+ if (subframe) {
+ *aResultContent = GetRootForFocus(subframe, subdoc, false, true);
+ if (*aResultContent) {
+ NS_ADDREF(*aResultContent);
+ return NS_OK;
+ }
+ }
+ }
+ Element* rootElement = subdoc->GetRootElement();
+ nsIPresShell* subShell = subdoc->GetShell();
+ if (rootElement && subShell) {
+ rv = GetNextTabbableContent(subShell, rootElement,
+ aOriginalStartContent, rootElement,
+ aForward, (aForward ? 1 : 0),
+ false, aForDocumentNavigation, aResultContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*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.
+ // Also, when going backwards, check to ensure that the focus
+ // wouldn't be redirected. Otherwise, for example, when an input in
+ // a textbox is focused, the enclosing textbox would be found and
+ // the same inner input would be returned again.
+ else if (currentContent == aRootContent ||
+ (currentContent != startContent &&
+ (aForward || !GetRedirectedFocus(currentContent)))) {
+ 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.
+ 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);
+
+ nsCOMPtr<nsIContent> 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;
+ }
+
+ return NS_OK;
+}
+
+nsIContent*
+nsFocusManager::GetNextTabbableMapArea(bool aForward,
+ int32_t aCurrentTabIndex,
+ nsIContent* aImageContent,
+ nsIContent* aStartContent)
+{
+ nsAutoString useMap;
+ aImageContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, useMap);
+
+ nsCOMPtr<nsIDocument> doc = aImageContent->GetComposedDoc();
+ if (doc) {
+ nsCOMPtr<nsIContent> mapContent = doc->FindImageMap(useMap);
+ if (!mapContent)
+ return nullptr;
+ uint32_t count = mapContent->GetChildCount();
+ // First see if the the start content is in this map
+
+ int32_t index = mapContent->IndexOf(aStartContent);
+ int32_t tabIndex;
+ if (index < 0 || (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.
+ index = aForward ? -1 : (int32_t)count;
+ }
+
+ // GetChildAt will return nullptr if our index < 0 or index >= count
+ nsCOMPtr<nsIContent> areaContent;
+ while ((areaContent = mapContent->GetChildAt(aForward ? ++index : --index)) != nullptr) {
+ 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;
+
+ if (aForward) {
+ tabIndex = 0;
+ for (nsIContent* child = aParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
+ if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) {
+ tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex;
+ }
+
+ nsAutoString tabIndexStr;
+ child->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 = aParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward);
+ if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) ||
+ (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) {
+ tabIndex = childTabIndex;
+ }
+
+ nsAutoString tabIndexStr;
+ child->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(nsIContent* aRootContent, nsIContent** aNextContent)
+{
+ if (!aRootContent) {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = aRootContent->GetComposedDoc();
+ if (doc) {
+ if (doc->IsXULDocument()) {
+ // 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 (aRootContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::retargetdocumentfocus, retarget)) {
+ nsCOMPtr<Element> element = doc->GetElementById(retarget);
+ nsCOMPtr<nsIContent> retargetElement =
+ CheckIfFocusable(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.
+ nsIPresShell* presShell = doc->GetShell();
+ if (presShell) {
+ return GetNextTabbableContent(presShell, aRootContent,
+ nullptr, aRootContent,
+ true, 1, false, false,
+ aNextContent);
+ }
+ }
+ }
+
+ NS_ADDREF(*aNextContent = aRootContent);
+ return NS_OK;
+}
+
+nsIContent*
+nsFocusManager::GetRootForFocus(nsPIDOMWindowOuter* aWindow,
+ nsIDocument* 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.
+ nsCOMPtr<nsIContent> 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
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
+ if (htmlDoc) {
+ nsIContent* 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;
+}
+
+nsIContent*
+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;
+ }
+
+ nsIDocument* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
+ if (!subdoc || subdoc->EventHandlingSuppressed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = subdoc->GetWindow();
+ return GetRootForFocus(window, subdoc, true, true);
+}
+
+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->GetFocusedNode();
+
+ // 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
+
+ nsCOMPtr<nsIURI> uri;
+ if (testContent == currentFocus ||
+ testContent->IsLink(getter_AddRefs(uri))) {
+ 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)
+
+ // Turn into nodes so that we can use GetNextSibling() and GetFirstChild()
+ nsCOMPtr<nsIDOMNode> selectionNode(do_QueryInterface(aStartSelection));
+ nsCOMPtr<nsIDOMNode> endSelectionNode(do_QueryInterface(aEndSelection));
+ nsCOMPtr<nsIDOMNode> testNode;
+
+ do {
+ testContent = do_QueryInterface(selectionNode);
+
+ // We're looking for any focusable link that could be part of the
+ // main document's selection.
+ nsCOMPtr<nsIURI> uri;
+ if (testContent == currentFocus ||
+ testContent->IsLink(getter_AddRefs(uri))) {
+ testContent.forget(aFocusedContent);
+ return;
+ }
+
+ selectionNode->GetFirstChild(getter_AddRefs(testNode));
+ if (testNode) {
+ selectionNode = testNode;
+ continue;
+ }
+
+ if (selectionNode == endSelectionNode)
+ break;
+ selectionNode->GetNextSibling(getter_AddRefs(testNode));
+ if (testNode) {
+ selectionNode = testNode;
+ continue;
+ }
+
+ do {
+ selectionNode->GetParentNode(getter_AddRefs(testNode));
+ if (!testNode || testNode == endSelectionNode) {
+ selectionNode = nullptr;
+ break;
+ }
+ testNode->GetNextSibling(getter_AddRefs(selectionNode));
+ if (selectionNode)
+ break;
+ selectionNode = testNode;
+ } while (true);
+ }
+ while (selectionNode && selectionNode != endSelectionNode);
+}
+
+class PointerUnlocker : public Runnable
+{
+public:
+ PointerUnlocker()
+ {
+ 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();
+ nsCOMPtr<nsIDocument> pointerLockedDoc =
+ do_QueryReferent(EventStateManager::sPointerLockedDoc);
+ if (pointerLockedDoc &&
+ !nsContentUtils::IsInPointerLockContext(focused)) {
+ nsIDocument::UnlockPointer();
+ }
+ return NS_OK;
+ }
+
+ static PointerUnlocker* sActiveUnlocker;
+};
+
+PointerUnlocker*
+PointerUnlocker::sActiveUnlocker = nullptr;
+
+void
+nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow)
+{
+ if (!PointerUnlocker::sActiveUnlocker &&
+ nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
+ !nsContentUtils::IsInPointerLockContext(aWindow)) {
+ nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
+ NS_DispatchToCurrentThread(runnable);
+ }
+ mFocusedWindow = aWindow;
+}
+
+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->mFocusedContent) {
+ sInstance->mFocusedContent->OwnerDoc()->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mFirstBlurEvent) {
+ sInstance->mFirstBlurEvent->OwnerDoc()->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mFirstFocusEvent) {
+ sInstance->mFirstFocusEvent->OwnerDoc()->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+ if (sInstance->mMouseButtonEventHandlingDocument) {
+ sInstance->mMouseButtonEventHandlingDocument->
+ MarkUncollectableForCCGeneration(aGeneration);
+ }
+}
+
+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 000000000..98deb3c0a
--- /dev/null
+++ b/dom/base/nsFocusManager.h
@@ -0,0 +1,552 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDocument.h"
+#include "nsIFocusManager.h"
+#include "nsIObserver.h"
+#include "nsIWidget.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+#define FOCUSMETHOD_MASK 0xF000
+#define FOCUSMETHODANDRING_MASK 0xF0F000
+
+#define FOCUSMANAGER_CONTRACTID "@mozilla.org/focus-manager;1"
+
+class nsIContent;
+class nsIDocShellTreeItem;
+class nsPIDOMWindowOuter;
+class nsIMessageBroadcaster;
+
+namespace mozilla {
+namespace dom {
+class TabParent;
+}
+}
+
+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
+{
+ typedef mozilla::widget::InputContextAction InputContextAction;
+
+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();
+
+ /**
+ * Retrieve the single focus manager.
+ */
+ static nsFocusManager* GetFocusManager() { return sInstance; }
+
+ /**
+ * A faster version of nsIFocusManager::GetFocusedElement, returning a
+ * raw nsIContent pointer (instead of having AddRef-ed nsIDOMElement
+ * pointer filled in to an out-parameter).
+ */
+ nsIContent* GetFocusedContent() { return mFocusedContent; }
+
+ /**
+ * Return a focused window. Version of nsIFocusManager::GetFocusedWindow.
+ */
+ nsPIDOMWindowOuter* GetFocusedWindow() const { return mFocusedWindow; }
+
+ /**
+ * Return an active window. Version of nsIFocusManager::GetActiveWindow.
+ */
+ nsPIDOMWindowOuter* GetActiveWindow() const { return mActiveWindow; }
+
+ /**
+ * Called when content has been removed.
+ */
+ nsresult ContentRemoved(nsIDocument* aDocument, nsIContent* aContent);
+
+ /**
+ * Called when mouse button event handling is started and finished.
+ */
+ already_AddRefed<nsIDocument>
+ SetMouseButtonHandlingDocument(nsIDocument* aDocument)
+ {
+ nsCOMPtr<nsIDocument> handlingDocument = mMouseButtonEventHandlingDocument;
+ mMouseButtonEventHandlingDocument = aDocument;
+ return handlingDocument.forget();
+ }
+
+ /**
+ * Update the caret with current mode (whether in caret browsing mode or not).
+ */
+ void UpdateCaretForCaretBrowsingMode();
+
+ /**
+ * 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 and aFocusedWindow must both be non-null.
+ */
+ static nsIContent* GetFocusedDescendant(nsPIDOMWindowOuter* aWindow, bool aDeep,
+ nsPIDOMWindowOuter** aFocusedWindow);
+
+ /**
+ * Returns the content node that focus will be redirected to if aContent was
+ * focused. This is used for the special case of certain XUL elements such
+ * as textboxes or input number which redirect focus to an anonymous child.
+ *
+ * aContent must be non-null.
+ *
+ * XXXndeakin this should be removed eventually but I want to do that as
+ * followup work.
+ */
+ static nsIContent* GetRedirectedFocus(nsIContent* aContent);
+
+ /**
+ * Returns an InputContextAction cause for aFlags.
+ */
+ static InputContextAction::Cause GetFocusMoveActionCause(uint32_t aFlags);
+
+ static bool sMouseFocusesFormControl;
+
+ static void MarkUncollectableForCCGeneration(uint32_t aGeneration);
+protected:
+
+ nsFocusManager();
+ ~nsFocusManager();
+
+ /**
+ * Ensure that the widget associated with the currently focused window is
+ * focused at the widget level.
+ */
+ void EnsureCurrentWidgetFocused();
+
+ /**
+ * Iterate over the children of the message broadcaster and notify them
+ * of the activation change.
+ */
+ void ActivateOrDeactivateChildren(nsIMessageBroadcaster* aManager, bool aActive);
+
+ /**
+ * 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).
+ */
+ void SetFocusInner(nsIContent* 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);
+
+ /**
+ * Returns the window that is the lowest common ancestor of both aWindow1
+ * and aWindow2, or null if they share no common ancestor.
+ */
+ already_AddRefed<nsPIDOMWindowOuter>
+ GetCommonAncestor(nsPIDOMWindowOuter* aWindow1, nsPIDOMWindowOuter* aWindow2);
+
+ /**
+ * When aNewWindow is focused, adjust the ancestors of aNewWindow 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 aNewWindow.
+ */
+ void AdjustWindowFocus(nsPIDOMWindowOuter* aNewWindow, bool aCheckPermission);
+
+ /**
+ * 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);
+
+ /**
+ * Checks and returns aContent if it may be focused, another content 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
+ * CheckIfFocusable method returns true. For <area> elements, there is no
+ * frame, so only the IsFocusable method on the content node must be
+ * true.
+ */
+ nsIContent* CheckIfFocusable(nsIContent* aContent, 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 aWindowToClear will be cleared.
+ * aWindowToClear 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.
+ *
+ * aAncestorWindowToFocus 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.
+ */
+ bool Blur(nsPIDOMWindowOuter* aWindowToClear,
+ nsPIDOMWindowOuter* aAncestorWindowToFocus,
+ bool aIsLeavingDocument,
+ bool aAdjustWidget,
+ nsIContent* aContentToFocus = nullptr);
+
+ /**
+ * 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.
+ */
+ void Focus(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aContent,
+ uint32_t aFlags,
+ bool aIsNewDocument,
+ bool aFocusChanged,
+ bool aWindowRaised,
+ bool aAdjustWidget,
+ nsIContent* aContentLostFocus = nullptr);
+
+ /**
+ * Fires 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.
+ */
+ void SendFocusOrBlurEvent(mozilla::EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsIDocument* aDocument,
+ nsISupports* aTarget,
+ uint32_t aFocusMethod,
+ bool aWindowRaised,
+ bool aIsRefocus = false,
+ mozilla::dom::EventTarget* aRelatedTarget = nullptr);
+
+ /**
+ * Send 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).
+ */
+ void SendFocusInOrOutEvent(mozilla::EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsISupports* aTarget,
+ nsPIDOMWindowOuter* aCurrentFocusedWindow,
+ nsIContent* aCurrentFocusedContent,
+ mozilla::dom::EventTarget* aRelatedTarget = nullptr);
+
+ /**
+ * Scrolls aContent into view unless the FLAG_NOSCROLL flag is set.
+ */
+ void ScrollIntoView(nsIPresShell* aPresShell,
+ nsIContent* aContent,
+ uint32_t aFlags);
+
+ /**
+ * Raises the top-level window aWindow at the widget level.
+ */
+ void RaiseWindow(nsPIDOMWindowOuter* aWindow);
+
+ /**
+ * 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.
+ */
+ void UpdateCaret(bool aMoveCaretToFocus,
+ bool aUpdateVisibility,
+ nsIContent* aContent);
+
+ /**
+ * Helper method to move the caret to the focused element aContent.
+ */
+ void MoveCaretToFocus(nsIPresShell* aPresShell, nsIContent* aContent);
+
+ /**
+ * Makes the caret visible or not, depending on aVisible.
+ */
+ nsresult SetCaretVisible(nsIPresShell* 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(nsIDocument* aDocument,
+ nsIPresShell* aPresShell,
+ nsIContent **aStartContent,
+ nsIContent **aEndContent);
+
+ /**
+ * 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.
+ */
+ nsresult DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
+ nsIContent* aStart,
+ int32_t aType, bool aNoParentTraversal,
+ nsIContent** aNextContent);
+
+ /**
+ * 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.
+ */
+ nsresult GetNextTabbableContent(nsIPresShell* aPresShell,
+ nsIContent* aRootContent,
+ nsIContent* aOriginalStartContent,
+ nsIContent* aStartContent,
+ bool aForward,
+ int32_t aCurrentTabIndex,
+ bool aIgnoreTabIndex,
+ bool aForDocumentNavigation,
+ 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,
+ nsIContent* 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.
+ */
+ nsresult FocusFirst(nsIContent* 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.
+ */
+ nsIContent* GetRootForFocus(nsPIDOMWindowOuter* aWindow,
+ nsIDocument* 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.
+ */
+ nsIContent* 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 aContent 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..
+ static void NotifyFocusStateChange(nsIContent* aContent,
+ bool aWindowShouldShowFocusRing,
+ bool aGettingFocus);
+
+ void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow);
+
+ // the currently active and front-most top-most window
+ nsCOMPtr<nsPIDOMWindowOuter> mActiveWindow;
+
+ // the child or top-level window that is currently focused. 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 currently focused content, which is always inside mFocusedWindow. This
+ // is a cached copy of the mFocusedWindow's current content. This may be null
+ // if no content is focused.
+ nsCOMPtr<nsIContent> mFocusedContent;
+
+ // 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.
+ nsCOMPtr<nsIContent> mFirstBlurEvent;
+ nsCOMPtr<nsIContent> 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;
+
+ // A document which is handling a mouse button event.
+ // When a mouse down event process is finished, ESM sets focus to the target
+ // content if it's not consumed. Therefore, while DOM event handlers are
+ // handling mouse down events or preceding mosue down event is consumed but
+ // handling mouse up events, they should be able to steal focus from any
+ // elements even if focus is in chrome content. So, if this isn't nullptr
+ // and the caller can access the document node, the caller should succeed in
+ // moving focus.
+ nsCOMPtr<nsIDocument> mMouseButtonEventHandlingDocument;
+
+ static bool sTestMode;
+
+ // the single focus manager
+ static 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 000000000..23067becd
--- /dev/null
+++ b/dom/base/nsFrameLoader.cpp
@@ -0,0 +1,3490 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "base/basictypes.h"
+
+#include "prenv.h"
+
+#include "mozIApplication.h"
+#include "nsDocShell.h"
+#include "nsIAppsService.h"
+#include "nsIDOMHTMLIFrameElement.h"
+#include "nsIDOMHTMLFrameElement.h"
+#include "nsIDOMMozBrowserFrame.h"
+#include "nsIDOMWindow.h"
+#include "nsIPresShell.h"
+#include "nsIContentInlines.h"
+#include "nsIContentViewer.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebProgress.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIBaseWindow.h"
+#include "nsIBrowser.h"
+#include "nsContentUtils.h"
+#include "nsIXPConnect.h"
+#include "nsUnicharUtils.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScrollable.h"
+#include "nsFrameLoader.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "nsError.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIXULWindow.h"
+#include "nsIEditor.h"
+#include "nsIMozBrowserFrame.h"
+#include "nsISHistory.h"
+#include "nsNullPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsGlobalWindow.h"
+#include "nsPIWindowRoot.h"
+#include "nsLayoutUtils.h"
+#include "nsView.h"
+#include "GroupedSHistory.h"
+#include "PartialSHistory.h"
+
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+
+#include "nsThreadUtils.h"
+
+#include "nsIDOMChromeWindow.h"
+#include "nsInProcessTabChildGlobal.h"
+
+#include "Layers.h"
+#include "ClientLayerManager.h"
+
+#include "AppProcessChecker.h"
+#include "ContentParent.h"
+#include "TabParent.h"
+#include "mozilla/plugins/PPluginWidgetParent.h"
+#include "../plugins/ipc/PluginWidgetParent.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/GuardObjects.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
+#include "mozilla/layout/RenderFrameParent.h"
+#include "nsIAppsService.h"
+#include "GeckoProfiler.h"
+
+#include "jsapi.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "nsSandboxFlags.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/WebBrowserPersistLocalDocument.h"
+
+#include "nsPrincipal.h"
+
+#ifdef MOZ_XUL
+#include "nsXULPopupManager.h"
+#endif
+
+#ifdef NS_PRINTING
+#include "mozilla/embedding/printingui/PrintingParent.h"
+#include "nsIWebBrowserPrint.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::hal;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+using namespace mozilla::layers;
+using namespace mozilla::layout;
+typedef FrameMetrics::ViewID ViewID;
+
+// Bug 136580: Limit to the number of nested content frames that can have the
+// same URL. This is to stop content that is recursively loading
+// itself. Note that "#foo" on the end of URL doesn't affect
+// whether it's considered identical, but "?foo" or ";foo" are
+// considered and compared.
+// Bug 228829: Limit this to 1, like IE does.
+#define MAX_SAME_URL_CONTENT_FRAMES 1
+
+// 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(nsFrameLoader,
+ mDocShell,
+ mMessageManager,
+ mChildMessageManager,
+ mOpener,
+ mPartialSessionHistory,
+ mGroupedSessionHistory)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
+ NS_INTERFACE_MAP_ENTRY(nsIFrameLoader)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFrameLoader)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistable)
+NS_INTERFACE_MAP_END
+
+nsFrameLoader::nsFrameLoader(Element* aOwner, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated)
+ : mOwnerContent(aOwner)
+ , mDetachedSubdocFrame(nullptr)
+ , mOpener(aOpener)
+ , mRemoteBrowser(nullptr)
+ , mChildID(0)
+ , mEventMode(EVENT_MODE_NORMAL_DISPATCH)
+ , mIsPrerendered(false)
+ , mDepthTooGreat(false)
+ , mIsTopLevelContent(false)
+ , mDestroyCalled(false)
+ , mNeedsAsyncDestroy(false)
+ , mInSwap(false)
+ , mInShow(false)
+ , mHideCalled(false)
+ , mNetworkCreated(aNetworkCreated)
+ , mRemoteBrowserShown(false)
+ , mRemoteFrame(false)
+ , mClipSubdocument(true)
+ , mClampScrollPosition(true)
+ , mObservingOwnerContent(false)
+ , mVisible(true)
+ , mFreshProcess(false)
+{
+ mRemoteFrame = ShouldUseRemoteProcess();
+ MOZ_ASSERT(!mRemoteFrame || !aOpener,
+ "Cannot pass aOpener for a remote frame!");
+
+ // Check if we are supposed to load into a fresh process
+ mFreshProcess = mOwnerContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::freshProcess,
+ nsGkAtoms::_true,
+ eCaseMatters);
+}
+
+nsFrameLoader::~nsFrameLoader()
+{
+ if (mMessageManager) {
+ mMessageManager->Disconnect();
+ }
+ MOZ_RELEASE_ASSERT(mDestroyCalled);
+}
+
+nsFrameLoader*
+nsFrameLoader::Create(Element* aOwner, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated)
+{
+ NS_ENSURE_TRUE(aOwner, nullptr);
+ nsIDocument* 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);
+
+ return new nsFrameLoader(aOwner, aOpener, aNetworkCreated);
+}
+
+NS_IMETHODIMP
+nsFrameLoader::LoadFrame()
+{
+ NS_ENSURE_TRUE(mOwnerContent, NS_ERROR_NOT_INITIALIZED);
+
+ nsAutoString src;
+
+ bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) &&
+ mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
+ if (isSrcdoc) {
+ src.AssignLiteral("about:srcdoc");
+ }
+ else {
+ GetURL(src);
+
+ 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 NS_OK;
+ }
+ src.AssignLiteral("about:blank");
+ }
+ }
+
+ nsIDocument* doc = mOwnerContent->OwnerDoc();
+ if (doc->IsStaticDocument()) {
+ return NS_OK;
+ }
+
+ if (doc->IsLoadedAsInteractiveData()) {
+ // XBL bindings doc shouldn't load sub-documents.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> base_uri = mOwnerContent->GetBaseURI();
+ const nsAFlatCString &doc_charset = doc->GetDocumentCharacterSet();
+ const char *charset = doc_charset.IsEmpty() ? nullptr : doc_charset.get();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), src, charset, 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), NS_LITERAL_STRING("about:blank"),
+ charset, base_uri);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = LoadURI(uri);
+ }
+
+ if (NS_FAILED(rv)) {
+ FireErrorEvent();
+
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+nsFrameLoader::FireErrorEvent()
+{
+ if (!mOwnerContent) {
+ return;
+ }
+ RefPtr<AsyncEventDispatcher > loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(mOwnerContent,
+ NS_LITERAL_STRING("error"),
+ false, false);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+}
+
+NS_IMETHODIMP
+nsFrameLoader::LoadURI(nsIURI* aURI)
+{
+ if (!aURI)
+ return NS_ERROR_INVALID_POINTER;
+ NS_ENSURE_STATE(!mDestroyCalled && mOwnerContent);
+
+ nsCOMPtr<nsIDocument> doc = mOwnerContent->OwnerDoc();
+
+ nsresult rv = CheckURILoad(aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mURIToLoad = aURI;
+ rv = doc->InitializeFrameLoader(this);
+ if (NS_FAILED(rv)) {
+ mURIToLoad = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::SetIsPrerendered()
+{
+ MOZ_ASSERT(!mDocShell, "Please call SetIsPrerendered before docShell is created");
+ mIsPrerendered = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::MakePrerenderedLoaderActive()
+{
+ MOZ_ASSERT(mIsPrerendered, "This frameloader was not in prerendered mode.");
+
+ mIsPrerendered = false;
+ if (IsRemoteFrame()) {
+ if (!mRemoteBrowser) {
+ NS_WARNING("Missing remote browser.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mRemoteBrowser->SetDocShellIsActive(true);
+ } else {
+ if (!mDocShell) {
+ NS_WARNING("Missing docshell.");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = mDocShell->SetIsActive(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetPartialSessionHistory(nsIPartialSHistory** aResult)
+{
+ if (mRemoteBrowser && !mPartialSessionHistory) {
+ // For remote case we can lazy initialize PartialSHistory since
+ // it doens't need to be registered as a listener to nsISHistory directly.
+ mPartialSessionHistory = new PartialSHistory(this);
+ }
+
+ nsCOMPtr<nsIPartialSHistory> partialHistory(mPartialSessionHistory);
+ partialHistory.forget(aResult);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFrameLoader::GetGroupedSessionHistory(nsIGroupedSHistory** aResult)
+{
+ nsCOMPtr<nsIGroupedSHistory> groupedHistory(mGroupedSessionHistory);
+ groupedHistory.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::AppendPartialSessionHistoryAndSwap(nsIFrameLoader* aOther)
+{
+ if (!aOther) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ nsCOMPtr<nsIGroupedSHistory> otherGroupedHistory;
+ aOther->GetGroupedSessionHistory(getter_AddRefs(otherGroupedHistory));
+ MOZ_ASSERT(!otherGroupedHistory,
+ "Cannot append a GroupedSHistory owner to another.");
+ if (otherGroupedHistory) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Append ourselves.
+ nsresult rv;
+ if (!mGroupedSessionHistory) {
+ mGroupedSessionHistory = new GroupedSHistory();
+ rv = mGroupedSessionHistory->AppendPartialSessionHistory(mPartialSessionHistory);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (aOther == this) {
+ return NS_OK;
+ }
+
+ // Append the other.
+ RefPtr<nsFrameLoader> otherLoader = static_cast<nsFrameLoader*>(aOther);
+ rv = mGroupedSessionHistory->
+ AppendPartialSessionHistory(otherLoader->mPartialSessionHistory);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Swap loaders through our owner, so the owner's listeners will be correctly
+ // setup.
+ nsCOMPtr<nsIBrowser> ourBrowser = do_QueryInterface(mOwnerContent);
+ nsCOMPtr<nsIBrowser> otherBrowser = do_QueryInterface(otherLoader->mOwnerContent);
+ if (!ourBrowser || !otherBrowser) {
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(ourBrowser->SwapBrowsers(otherBrowser))) {
+ return NS_ERROR_FAILURE;
+ }
+ mGroupedSessionHistory.swap(otherLoader->mGroupedSessionHistory);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::RequestGroupedHistoryNavigation(uint32_t aGlobalIndex)
+{
+ if (!mGroupedSessionHistory) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIFrameLoader> targetLoader;
+ nsresult rv = mGroupedSessionHistory->
+ GotoIndex(aGlobalIndex, getter_AddRefs(targetLoader));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsFrameLoader> otherLoader = static_cast<nsFrameLoader*>(targetLoader.get());
+ if (!targetLoader) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (targetLoader == this) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIBrowser> ourBrowser = do_QueryInterface(mOwnerContent);
+ nsCOMPtr<nsIBrowser> otherBrowser = do_QueryInterface(otherLoader->mOwnerContent);
+ if (!ourBrowser || !otherBrowser) {
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(ourBrowser->SwapBrowsers(otherBrowser))) {
+ return NS_ERROR_FAILURE;
+ }
+ mGroupedSessionHistory.swap(otherLoader->mGroupedSessionHistory);
+
+ return NS_OK;
+}
+
+nsresult
+nsFrameLoader::ReallyStartLoading()
+{
+ nsresult rv = ReallyStartLoadingInternal();
+ if (NS_FAILED(rv)) {
+ FireErrorEvent();
+ }
+
+ return rv;
+}
+
+nsresult
+nsFrameLoader::ReallyStartLoadingInternal()
+{
+ NS_ENSURE_STATE(mURIToLoad && mOwnerContent && mOwnerContent->IsInComposedDoc());
+
+ PROFILER_LABEL("nsFrameLoader", "ReallyStartLoading",
+ js::ProfileEntry::Category::OTHER);
+
+ if (IsRemoteFrame()) {
+ if (!mRemoteBrowser && !TryRemoteBrowser()) {
+ NS_WARNING("Couldn't create child process for iframe.");
+ return NS_ERROR_FAILURE;
+ }
+
+ // FIXME get error codes from child
+ mRemoteBrowser->LoadURL(mURIToLoad);
+
+ if (!mRemoteBrowserShown && !ShowRemoteFrame(ScreenIntSize(0, 0))) {
+ NS_WARNING("[nsFrameLoader] ReallyStartLoadingInternal tried but couldn't show remote browser.\n");
+ }
+
+ return NS_OK;
+ }
+
+ nsresult rv = MaybeCreateDocShell();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ NS_ASSERTION(mDocShell,
+ "MaybeCreateDocShell succeeded with a null mDocShell");
+
+ // Just to be safe, recheck uri.
+ rv = CheckURILoad(mURIToLoad);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ mDocShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
+
+ // If this frame is sandboxed with respect to origin we will set it up with
+ // a null principal later in nsDocShell::DoURILoad.
+ // We do it there to correctly sandbox content that was loaded into
+ // the frame via other methods than the src attribute.
+ // We'll use our principal, not that of the document loaded inside us. This
+ // is very important; needed to prevent XSS attacks on documents loaded in
+ // subframes!
+ loadInfo->SetTriggeringPrincipal(mOwnerContent->NodePrincipal());
+
+ nsCOMPtr<nsIURI> referrer;
+
+ nsAutoString srcdoc;
+ bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) &&
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::srcdoc,
+ srcdoc);
+
+ if (isSrcdoc) {
+ nsAutoString referrerStr;
+ mOwnerContent->OwnerDoc()->GetReferrer(referrerStr);
+ rv = NS_NewURI(getter_AddRefs(referrer), referrerStr);
+
+ loadInfo->SetSrcdocData(srcdoc);
+ nsCOMPtr<nsIURI> baseURI = mOwnerContent->GetBaseURI();
+ loadInfo->SetBaseURI(baseURI);
+ }
+ else {
+ rv = mOwnerContent->NodePrincipal()->GetURI(getter_AddRefs(referrer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Use referrer as long as it is not an nsNullPrincipalURI.
+ // We could add a method such as GetReferrerURI to principals to make this
+ // cleaner, but given that we need to start using Source Browsing Context for
+ // referrer (see Bug 960639) this may be wasted effort at this stage.
+ if (referrer) {
+ bool isNullPrincipalScheme;
+ rv = referrer->SchemeIs(NS_NULLPRINCIPAL_SCHEME, &isNullPrincipalScheme);
+ if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) {
+ loadInfo->SetReferrer(referrer);
+ }
+ }
+
+ // get referrer policy for this iframe:
+ // first load document wide policy, then
+ // load iframe referrer attribute if enabled in preferences
+ // per element referrer overrules document wide referrer if enabled
+ net::ReferrerPolicy referrerPolicy = mOwnerContent->OwnerDoc()->GetReferrerPolicy();
+ HTMLIFrameElement* iframe = HTMLIFrameElement::FromContent(mOwnerContent);
+ if (iframe) {
+ net::ReferrerPolicy iframeReferrerPolicy = iframe->GetReferrerPolicyAsEnum();
+ if (iframeReferrerPolicy != net::RP_Unset) {
+ referrerPolicy = iframeReferrerPolicy;
+ }
+ }
+ loadInfo->SetReferrerPolicy(referrerPolicy);
+
+ // 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;
+ }
+
+ // Kick off the load...
+ bool tmpState = mNeedsAsyncDestroy;
+ mNeedsAsyncDestroy = true;
+ nsCOMPtr<nsIURI> uriToLoad = mURIToLoad;
+ rv = mDocShell->LoadURI(uriToLoad, loadInfo, flags, false);
+ mNeedsAsyncDestroy = tmpState;
+ mURIToLoad = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsFrameLoader::CheckURILoad(nsIURI* aURI)
+{
+ // 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 = mOwnerContent->NodePrincipal();
+
+ // Check if we are allowed to load absURL
+ nsresult rv =
+ secMan->CheckLoadURIWithPrincipal(principal, aURI,
+ nsIScriptSecurityManager::STANDARD);
+ 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);
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetDocShell(nsIDocShell **aDocShell)
+{
+ *aDocShell = nullptr;
+ nsresult rv = NS_OK;
+
+ if (IsRemoteFrame()) {
+ return rv;
+ }
+
+ // 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)) {
+ return rv;
+ }
+ NS_ASSERTION(mDocShell,
+ "MaybeCreateDocShell succeeded, but null mDocShell");
+ }
+
+ *aDocShell = mDocShell;
+ NS_IF_ADDREF(*aDocShell);
+
+ return rv;
+}
+
+static void
+SetTreeOwnerAndChromeEventHandlerOnDocshellTree(nsIDocShellTreeItem* aItem,
+ nsIDocShellTreeOwner* aOwner,
+ EventTarget* aHandler)
+{
+ NS_PRECONDITION(aItem, "Must have item");
+
+ aItem->SetTreeOwner(aOwner);
+
+ int32_t childCount = 0;
+ aItem->GetChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ aItem->GetChildAt(i, getter_AddRefs(item));
+ if (aHandler) {
+ nsCOMPtr<nsIDocShell> shell(do_QueryInterface(item));
+ shell->SetChromeEventHandler(aHandler);
+ }
+ SetTreeOwnerAndChromeEventHandlerOnDocshellTree(item, aOwner, aHandler);
+ }
+}
+
+/**
+ * Set the type of the treeitem and hook it up to the treeowner.
+ * @param aItem the treeitem we're working with
+ * @param aTreeOwner the relevant treeowner; might be null
+ * @param aParentType the nsIDocShellTreeItem::GetType of our parent docshell
+ * @param aParentNode if non-null, the docshell we should be added as a child to
+ *
+ * @return whether aItem is top-level content
+ */
+bool
+nsFrameLoader::AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem,
+ nsIDocShellTreeOwner* aOwner,
+ int32_t aParentType,
+ nsIDocShell* aParentNode)
+{
+ NS_PRECONDITION(aItem, "Must have docshell treeitem");
+ NS_PRECONDITION(mOwnerContent, "Must have owning content");
+
+ nsAutoString value;
+ bool isContent = false;
+ mOwnerContent->GetAttr(kNameSpaceID_None, TypeAttrName(), value);
+
+ // we accept "content" and "content-xxx" values.
+ // at time of writing, we expect "xxx" to be "primary" or "targetable", but
+ // someday it might be an integer expressing priority or something else.
+
+ isContent = value.LowerCaseEqualsLiteral("content") ||
+ StringBeginsWith(value, NS_LITERAL_STRING("content-"),
+ nsCaseInsensitiveStringComparator());
+
+ // Force mozbrowser frames to always be typeContent, even if the
+ // mozbrowser interfaces are disabled.
+ nsCOMPtr<nsIDOMMozBrowserFrame> mozbrowser =
+ do_QueryInterface(mOwnerContent);
+ if (mozbrowser) {
+ bool isMozbrowser = false;
+ mozbrowser->GetMozbrowser(&isMozbrowser);
+ isContent |= isMozbrowser;
+ }
+
+ if (isContent) {
+ // The web shell's type is content.
+
+ aItem->SetItemType(nsIDocShellTreeItem::typeContent);
+ } else {
+ // Inherit our type from our parent docshell. If it is
+ // chrome, we'll be chrome. If it is content, we'll be
+ // content.
+
+ aItem->SetItemType(aParentType);
+ }
+
+ // Now that we have our type set, add ourselves to the parent, as needed.
+ if (aParentNode) {
+ aParentNode->AddChild(aItem);
+ }
+
+ bool retval = false;
+ if (aParentType == nsIDocShellTreeItem::typeChrome && isContent) {
+ retval = true;
+
+ bool is_primary = value.LowerCaseEqualsLiteral("content-primary");
+
+ if (aOwner) {
+ bool is_targetable = is_primary ||
+ value.LowerCaseEqualsLiteral("content-targetable");
+ mOwnerContent->AddMutationObserver(this);
+ mObservingOwnerContent = true;
+ aOwner->ContentShellAdded(aItem, is_primary, is_targetable, value);
+ }
+ }
+
+ return retval;
+}
+
+static bool
+AllDescendantsOfType(nsIDocShellTreeItem* aParentItem, int32_t aType)
+{
+ int32_t childCount = 0;
+ aParentItem->GetChildCount(&childCount);
+
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> kid;
+ aParentItem->GetChildAt(i, getter_AddRefs(kid));
+
+ if (kid->ItemType() != aType || !AllDescendantsOfType(kid, aType)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * A class that automatically sets mInShow to false when it goes
+ * out of scope.
+ */
+class MOZ_RAII AutoResetInShow {
+ private:
+ nsFrameLoader* mFrameLoader;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+ public:
+ explicit AutoResetInShow(nsFrameLoader* aFrameLoader MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mFrameLoader(aFrameLoader)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ }
+ ~AutoResetInShow() { mFrameLoader->mInShow = false; }
+};
+
+
+bool
+nsFrameLoader::Show(int32_t marginWidth, int32_t marginHeight,
+ int32_t scrollbarPrefX, int32_t scrollbarPrefY,
+ nsSubDocumentFrame* frame)
+{
+ if (mInShow) {
+ return false;
+ }
+ // Reset mInShow if we exit early.
+ AutoResetInShow resetInShow(this);
+ mInShow = true;
+
+ ScreenIntSize size = frame->GetSubdocumentSize();
+ if (IsRemoteFrame()) {
+ return ShowRemoteFrame(size, frame);
+ }
+
+ nsresult rv = MaybeCreateDocShell();
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ NS_ASSERTION(mDocShell,
+ "MaybeCreateDocShell succeeded, but null mDocShell");
+ if (!mDocShell) {
+ return false;
+ }
+
+ mDocShell->SetMarginWidth(marginWidth);
+ mDocShell->SetMarginHeight(marginHeight);
+
+ nsCOMPtr<nsIScrollable> sc = do_QueryInterface(mDocShell);
+ if (sc) {
+ sc->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X,
+ scrollbarPrefX);
+ sc->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_Y,
+ scrollbarPrefY);
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+ if (presShell) {
+ // Ensure root scroll frame is reflowed in case scroll preferences or
+ // margins have changed
+ nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+ if (rootScrollFrame) {
+ presShell->FrameNeedsReflow(rootScrollFrame, nsIPresShell::eResize,
+ NS_FRAME_IS_DIRTY);
+ }
+ return true;
+ }
+
+ nsView* view = frame->EnsureInnerView();
+ if (!view)
+ return false;
+
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(mDocShell);
+ NS_ASSERTION(baseWindow, "Found a nsIDocShell that isn't a nsIBaseWindow.");
+ baseWindow->InitWindow(nullptr, view->GetWidget(), 0, 0,
+ size.width, size.height);
+ // This is kinda whacky, this "Create()" call doesn't really
+ // create anything, one starts to wonder why this was named
+ // "Create"...
+ baseWindow->Create();
+ baseWindow->SetVisibility(true);
+ NS_ENSURE_TRUE(mDocShell, 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
+ presShell = mDocShell->GetPresShell();
+ if (presShell) {
+ nsCOMPtr<nsIDOMHTMLDocument> doc =
+ do_QueryInterface(presShell->GetDocument());
+
+ if (doc) {
+ nsAutoString designMode;
+ doc->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.
+ nsCOMPtr<nsIEditor> editor;
+ nsresult rv = mDocShell->GetEditor(getter_AddRefs(editor));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ doc->SetDesignMode(NS_LITERAL_STRING("off"));
+ doc->SetDesignMode(NS_LITERAL_STRING("on"));
+ } else {
+ // Re-initialize the presentation for contenteditable documents
+ bool editable = false,
+ hasEditingSession = false;
+ mDocShell->GetEditable(&editable);
+ mDocShell->GetHasEditingSession(&hasEditingSession);
+ nsCOMPtr<nsIEditor> editor;
+ mDocShell->GetEditor(getter_AddRefs(editor));
+ if (editable && hasEditingSession && editor) {
+ editor->PostCreate();
+ }
+ }
+ }
+ }
+
+ mInShow = false;
+ if (mHideCalled) {
+ mHideCalled = false;
+ Hide();
+ return false;
+ }
+ return true;
+}
+
+void
+nsFrameLoader::MarginsChanged(uint32_t aMarginWidth,
+ uint32_t aMarginHeight)
+{
+ // We assume that the margins are always zero for remote frames.
+ if (IsRemoteFrame())
+ return;
+
+ // If there's no docshell, we're probably not up and running yet.
+ // nsFrameLoader::Show() will take care of setting the right
+ // margins.
+ if (!mDocShell)
+ return;
+
+ // Set the margins
+ mDocShell->SetMarginWidth(aMarginWidth);
+ mDocShell->SetMarginHeight(aMarginHeight);
+
+ // Trigger a restyle if there's a prescontext
+ // FIXME: This could do something much less expensive.
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ if (presContext)
+ presContext->RebuildAllStyleData(nsChangeHint(0), eRestyle_Subtree);
+}
+
+bool
+nsFrameLoader::ShowRemoteFrame(const ScreenIntSize& size,
+ nsSubDocumentFrame *aFrame)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
+ NS_ASSERTION(IsRemoteFrame(), "ShowRemote only makes sense on remote frames.");
+
+ if (!mRemoteBrowser && !TryRemoteBrowser()) {
+ 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;
+ }
+
+ RefPtr<layers::LayerManager> layerManager =
+ nsContentUtils::LayerManagerForDocument(mOwnerContent->GetComposedDoc());
+ if (!layerManager) {
+ // This is just not going to work.
+ return false;
+ }
+
+ nsPIDOMWindowOuter* win = mOwnerContent->OwnerDoc()->GetWindow();
+ bool parentIsActive = false;
+ if (win) {
+ nsCOMPtr<nsPIWindowRoot> windowRoot =
+ nsGlobalWindow::Cast(win)->GetTopWindowRoot();
+ if (windowRoot) {
+ nsPIDOMWindowOuter* topWin = windowRoot->GetWindow();
+ parentIsActive = topWin && topWin->IsActive();
+ }
+ }
+ mRemoteBrowser->Show(size, parentIsActive);
+ mRemoteBrowserShown = true;
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
+ "remote-browser-shown", nullptr);
+ }
+ } 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->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ mRemoteBrowser->UpdateDimensions(dimensions, size);
+ }
+ }
+
+ return true;
+}
+
+void
+nsFrameLoader::Hide()
+{
+ if (mHideCalled) {
+ return;
+ }
+ if (mInShow) {
+ mHideCalled = true;
+ return;
+ }
+
+ if (!mDocShell)
+ return;
+
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ mDocShell->GetContentViewer(getter_AddRefs(contentViewer));
+ if (contentViewer)
+ contentViewer->SetSticky(false);
+
+ nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell);
+ NS_ASSERTION(baseWin,
+ "Found an nsIDocShell which doesn't implement nsIBaseWindow.");
+ baseWin->SetVisibility(false);
+ baseWin->SetParentWidget(nullptr);
+}
+
+nsresult
+nsFrameLoader::SwapWithOtherRemoteLoader(nsFrameLoader* aOther,
+ nsIFrameLoaderOwner* aThisOwner,
+ nsIFrameLoaderOwner* 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;
+ }
+
+ nsIDocument* ourDoc = ourContent->GetComposedDoc();
+ nsIDocument* otherDoc = otherContent->GetComposedDoc();
+ if (!ourDoc || !otherDoc) {
+ // Again, how odd, given that we had docshells
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsIPresShell* ourShell = ourDoc->GetShell();
+ nsIPresShell* otherShell = otherDoc->GetShell();
+ if (!ourShell || !otherShell) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!mRemoteBrowser || !aOther->mRemoteBrowser) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (mRemoteBrowser->IsIsolatedMozBrowserElement() !=
+ aOther->mRemoteBrowser->IsIsolatedMozBrowserElement() ||
+ mRemoteBrowser->HasOwnApp() != aOther->mRemoteBrowser->HasOwnApp()) {
+ 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 XULElement, 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.
+ DocShellOriginAttributes ourOriginAttributes =
+ mRemoteBrowser->OriginAttributesRef();
+ rv = PopulateUserContextIdFromAttribute(ourOriginAttributes);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ DocShellOriginAttributes otherOriginAttributes =
+ aOther->mRemoteBrowser->OriginAttributesRef();
+ rv = aOther->PopulateUserContextIdFromAttribute(otherOriginAttributes);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (ourOriginAttributes != 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;
+
+ 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 =
+ aOther->mRemoteBrowser->GetBrowserDOMWindow();
+ nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
+ mRemoteBrowser->GetBrowserDOMWindow();
+
+ if (!!otherBrowserDOMWindow != !!browserDOMWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Destroy browser frame scripts for content leaving a frame with browser API
+ if (OwnerIsMozBrowserOrAppFrame() && !aOther->OwnerIsMozBrowserOrAppFrame()) {
+ DestroyBrowserFrameScripts();
+ }
+ if (!OwnerIsMozBrowserOrAppFrame() && aOther->OwnerIsMozBrowserOrAppFrame()) {
+ aOther->DestroyBrowserFrameScripts();
+ }
+
+ aOther->mRemoteBrowser->SetBrowserDOMWindow(browserDOMWindow);
+ mRemoteBrowser->SetBrowserDOMWindow(otherBrowserDOMWindow);
+
+ // Native plugin windows used by this remote content need to be reparented.
+ if (nsPIDOMWindowOuter* newWin = ourDoc->GetWindow()) {
+ RefPtr<nsIWidget> newParent = nsGlobalWindow::Cast(newWin)->GetMainWidget();
+ const ManagedContainer<mozilla::plugins::PPluginWidgetParent>& plugins =
+ aOther->mRemoteBrowser->ManagedPPluginWidgetParent();
+ for (auto iter = plugins.ConstIter(); !iter.Done(); iter.Next()) {
+ static_cast<mozilla::plugins::PluginWidgetParent*>(iter.Get()->GetKey())->SetParent(newParent);
+ }
+ }
+
+ MaybeUpdatePrimaryTabParent(eTabParentRemoved);
+ aOther->MaybeUpdatePrimaryTabParent(eTabParentRemoved);
+
+ SetOwnerContent(otherContent);
+ aOther->SetOwnerContent(ourContent);
+
+ mRemoteBrowser->SetOwnerElement(otherContent);
+ aOther->mRemoteBrowser->SetOwnerElement(ourContent);
+
+ MaybeUpdatePrimaryTabParent(eTabParentChanged);
+ aOther->MaybeUpdatePrimaryTabParent(eTabParentChanged);
+
+ 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);
+
+ // 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.
+ nsCOMPtr<nsIFrameLoader> kungFuDeathGrip(this);
+ aThisOwner->InternalSetFrameLoader(aOther);
+ aOtherOwner->InternalSetFrameLoader(kungFuDeathGrip);
+
+ ourFrameFrame->EndSwapDocShells(otherFrame);
+
+ ourShell->BackingScaleFactorChanged();
+ otherShell->BackingScaleFactorChanged();
+
+ ourDoc->FlushPendingNotifications(Flush_Layout);
+ otherDoc->FlushPendingNotifications(Flush_Layout);
+
+ // 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 << mRemoteBrowser->SendSwappedWithOtherRemoteLoader(
+ ourContext.AsIPCTabContext());
+ Unused << aOther->mRemoteBrowser->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
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mThisFrameLoader(aThisFrameLoader)
+ , mOtherFrameLoader(aOtherFrameLoader)
+ , mThisDocShell(aThisDocShell)
+ , mOtherDocShell(aOtherDocShell)
+ , mThisEventTarget(aThisEventTarget)
+ , mOtherEventTarget(aOtherEventTarget)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+ 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::FirePageShowEvent(mThisDocShell, mThisEventTarget, false);
+ nsContentUtils::FirePageShowEvent(mOtherDocShell, mOtherEventTarget, false);
+ nsContentUtils::FirePageHideEvent(mThisDocShell, mThisEventTarget);
+ nsContentUtils::FirePageHideEvent(mOtherDocShell, mOtherEventTarget);
+ }
+
+ ~AutoResetInFrameSwap()
+ {
+ nsContentUtils::FirePageShowEvent(mThisDocShell, mThisEventTarget, true);
+ nsContentUtils::FirePageShowEvent(mOtherDocShell, mOtherEventTarget, true);
+
+ mThisFrameLoader->mInSwap = false;
+ mOtherFrameLoader->mInSwap = false;
+ mThisDocShell->SetInFrameSwap(false);
+ mOtherDocShell->SetInFrameSwap(false);
+ }
+
+private:
+ RefPtr<nsFrameLoader> mThisFrameLoader;
+ RefPtr<nsFrameLoader> mOtherFrameLoader;
+ RefPtr<nsDocShell> mThisDocShell;
+ RefPtr<nsDocShell> mOtherDocShell;
+ nsCOMPtr<EventTarget> mThisEventTarget;
+ nsCOMPtr<EventTarget> mOtherEventTarget;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+nsresult
+nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
+ nsIFrameLoaderOwner* aThisOwner,
+ nsIFrameLoaderOwner* 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;
+ }
+
+ Element* ourContent = mOwnerContent;
+ Element* otherContent = aOther->mOwnerContent;
+
+ if (!ourContent || !otherContent) {
+ // Can't handle this
+ 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() ||
+ (OwnerIsMozBrowserOrAppFrame() &&
+ (ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
+ ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)));
+ bool otherFullscreenAllowed =
+ otherContent->IsXULElement() ||
+ (aOther->OwnerIsMozBrowserOrAppFrame() &&
+ (otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
+ otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)));
+ if (ourFullscreenAllowed != otherFullscreenAllowed) {
+ 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 = static_cast<nsDocShell*>(GetExistingDocShell());
+ RefPtr<nsDocShell> otherDocshell = static_cast<nsDocShell*>(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->GetSameTypeRootTreeItem(getter_AddRefs(ourRootTreeItem));
+ otherDocshell->GetSameTypeRootTreeItem(getter_AddRefs(otherRootTreeItem));
+ nsCOMPtr<nsIWebNavigation> ourRootWebnav =
+ do_QueryInterface(ourRootTreeItem);
+ nsCOMPtr<nsIWebNavigation> otherRootWebnav =
+ do_QueryInterface(otherRootTreeItem);
+
+ if (!ourRootWebnav || !otherRootWebnav) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsISHistory> ourHistory;
+ nsCOMPtr<nsISHistory> otherHistory;
+ ourRootWebnav->GetSessionHistory(getter_AddRefs(ourHistory));
+ otherRootWebnav->GetSessionHistory(getter_AddRefs(otherHistory));
+
+ if ((ourRootTreeItem != ourDocshell || otherRootTreeItem != otherDocshell) &&
+ (ourHistory || otherHistory)) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Also make sure that the two docshells 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.
+ int32_t ourType = ourDocshell->ItemType();
+ int32_t otherType = otherDocshell->ItemType();
+ if (ourType != otherType) {
+ 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 ourType is not
+ // nsIDocShellTreeItem::typeContent then all of our descendants are the same
+ // type as us.
+ if (ourType != nsIDocShellTreeItem::typeContent &&
+ (!AllDescendantsOfType(ourDocshell, ourType) ||
+ !AllDescendantsOfType(otherDocshell, otherType))) {
+ 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->GetParent(getter_AddRefs(ourParentItem));
+ otherDocshell->GetParent(getter_AddRefs(otherParentItem));
+ if (!ourParentItem || !otherParentItem) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Make sure our parents are the same type too
+ int32_t ourParentType = ourParentItem->ItemType();
+ int32_t otherParentType = otherParentItem->ItemType();
+ if (ourParentType != otherParentType) {
+ 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 =
+ do_QueryInterface(ourWindow->GetChromeEventHandler());
+ nsCOMPtr<EventTarget> otherChromeEventHandler =
+ do_QueryInterface(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<nsIDocument> ourChildDocument = ourWindow->GetExtantDoc();
+ nsCOMPtr<nsIDocument> otherChildDocument = otherWindow ->GetExtantDoc();
+ if (!ourChildDocument || !otherChildDocument) {
+ // This shouldn't be happening
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<nsIDocument> ourParentDocument =
+ ourChildDocument->GetParentDocument();
+ nsCOMPtr<nsIDocument> otherParentDocument =
+ otherChildDocument->GetParentDocument();
+
+ // Make sure to swap docshells between the two frames.
+ nsIDocument* ourDoc = ourContent->GetComposedDoc();
+ nsIDocument* 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");
+
+ nsIPresShell* ourShell = ourDoc->GetShell();
+ nsIPresShell* otherShell = otherDoc->GetShell();
+ if (!ourShell || !otherShell) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (ourDocshell->GetIsIsolatedMozBrowserElement() !=
+ otherDocshell->GetIsIsolatedMozBrowserElement() ||
+ ourDocshell->GetIsApp() != otherDocshell->GetIsApp()) {
+ 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 XULElement, 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.
+ DocShellOriginAttributes ourOriginAttributes =
+ ourDocshell->GetOriginAttributes();
+ rv = PopulateUserContextIdFromAttribute(ourOriginAttributes);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ DocShellOriginAttributes otherOriginAttributes =
+ otherDocshell->GetOriginAttributes();
+ rv = aOther->PopulateUserContextIdFromAttribute(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);
+
+ nsIFrame* ourFrame = ourContent->GetPrimaryFrame();
+ nsIFrame* otherFrame = otherContent->GetPrimaryFrame();
+ if (!ourFrame || !otherFrame) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ 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 (OwnerIsMozBrowserOrAppFrame() && !aOther->OwnerIsMozBrowserOrAppFrame()) {
+ DestroyBrowserFrameScripts();
+ }
+ if (!OwnerIsMozBrowserOrAppFrame() && aOther->OwnerIsMozBrowserOrAppFrame()) {
+ 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 (ourType == nsIDocShellTreeItem::typeContent) {
+ 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,
+ ourType == nsIDocShellTreeItem::typeContent ? otherChromeEventHandler.get() : nullptr);
+ SetTreeOwnerAndChromeEventHandlerOnDocshellTree(otherDocshell, ourOwner,
+ ourType == nsIDocShellTreeItem::typeContent ? 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, otherParentType, nullptr);
+ aOther->AddTreeItemToTreeOwner(otherDocshell, ourOwner, ourParentType,
+ nullptr);
+
+ // 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) {
+ nsInProcessTabChildGlobal* tabChild =
+ static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get());
+ tabChild->SetOwner(otherContent);
+ tabChild->SetChromeMessageManager(otherMessageManager);
+ }
+ if (aOther->mChildMessageManager) {
+ nsInProcessTabChildGlobal* otherTabChild =
+ static_cast<nsInProcessTabChildGlobal*>(aOther->mChildMessageManager.get());
+ otherTabChild->SetOwner(ourContent);
+ otherTabChild->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.
+ nsCOMPtr<nsIFrameLoader> kungFuDeathGrip(this);
+ aThisOwner->InternalSetFrameLoader(aOther);
+ aOtherOwner->InternalSetFrameLoader(kungFuDeathGrip);
+
+ // Drop any cached content viewers in the two session histories.
+ nsCOMPtr<nsISHistoryInternal> ourInternalHistory =
+ do_QueryInterface(ourHistory);
+ nsCOMPtr<nsISHistoryInternal> otherInternalHistory =
+ do_QueryInterface(otherHistory);
+ if (ourInternalHistory) {
+ ourInternalHistory->EvictAllContentViewers();
+ }
+ if (otherInternalHistory) {
+ otherInternalHistory->EvictAllContentViewers();
+ }
+
+ 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)
+ ourShell->BackingScaleFactorChanged();
+ otherShell->BackingScaleFactorChanged();
+
+ ourParentDocument->FlushPendingNotifications(Flush_Layout);
+ otherParentDocument->FlushPendingNotifications(Flush_Layout);
+
+ // Initialize browser API if needed now that owner content has changed
+ InitializeBrowserAPI();
+ aOther->InitializeBrowserAPI();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::Destroy()
+{
+ StartDestroy();
+ return NS_OK;
+}
+
+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)
+ : mFrameLoader(aFrameLoader), mPhase(eDestroyDocShell) {}
+
+ NS_IMETHOD Run() override;
+};
+
+void
+nsFrameLoader::StartDestroy()
+{
+ // 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;
+
+ // 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 (mRemoteBrowser) {
+ mRemoteBrowser->CacheFrameLoader(this);
+ }
+ if (mChildMessageManager) {
+ mChildMessageManager->CacheFrameLoader(this);
+ }
+ }
+
+ // If the TabParent has installed any event listeners on the window, this is
+ // its last chance to remove them while we're still in the document.
+ if (mRemoteBrowser) {
+ mRemoteBrowser->RemoveWindowListeners();
+ }
+
+ nsCOMPtr<nsIDocument> doc;
+ bool dynamicSubframeRemoval = false;
+ if (mOwnerContent) {
+ doc = mOwnerContent->OwnerDoc();
+ dynamicSubframeRemoval = !mIsTopLevelContent && !doc->InUnlinkOrDeletion();
+ doc->SetSubDocumentFor(mOwnerContent, nullptr);
+ MaybeUpdatePrimaryTabParent(eTabParentRemoved);
+ SetOwnerContent(nullptr);
+ }
+
+ // Seems like this is a dynamic frame removal.
+ if (dynamicSubframeRemoval) {
+ if (mDocShell) {
+ mDocShell->RemoveFromSessionHistory();
+ }
+ }
+
+ // Let the tree owner know we're gone.
+ if (mIsTopLevelContent) {
+ if (mDocShell) {
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ mDocShell->GetParent(getter_AddRefs(parentItem));
+ nsCOMPtr<nsIDocShellTreeOwner> owner = do_GetInterface(parentItem);
+ if (owner) {
+ owner->ContentShellRemoved(mDocShell);
+ }
+ }
+ }
+
+ // Let our window know that we are gone
+ if (mDocShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> win_private(mDocShell->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, TabParent will eventually call
+ // DestroyComplete once it receives a __delete__ message from the child. In
+ // the in-process case, 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->mChildMessageManager) {
+ // 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 TabChild 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->Destroy();
+ }
+
+ // Fire the "unload" event if we're in-process.
+ if (mChildMessageManager) {
+ static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->FireUnloadEvent();
+ }
+
+ // Destroy the docshell.
+ nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
+ if (base_win) {
+ base_win->Destroy();
+ }
+ mDocShell = nullptr;
+
+ if (mChildMessageManager) {
+ // Stop handling events in the in-process frame script.
+ static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->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 (mRemoteBrowser) {
+ mRemoteBrowser->CacheFrameLoader(nullptr);
+ }
+ if (mChildMessageManager) {
+ mChildMessageManager->CacheFrameLoader(nullptr);
+ }
+ }
+
+ // Call TabParent::Destroy if we haven't already (in case of a crash).
+ if (mRemoteBrowser) {
+ mRemoteBrowser->SetOwnerElement(nullptr);
+ mRemoteBrowser->Destroy();
+ mRemoteBrowser = nullptr;
+ }
+
+ if (mMessageManager) {
+ mMessageManager->Disconnect();
+ }
+
+ if (mChildMessageManager) {
+ static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get())->Disconnect();
+ }
+
+ mMessageManager = nullptr;
+ mChildMessageManager = nullptr;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetDepthTooGreat(bool* aDepthTooGreat)
+{
+ *aDepthTooGreat = mDepthTooGreat;
+ return NS_OK;
+}
+
+void
+nsFrameLoader::SetOwnerContent(Element* aContent)
+{
+ if (mObservingOwnerContent) {
+ mObservingOwnerContent = false;
+ mOwnerContent->RemoveMutationObserver(this);
+ }
+ mOwnerContent = aContent;
+ if (RenderFrameParent* rfp = GetCurrentRenderFrame()) {
+ rfp->OwnerContentChanged(aContent);
+ }
+}
+
+bool
+nsFrameLoader::OwnerIsMozBrowserOrAppFrame()
+{
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false;
+}
+
+// The xpcom getter version
+NS_IMETHODIMP
+nsFrameLoader::GetOwnerIsMozBrowserOrAppFrame(bool* aResult)
+{
+ *aResult = OwnerIsMozBrowserOrAppFrame();
+ return NS_OK;
+}
+
+bool
+nsFrameLoader::OwnerIsAppFrame()
+{
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ return browserFrame ? browserFrame->GetReallyIsApp() : false;
+}
+
+bool
+nsFrameLoader::OwnerIsMozBrowserFrame()
+{
+ return OwnerIsMozBrowserOrAppFrame() && !OwnerIsAppFrame();
+}
+
+bool
+nsFrameLoader::OwnerIsIsolatedMozBrowserFrame()
+{
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ if (!browserFrame) {
+ return false;
+ }
+
+ if (!OwnerIsMozBrowserFrame()) {
+ return false;
+ }
+
+ bool isolated = browserFrame->GetIsolated();
+ if (isolated) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsFrameLoader::GetOwnerAppManifestURL(nsAString& aOut)
+{
+ aOut.Truncate();
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ if (browserFrame) {
+ browserFrame->GetAppManifestURL(aOut);
+ }
+}
+
+already_AddRefed<mozIApplication>
+nsFrameLoader::GetOwnApp()
+{
+ nsAutoString manifest;
+ GetOwnerAppManifestURL(manifest);
+ if (manifest.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(appsService, nullptr);
+
+ nsCOMPtr<mozIApplication> app;
+ appsService->GetAppByManifestURL(manifest, getter_AddRefs(app));
+
+ return app.forget();
+}
+
+already_AddRefed<mozIApplication>
+nsFrameLoader::GetContainingApp()
+{
+ // See if our owner content's principal has an associated app.
+ uint32_t appId = mOwnerContent->NodePrincipal()->GetAppId();
+ MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
+
+ if (appId == nsIScriptSecurityManager::NO_APP_ID ||
+ appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(appsService, nullptr);
+
+ nsCOMPtr<mozIApplication> app;
+ appsService->GetAppByLocalId(appId, getter_AddRefs(app));
+
+ return app.forget();
+}
+
+bool
+nsFrameLoader::ShouldUseRemoteProcess()
+{
+ if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") ||
+ Preferences::GetBool("dom.ipc.tabs.disabled", false)) {
+ return false;
+ }
+
+ // Don't try to launch nested children if we don't have OMTC.
+ // They won't render!
+ if (XRE_IsContentProcess() &&
+ !CompositorBridgeChild::ChildProcessHasCompositorBridge()) {
+ return false;
+ }
+
+ if (XRE_IsContentProcess() &&
+ !(PR_GetEnv("MOZ_NESTED_OOP_TABS") ||
+ Preferences::GetBool("dom.ipc.tabs.nested.enabled", false))) {
+ return false;
+ }
+
+ // If we're an <iframe mozbrowser> and we don't have a "remote" attribute,
+ // fall back to the default.
+ if (OwnerIsMozBrowserOrAppFrame() &&
+ !mOwnerContent->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 (OwnerIsMozBrowserOrAppFrame() ||
+ mOwnerContent->GetNameSpaceID() == kNameSpaceID_XUL) &&
+ mOwnerContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::Remote,
+ nsGkAtoms::_true,
+ eCaseMatters);
+}
+
+bool
+nsFrameLoader::IsRemoteFrame()
+{
+ if (mRemoteFrame) {
+ MOZ_ASSERT(!mDocShell, "Found a remote frame with a DocShell");
+ return true;
+ }
+ return false;
+}
+
+nsresult
+nsFrameLoader::MaybeCreateDocShell()
+{
+ if (mDocShell) {
+ return NS_OK;
+ }
+ if (IsRemoteFrame()) {
+ return NS_OK;
+ }
+ NS_ENSURE_STATE(!mDestroyCalled);
+
+ // 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.
+ nsIDocument* doc = mOwnerContent->OwnerDoc();
+
+ MOZ_RELEASE_ASSERT(!doc->IsResourceDoc(), "We shouldn't even exist");
+
+ if (!(doc->IsStaticDocument() || 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;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
+ nsCOMPtr<nsIWebNavigation> parentAsWebNav = do_QueryInterface(docShell);
+ NS_ENSURE_STATE(parentAsWebNav);
+
+ // Create the docshell...
+ mDocShell = do_CreateInstance("@mozilla.org/docshell;1");
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+ if (mIsPrerendered) {
+ nsresult rv = mDocShell->SetIsPrerendered();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (!mNetworkCreated) {
+ if (mDocShell) {
+ mDocShell->SetCreatedDynamically(true);
+ }
+ }
+
+ // Get the frame name and tell the docshell about it.
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+ nsAutoString frameName;
+
+ int32_t namespaceID = mOwnerContent->GetNameSpaceID();
+ if (namespaceID == kNameSpaceID_XHTML && !mOwnerContent->IsInHTMLDocument()) {
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, frameName);
+ } else {
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, frameName);
+ // 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 (frameName.IsEmpty() && namespaceID == kNameSpaceID_XUL) {
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, frameName);
+ }
+ }
+
+ if (!frameName.IsEmpty()) {
+ mDocShell->SetName(frameName);
+ }
+
+ // Inform our docShell that it has a new child.
+ // Note: This logic duplicates a lot of logic in
+ // nsSubDocumentFrame::AttributeChanged. We should fix that.
+
+ int32_t parentType = docShell->ItemType();
+
+ // XXXbz why is this in content code, exactly? We should handle
+ // this some other way..... Not sure how yet.
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
+ docShell->GetTreeOwner(getter_AddRefs(parentTreeOwner));
+ NS_ENSURE_STATE(parentTreeOwner);
+ mIsTopLevelContent =
+ AddTreeItemToTreeOwner(mDocShell, parentTreeOwner, parentType, docShell);
+
+ // Make sure all shells have links back to the content element
+ // in the nearest enclosing chrome shell.
+ nsCOMPtr<nsIDOMEventTarget> chromeEventHandler;
+
+ if (parentType == nsIDocShellTreeItem::typeChrome) {
+ // Our parent shell is a chrome shell. It is therefore our nearest
+ // enclosing chrome shell.
+
+ chromeEventHandler = do_QueryInterface(mOwnerContent);
+ NS_ASSERTION(chromeEventHandler,
+ "This mContent should implement this.");
+ } else {
+ // Our parent shell is a content shell. Get the chrome event
+ // handler from it and use that for our shell as well.
+
+ docShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
+ }
+
+ mDocShell->SetChromeEventHandler(chromeEventHandler);
+
+ // This is nasty, this code (the mDocShell->GetWindow() below)
+ // *must* come *after* the above call to
+ // mDocShell->SetChromeEventHandler() for the global window to get
+ // the right chrome event handler.
+
+ // Tell the window about the frame that hosts it.
+ nsCOMPtr<Element> frame_element = mOwnerContent;
+ NS_ASSERTION(frame_element, "frame loader owner element not a DOM element!");
+
+ nsCOMPtr<nsPIDOMWindowOuter> win_private(mDocShell->GetWindow());
+ nsCOMPtr<nsIBaseWindow> base_win(do_QueryInterface(mDocShell));
+ if (win_private) {
+ win_private->SetFrameElementInternal(frame_element);
+
+ // Set the opener window if we have one provided here
+ if (mOpener) {
+ win_private->SetOpenerWindow(mOpener, true);
+ mOpener = nullptr;
+ }
+ }
+
+ // This is kinda whacky, this call doesn't really create anything,
+ // but it must be called to make sure things are properly
+ // initialized.
+ if (NS_FAILED(base_win->Create()) || !win_private) {
+ // Do not call Destroy() here. See bug 472312.
+ NS_WARNING("Something wrong when creating the docshell for a frameloader!");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mIsTopLevelContent &&
+ mOwnerContent->IsXULElement(nsGkAtoms::browser) &&
+ !mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory)) {
+ nsresult rv;
+ nsCOMPtr<nsISHistory> sessionHistory =
+ do_CreateInstance(NS_SHISTORY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ webNav->SetSessionHistory(sessionHistory);
+
+
+ if (GroupedSHistory::GroupedHistoryEnabled()) {
+ mPartialSessionHistory = new PartialSHistory(this);
+ nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(mPartialSessionHistory));
+ nsCOMPtr<nsIPartialSHistoryListener> partialListener(do_QueryInterface(mPartialSessionHistory));
+ sessionHistory->AddSHistoryListener(listener);
+ sessionHistory->SetPartialSHistoryListener(partialListener);
+ }
+ }
+
+ DocShellOriginAttributes attrs;
+ if (docShell->ItemType() == mDocShell->ItemType()) {
+ attrs = nsDocShell::Cast(docShell)->GetOriginAttributes();
+ }
+
+ // Inherit origin attributes from parent document if
+ // 1. It's in a content docshell.
+ // 2. its nodePrincipal is not a SystemPrincipal.
+ // 3. It's not a mozbrowser nor mozapp frame.
+ //
+ // For example, firstPartyDomain is computed from top-level document, it
+ // doesn't exist in the top-level docshell.
+ if (parentType == nsIDocShellTreeItem::typeContent &&
+ !nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()) &&
+ !OwnerIsMozBrowserOrAppFrame()) {
+ PrincipalOriginAttributes poa = BasePrincipal::Cast(doc->NodePrincipal())->OriginAttributesRef();
+
+ // Assert on the firstPartyDomain from top-level docshell should be empty
+ if (mIsTopLevelContent) {
+ MOZ_ASSERT(attrs.mFirstPartyDomain.IsEmpty(),
+ "top-level docshell shouldn't have firstPartyDomain attribute.");
+ }
+
+ // So far we want to make sure InheritFromDocToChildDocShell doesn't override
+ // any other origin attribute than firstPartyDomain.
+ MOZ_ASSERT(attrs.mAppId == poa.mAppId,
+ "docshell and document should have the same appId attribute.");
+ MOZ_ASSERT(attrs.mUserContextId == poa.mUserContextId,
+ "docshell and document should have the same userContextId attribute.");
+ MOZ_ASSERT(attrs.mInIsolatedMozBrowser == poa.mInIsolatedMozBrowser,
+ "docshell and document should have the same inIsolatedMozBrowser attribute.");
+ MOZ_ASSERT(attrs.mPrivateBrowsingId == poa.mPrivateBrowsingId,
+ "docshell and document should have the same privateBrowsingId attribute.");
+
+ attrs.InheritFromDocToChildDocShell(poa);
+ }
+
+ if (OwnerIsAppFrame()) {
+ // You can't be both an app and a browser frame.
+ MOZ_ASSERT(!OwnerIsMozBrowserFrame());
+
+ nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+ MOZ_ASSERT(ownApp);
+ uint32_t ownAppId = nsIScriptSecurityManager::NO_APP_ID;
+ if (ownApp) {
+ NS_ENSURE_SUCCESS(ownApp->GetLocalId(&ownAppId), NS_ERROR_FAILURE);
+ }
+
+ attrs.mAppId = ownAppId;
+ mDocShell->SetFrameType(nsIDocShell::FRAME_TYPE_APP);
+ }
+
+ if (OwnerIsMozBrowserFrame()) {
+ // You can't be both a browser and an app frame.
+ MOZ_ASSERT(!OwnerIsAppFrame());
+
+ nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
+ uint32_t containingAppId = nsIScriptSecurityManager::NO_APP_ID;
+ if (containingApp) {
+ NS_ENSURE_SUCCESS(containingApp->GetLocalId(&containingAppId),
+ NS_ERROR_FAILURE);
+ }
+
+ attrs.mAppId = containingAppId;
+ attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
+ mDocShell->SetFrameType(nsIDocShell::FRAME_TYPE_BROWSER);
+ }
+
+ // 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 mDocShell->SetFrameType
+ // because we need to get the correct presentation URL in ApplySandboxFlags.
+ uint32_t sandboxFlags = 0;
+ HTMLIFrameElement* iframe = HTMLIFrameElement::FromContent(mOwnerContent);
+ if (iframe) {
+ sandboxFlags = iframe->GetSandboxFlags();
+ }
+ ApplySandboxFlags(sandboxFlags);
+
+ // Grab the userContextId from owner if XUL
+ nsresult rv = PopulateUserContextIdFromAttribute(attrs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isPrivate = false;
+ nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(docShell);
+ NS_ENSURE_STATE(parentContext);
+
+ rv = parentContext->GetUsePrivateBrowsing(&isPrivate);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ attrs.SyncAttributesWithPrivateBrowsing(isPrivate);
+
+ if (OwnerIsMozBrowserOrAppFrame()) {
+ // For inproc frames, set the docshell properties.
+ nsAutoString name;
+ if (mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name)) {
+ docShell->SetName(name);
+ }
+ mDocShell->SetFullscreenAllowed(
+ mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
+ mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen));
+ bool isPrivate = mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozprivatebrowsing);
+ if (isPrivate) {
+ if (mDocShell->GetHasLoadedNonBlankURI()) {
+ nsContentUtils::ReportToConsoleNonLocalized(
+ NS_LITERAL_STRING("We should not switch to Private Browsing after loading a document."),
+ nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("mozprivatebrowsing"),
+ nullptr);
+ } else {
+ // This handles the case where a frames private browsing is set by chrome flags
+ // and not inherited by its parent.
+ attrs.SyncAttributesWithPrivateBrowsing(isPrivate);
+ }
+ }
+ }
+
+ nsDocShell::Cast(mDocShell)->SetOriginAttributes(attrs);
+
+ ReallyLoadFrameScripts();
+ InitializeBrowserAPI();
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
+ "inprocess-browser-shown", nullptr);
+ }
+
+ return NS_OK;
+}
+
+void
+nsFrameLoader::GetURL(nsString& aURI)
+{
+ aURI.Truncate();
+
+ if (mOwnerContent->IsHTMLElement(nsGkAtoms::object)) {
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::data, aURI);
+ } else {
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, aURI);
+ }
+}
+
+nsresult
+nsFrameLoader::CheckForRecursiveLoad(nsIURI* aURI)
+{
+ nsresult rv;
+
+ MOZ_ASSERT(!IsRemoteFrame(),
+ "Shouldn't call CheckForRecursiveLoad on remote frames.");
+
+ mDepthTooGreat = false;
+ rv = MaybeCreateDocShell();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ NS_ASSERTION(mDocShell,
+ "MaybeCreateDocShell succeeded, but null mDocShell");
+ if (!mDocShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Check that we're still in the docshell tree.
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ NS_WARNING_ASSERTION(treeOwner,
+ "Trying to load a new url to a docshell without owner!");
+ NS_ENSURE_STATE(treeOwner);
+
+ if (mDocShell->ItemType() != nsIDocShellTreeItem::typeContent) {
+ // No need to do recursion-protection here XXXbz why not?? Do we really
+ // trust people not to screw up with non-content docshells?
+ return NS_OK;
+ }
+
+ // Bug 8065: Don't exceed some maximum depth in content frames
+ // (MAX_DEPTH_CONTENT_FRAMES)
+ nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
+ mDocShell->GetSameTypeParent(getter_AddRefs(parentAsItem));
+ int32_t depth = 0;
+ while (parentAsItem) {
+ ++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?)
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> temp;
+ temp.swap(parentAsItem);
+ temp->GetSameTypeParent(getter_AddRefs(parentAsItem));
+ }
+
+ // Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs.
+ // srcdoc URIs require their contents to be specified inline, so it isn't
+ // possible for undesirable recursion to occur without the aid of a
+ // non-srcdoc URI, which this method will block normally.
+ // Besides, URI is not enough to guarantee uniqueness of srcdoc documents.
+ nsAutoCString buffer;
+ rv = aURI->GetScheme(buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("about")) {
+ rv = aURI->GetPath(buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) {
+ // Duplicates allowed up to depth limits
+ return NS_OK;
+ }
+ }
+ int32_t matchCount = 0;
+ mDocShell->GetSameTypeParent(getter_AddRefs(parentAsItem));
+ while (parentAsItem) {
+ // Check the parent URI with the URI we're loading
+ nsCOMPtr<nsIWebNavigation> parentAsNav(do_QueryInterface(parentAsItem));
+ if (parentAsNav) {
+ // Does the URI match the one we're about to load?
+ nsCOMPtr<nsIURI> parentURI;
+ parentAsNav->GetCurrentURI(getter_AddRefs(parentURI));
+ if (parentURI) {
+ // Bug 98158/193011: We need to ignore data after the #
+ bool equal;
+ rv = aURI->EqualsExceptRef(parentURI, &equal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (equal) {
+ matchCount++;
+ if (matchCount >= MAX_SAME_URL_CONTENT_FRAMES) {
+ NS_WARNING("Too many nested content frames have the same url (recursion?) so giving up");
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+ }
+ nsCOMPtr<nsIDocShellTreeItem> temp;
+ temp.swap(parentAsItem);
+ temp->GetSameTypeParent(getter_AddRefs(parentAsItem));
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFrameLoader::GetWindowDimensions(nsIntRect& aRect)
+{
+ // Need to get outer window position here
+ nsIDocument* 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;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::UpdatePositionAndSize(nsSubDocumentFrame *aIFrame)
+{
+ if (IsRemoteFrame()) {
+ if (mRemoteBrowser) {
+ ScreenIntSize size = aIFrame->GetSubdocumentSize();
+ 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::UpdateBaseWindowPositionAndSize(nsSubDocumentFrame *aIFrame)
+{
+ nsCOMPtr<nsIDocShell> docShell;
+ GetDocShell(getter_AddRefs(docShell));
+ nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(docShell));
+
+ // resize the sub document
+ if (baseWindow) {
+ int32_t x = 0;
+ int32_t y = 0;
+
+ nsWeakFrame 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);
+ }
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetLazyWidth(uint32_t* aLazyWidth)
+{
+ *aLazyWidth = mLazySize.width;
+
+ nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+ if (frame) {
+ *aLazyWidth = frame->PresContext()->DevPixelsToIntCSSPixels(*aLazyWidth);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetLazyHeight(uint32_t* aLazyHeight)
+{
+ *aLazyHeight = mLazySize.height;
+
+ nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+ if (frame) {
+ *aLazyHeight = frame->PresContext()->DevPixelsToIntCSSPixels(*aLazyHeight);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetEventMode(uint32_t* aEventMode)
+{
+ *aEventMode = mEventMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::SetEventMode(uint32_t aEventMode)
+{
+ mEventMode = aEventMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetClipSubdocument(bool* aResult)
+{
+ *aResult = mClipSubdocument;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::SetClipSubdocument(bool aClip)
+{
+ mClipSubdocument = aClip;
+ nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+ if (frame) {
+ frame->InvalidateFrame();
+ frame->PresContext()->PresShell()->
+ FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+ nsSubDocumentFrame* subdocFrame = do_QueryFrame(frame);
+ if (subdocFrame) {
+ nsIFrame* subdocRootFrame = subdocFrame->GetSubdocumentRootFrame();
+ if (subdocRootFrame) {
+ nsIFrame* subdocRootScrollFrame = subdocRootFrame->PresContext()->PresShell()->
+ GetRootScrollFrame();
+ if (subdocRootScrollFrame) {
+ frame->PresContext()->PresShell()->
+ FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetClampScrollPosition(bool* aResult)
+{
+ *aResult = mClampScrollPosition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::SetClampScrollPosition(bool aClamp)
+{
+ mClampScrollPosition = aClamp;
+
+ // When turning clamping on, make sure the current position is clamped.
+ if (aClamp) {
+ nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+ nsSubDocumentFrame* subdocFrame = do_QueryFrame(frame);
+ if (subdocFrame) {
+ nsIFrame* subdocRootFrame = subdocFrame->GetSubdocumentRootFrame();
+ if (subdocRootFrame) {
+ nsIScrollableFrame* subdocRootScrollFrame = subdocRootFrame->PresContext()->PresShell()->
+ GetRootScrollFrameAsScrollable();
+ if (subdocRootScrollFrame) {
+ subdocRootScrollFrame->ScrollTo(subdocRootScrollFrame->GetScrollPosition(), nsIScrollableFrame::INSTANT);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+static
+ContentParent*
+GetContentParent(Element* aBrowser)
+{
+ nsCOMPtr<nsIBrowser> browser = do_QueryInterface(aBrowser);
+ if (!browser) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDOMElement> related;
+ browser->GetRelatedBrowser(getter_AddRefs(related));
+
+ nsCOMPtr<nsIFrameLoaderOwner> otherOwner = do_QueryInterface(related);
+ if (!otherOwner) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFrameLoader> otherLoader = otherOwner->GetFrameLoader();
+ TabParent* tabParent = TabParent::GetFrom(otherLoader);
+ if (tabParent &&
+ tabParent->Manager() &&
+ tabParent->Manager()->IsContentParent()) {
+ return tabParent->Manager()->AsContentParent();
+ }
+
+ return nullptr;
+}
+
+bool
+nsFrameLoader::TryRemoteBrowser()
+{
+ NS_ASSERTION(!mRemoteBrowser, "TryRemoteBrowser called with a remote browser already?");
+
+ //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
+ nsIDocument* doc = mOwnerContent->GetComposedDoc();
+ if (!doc) {
+ return false;
+ }
+
+ MOZ_RELEASE_ASSERT(!doc->IsResourceDoc(), "We shouldn't even exist");
+
+ 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;
+ }
+
+ TabParent* openingTab = TabParent::GetFrom(parentDocShell->GetOpener());
+ ContentParent* openerContentParent = nullptr;
+
+ if (openingTab &&
+ openingTab->Manager() &&
+ openingTab->Manager()->IsContentParent()) {
+ openerContentParent = openingTab->Manager()->AsContentParent();
+ }
+
+ // <iframe mozbrowser> gets to skip these checks.
+ if (!OwnerIsMozBrowserOrAppFrame()) {
+ if (parentDocShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ return false;
+ }
+
+ if (!mOwnerContent->IsXULElement()) {
+ return false;
+ }
+
+ nsAutoString value;
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
+
+ if (!value.LowerCaseEqualsLiteral("content") &&
+ !StringBeginsWith(value, NS_LITERAL_STRING("content-"),
+ nsCaseInsensitiveStringComparator())) {
+ return false;
+ }
+
+ // Try to get the related content parent from our browser element.
+ openerContentParent = GetContentParent(mOwnerContent);
+ }
+
+ uint32_t chromeFlags = 0;
+ nsCOMPtr<nsIDocShellTreeOwner> parentOwner;
+ if (NS_FAILED(parentDocShell->GetTreeOwner(getter_AddRefs(parentOwner))) ||
+ !parentOwner) {
+ return false;
+ }
+ nsCOMPtr<nsIXULWindow> window(do_GetInterface(parentOwner));
+ if (window && NS_FAILED(window->GetChromeFlags(&chromeFlags))) {
+ return false;
+ }
+
+ PROFILER_LABEL("nsFrameLoader", "CreateRemoteBrowser",
+ js::ProfileEntry::Category::OTHER);
+
+ MutableTabContext context;
+ nsresult rv = GetNewTabContext(&context);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<Element> ownerElement = mOwnerContent;
+ mRemoteBrowser = ContentParent::CreateBrowserOrApp(context, ownerElement,
+ openerContentParent, mFreshProcess);
+ if (!mRemoteBrowser) {
+ return false;
+ }
+
+ MaybeUpdatePrimaryTabParent(eTabParentChanged);
+
+ mChildID = mRemoteBrowser->Manager()->ChildID();
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ parentDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
+ nsCOMPtr<nsIDOMChromeWindow> rootChromeWin = do_QueryInterface(rootWin);
+
+ if (rootChromeWin) {
+ nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin;
+ rootChromeWin->GetBrowserDOMWindow(getter_AddRefs(browserDOMWin));
+ mRemoteBrowser->SetBrowserDOMWindow(browserDOMWin);
+ }
+
+ ReallyLoadFrameScripts();
+ InitializeBrowserAPI();
+
+ return true;
+}
+
+mozilla::dom::PBrowserParent*
+nsFrameLoader::GetRemoteBrowser() const
+{
+ return mRemoteBrowser;
+}
+
+RenderFrameParent*
+nsFrameLoader::GetCurrentRenderFrame() const
+{
+ if (mRemoteBrowser) {
+ return mRemoteBrowser->GetRenderFrame();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::ActivateRemoteFrame() {
+ if (mRemoteBrowser) {
+ mRemoteBrowser->Activate();
+ return NS_OK;
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::DeactivateRemoteFrame() {
+ if (mRemoteBrowser) {
+ mRemoteBrowser->Deactivate();
+ return NS_OK;
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::SendCrossProcessMouseEvent(const nsAString& aType,
+ float aX,
+ float aY,
+ int32_t aButton,
+ int32_t aClickCount,
+ int32_t aModifiers,
+ bool aIgnoreRootScrollFrame)
+{
+ if (mRemoteBrowser) {
+ mRemoteBrowser->SendMouseEvent(aType, aX, aY, aButton,
+ aClickCount, aModifiers,
+ aIgnoreRootScrollFrame);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::ActivateFrameEvent(const nsAString& aType,
+ bool aCapture)
+{
+ if (mRemoteBrowser) {
+ return mRemoteBrowser->SendActivateFrameEvent(nsString(aType), aCapture) ?
+ NS_OK : NS_ERROR_NOT_AVAILABLE;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::SendCrossProcessKeyEvent(const nsAString& aType,
+ int32_t aKeyCode,
+ int32_t aCharCode,
+ int32_t aModifiers,
+ bool aPreventDefault)
+{
+ if (mRemoteBrowser) {
+ mRemoteBrowser->SendKeyEvent(aType, aKeyCode, aCharCode, aModifiers,
+ aPreventDefault);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+nsFrameLoader::CreateStaticClone(nsIFrameLoader* aDest)
+{
+ nsFrameLoader* dest = static_cast<nsFrameLoader*>(aDest);
+ dest->MaybeCreateDocShell();
+ NS_ENSURE_STATE(dest->mDocShell);
+
+ nsCOMPtr<nsIDocument> kungFuDeathGrip = dest->mDocShell->GetDocument();
+ Unused << kungFuDeathGrip;
+
+ nsCOMPtr<nsIContentViewer> viewer;
+ dest->mDocShell->GetContentViewer(getter_AddRefs(viewer));
+ NS_ENSURE_STATE(viewer);
+
+ nsCOMPtr<nsIDocShell> origDocShell;
+ GetDocShell(getter_AddRefs(origDocShell));
+ NS_ENSURE_STATE(origDocShell);
+
+ nsCOMPtr<nsIDocument> doc = origDocShell->GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ nsCOMPtr<nsIDocument> clonedDoc = doc->CreateStaticClone(dest->mDocShell);
+ nsCOMPtr<nsIDOMDocument> clonedDOMDoc = do_QueryInterface(clonedDoc);
+
+ viewer->SetDOMDocument(clonedDOMDoc);
+ return NS_OK;
+}
+
+bool
+nsFrameLoader::DoLoadMessageManagerScript(const nsAString& aURL, bool aRunInGlobalScope)
+{
+ auto* tabParent = TabParent::GetFrom(GetRemoteBrowser());
+ if (tabParent) {
+ return tabParent->SendLoadRemoteScript(nsString(aURL), aRunInGlobalScope);
+ }
+ RefPtr<nsInProcessTabChildGlobal> tabChild =
+ static_cast<nsInProcessTabChildGlobal*>(GetTabChildGlobalAsEventTarget());
+ if (tabChild) {
+ tabChild->LoadFrameScript(aURL, aRunInGlobalScope);
+ }
+ return true;
+}
+
+class nsAsyncMessageToChild : public nsSameProcessAsyncMessageBase,
+ public Runnable
+{
+public:
+ nsAsyncMessageToChild(JS::RootingContext* aRootingCx,
+ JS::Handle<JSObject*> aCpows,
+ nsFrameLoader* aFrameLoader)
+ : nsSameProcessAsyncMessageBase(aRootingCx, aCpows)
+ , mFrameLoader(aFrameLoader)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsInProcessTabChildGlobal* tabChild =
+ static_cast<nsInProcessTabChildGlobal*>(mFrameLoader->mChildMessageManager.get());
+ // Since bug 1126089, messages can arrive even when the docShell is destroyed.
+ // Here we make sure that those messages are not delivered.
+ if (tabChild && tabChild->GetInnerManager() && mFrameLoader->GetExistingDocShell()) {
+ nsCOMPtr<nsIXPConnectJSObjectHolder> kungFuDeathGrip(tabChild->GetGlobal());
+ ReceiveMessage(static_cast<EventTarget*>(tabChild), mFrameLoader,
+ tabChild->GetInnerManager());
+ }
+ return NS_OK;
+ }
+ RefPtr<nsFrameLoader> mFrameLoader;
+};
+
+nsresult
+nsFrameLoader::DoSendAsyncMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal)
+{
+ TabParent* tabParent = mRemoteBrowser;
+ if (tabParent) {
+ ClonedMessageData data;
+ nsIContentParent* cp = tabParent->Manager();
+ if (!BuildClonedMessageDataForParent(cp, aData, data)) {
+ MOZ_CRASH();
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+ InfallibleTArray<mozilla::jsipc::CpowEntry> cpows;
+ jsipc::CPOWManager* mgr = cp->GetCPOWManager();
+ if (aCpows && (!mgr || !mgr->Wrap(aCx, aCpows, &cpows))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (tabParent->SendAsyncMessage(nsString(aMessage), cpows,
+ IPC::Principal(aPrincipal), data)) {
+ return NS_OK;
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (mChildMessageManager) {
+ JS::RootingContext* rcx = JS::RootingContext::get(aCx);
+ RefPtr<nsAsyncMessageToChild> ev = new nsAsyncMessageToChild(rcx, aCpows, this);
+ nsresult rv = ev->Init(aMessage, aData, aPrincipal);
+ 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;
+}
+
+bool
+nsFrameLoader::CheckPermission(const nsAString& aPermission)
+{
+ return AssertAppProcessPermission(GetRemoteBrowser(),
+ NS_ConvertUTF16toUTF8(aPermission).get());
+}
+
+bool
+nsFrameLoader::CheckManifestURL(const nsAString& aManifestURL)
+{
+ return AssertAppProcessManifestURL(GetRemoteBrowser(),
+ NS_ConvertUTF16toUTF8(aManifestURL).get());
+}
+
+bool
+nsFrameLoader::CheckAppHasPermission(const nsAString& aPermission)
+{
+ return AssertAppHasPermission(GetRemoteBrowser(),
+ NS_ConvertUTF16toUTF8(aPermission).get());
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetMessageManager(nsIMessageSender** aManager)
+{
+ EnsureMessageManager();
+ if (mMessageManager) {
+ RefPtr<nsFrameMessageManager> mm(mMessageManager);
+ mm.forget(aManager);
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsFrameLoader::EnsureMessageManager()
+{
+ NS_ENSURE_STATE(mOwnerContent);
+
+ if (mMessageManager) {
+ return NS_OK;
+ }
+
+ if (!mIsTopLevelContent &&
+ !OwnerIsMozBrowserOrAppFrame() &&
+ !IsRemoteFrame() &&
+ !(mOwnerContent->IsXULElement() &&
+ mOwnerContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::forcemessagemanager,
+ nsGkAtoms::_true, eCaseMatters))) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMChromeWindow> chromeWindow =
+ do_QueryInterface(GetOwnerDoc()->GetWindow());
+ nsCOMPtr<nsIMessageBroadcaster> parentManager;
+
+ if (chromeWindow) {
+ nsAutoString messagemanagergroup;
+ if (mOwnerContent->IsXULElement() &&
+ mOwnerContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::messagemanagergroup,
+ messagemanagergroup)) {
+ chromeWindow->GetGroupMessageManager(messagemanagergroup, getter_AddRefs(parentManager));
+ }
+
+ if (!parentManager) {
+ chromeWindow->GetMessageManager(getter_AddRefs(parentManager));
+ }
+ } else {
+ parentManager = do_GetService("@mozilla.org/globalmessagemanager;1");
+ }
+
+ mMessageManager = new nsFrameMessageManager(nullptr,
+ static_cast<nsFrameMessageManager*>(parentManager.get()),
+ MM_CHROME);
+ if (!IsRemoteFrame()) {
+ nsresult rv = MaybeCreateDocShell();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ NS_ASSERTION(mDocShell,
+ "MaybeCreateDocShell succeeded, but null mDocShell");
+ if (!mDocShell) {
+ return NS_ERROR_FAILURE;
+ }
+ mChildMessageManager =
+ new nsInProcessTabChildGlobal(mDocShell, mOwnerContent, mMessageManager);
+ }
+ 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;
+}
+
+EventTarget*
+nsFrameLoader::GetTabChildGlobalAsEventTarget()
+{
+ return static_cast<nsInProcessTabChildGlobal*>(mChildMessageManager.get());
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetOwnerElement(nsIDOMElement **aElement)
+{
+ nsCOMPtr<nsIDOMElement> ownerElement = do_QueryInterface(mOwnerContent);
+ ownerElement.forget(aElement);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetChildID(uint64_t* aChildID)
+{
+ *aChildID = mChildID;
+ return NS_OK;
+}
+
+void
+nsFrameLoader::SetRemoteBrowser(nsITabParent* aTabParent)
+{
+ MOZ_ASSERT(!mRemoteBrowser);
+ mRemoteFrame = true;
+ mRemoteBrowser = TabParent::GetFrom(aTabParent);
+ mChildID = mRemoteBrowser ? mRemoteBrowser->Manager()->ChildID() : 0;
+ MaybeUpdatePrimaryTabParent(eTabParentChanged);
+ ReallyLoadFrameScripts();
+ InitializeBrowserAPI();
+ ShowRemoteFrame(ScreenIntSize(0, 0));
+}
+
+void
+nsFrameLoader::SetDetachedSubdocFrame(nsIFrame* aDetachedFrame,
+ nsIDocument* aContainerDoc)
+{
+ mDetachedSubdocFrame = aDetachedFrame;
+ mContainerDocWhileDetached = aContainerDoc;
+}
+
+nsIFrame*
+nsFrameLoader::GetDetachedSubdocFrame(nsIDocument** aContainerDoc) const
+{
+ NS_IF_ADDREF(*aContainerDoc = mContainerDocWhileDetached);
+ return mDetachedSubdocFrame.GetFrame();
+}
+
+void
+nsFrameLoader::ApplySandboxFlags(uint32_t sandboxFlags)
+{
+ if (mDocShell) {
+ uint32_t parentSandboxFlags = mOwnerContent->OwnerDoc()->GetSandboxFlags();
+
+ // The child can only add restrictions, never remove them.
+ sandboxFlags |= parentSandboxFlags;
+
+ // If this frame is a receiving browsing context, we should add
+ // sandboxed auxiliary navigation flag to sandboxFlags. See
+ // https://w3c.github.io/presentation-api/#creating-a-receiving-browsing-context
+ nsAutoString presentationURL;
+ nsContentUtils::GetPresentationURL(mDocShell, presentationURL);
+ if (!presentationURL.IsEmpty()) {
+ sandboxFlags |= SANDBOXED_AUXILIARY_NAVIGATION;
+ }
+ mDocShell->SetSandboxFlags(sandboxFlags);
+ }
+}
+
+/* virtual */ void
+nsFrameLoader::AttributeChanged(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ MOZ_ASSERT(mObservingOwnerContent);
+
+ if (aNameSpaceID != kNameSpaceID_None || aAttribute != TypeAttrName()) {
+ return;
+ }
+
+ if (aElement != mOwnerContent) {
+ 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 (!mDocShell) {
+ MaybeUpdatePrimaryTabParent(eTabParentChanged);
+ return;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ mDocShell->GetParent(getter_AddRefs(parentItem));
+ if (!parentItem) {
+ return;
+ }
+
+ if (parentItem->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
+ parentItem->GetTreeOwner(getter_AddRefs(parentTreeOwner));
+ if (!parentTreeOwner) {
+ return;
+ }
+
+ nsAutoString value;
+ aElement->GetAttr(kNameSpaceID_None, TypeAttrName(), value);
+
+ bool is_primary = value.LowerCaseEqualsLiteral("content-primary");
+
+#ifdef MOZ_XUL
+ // 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(mDocShell);
+ }
+#endif
+
+ parentTreeOwner->ContentShellRemoved(mDocShell);
+ if (value.LowerCaseEqualsLiteral("content") ||
+ StringBeginsWith(value, NS_LITERAL_STRING("content-"),
+ nsCaseInsensitiveStringComparator())) {
+ bool is_targetable = is_primary ||
+ value.LowerCaseEqualsLiteral("content-targetable");
+
+ parentTreeOwner->ContentShellAdded(mDocShell, is_primary,
+ is_targetable, value);
+ }
+}
+
+/**
+ * Send the RequestNotifyAfterRemotePaint message to the current Tab.
+ */
+NS_IMETHODIMP
+nsFrameLoader::RequestNotifyAfterRemotePaint()
+{
+ // If remote browsing (e10s), handle this with the TabParent.
+ if (mRemoteBrowser) {
+ Unused << mRemoteBrowser->SendRequestNotifyAfterRemotePaint();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::RequestFrameLoaderClose()
+{
+ nsCOMPtr<nsIBrowser> browser = do_QueryInterface(mOwnerContent);
+ if (NS_WARN_IF(!browser)) {
+ // OwnerElement other than nsIBrowser is not supported yet.
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return browser->CloseBrowser();
+}
+
+NS_IMETHODIMP
+nsFrameLoader::Print(uint64_t aOuterWindowID,
+ nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aProgressListener)
+{
+#if defined(NS_PRINTING)
+ if (mRemoteBrowser) {
+ RefPtr<embedding::PrintingParent> printingParent =
+ mRemoteBrowser->Manager()->AsContentParent()->GetPrintingParent();
+
+ embedding::PrintData printData;
+ nsresult rv = printingParent->SerializeAndEnsureRemotePrintJob(
+ aPrintSettings, aProgressListener, nullptr, &printData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool success = mRemoteBrowser->SendPrint(aOuterWindowID, printData);
+ return success ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ nsGlobalWindow* outerWindow =
+ nsGlobalWindow::GetOuterWindowWithId(aOuterWindowID);
+ if (NS_WARN_IF(!outerWindow)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint =
+ do_GetInterface(outerWindow->AsOuter());
+ if (NS_WARN_IF(!webBrowserPrint)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return webBrowserPrint->Print(aPrintSettings, aProgressListener);
+#endif
+ return NS_OK;
+}
+
+/* [infallible] */ NS_IMETHODIMP
+nsFrameLoader::SetVisible(bool aVisible)
+{
+ if (mVisible == aVisible) {
+ return NS_OK;
+ }
+
+ mVisible = aVisible;
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
+ "frameloader-visible-changed", nullptr);
+ }
+ return NS_OK;
+}
+
+/* [infallible] */ NS_IMETHODIMP
+nsFrameLoader::GetVisible(bool* aVisible)
+{
+ *aVisible = mVisible;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetTabParent(nsITabParent** aTabParent)
+{
+ nsCOMPtr<nsITabParent> tp = mRemoteBrowser;
+ tp.forget(aTabParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetLoadContext(nsILoadContext** aLoadContext)
+{
+ nsCOMPtr<nsILoadContext> loadContext;
+ if (mRemoteBrowser) {
+ loadContext = mRemoteBrowser->GetLoadContext();
+ } else {
+ nsCOMPtr<nsIDocShell> docShell;
+ GetDocShell(getter_AddRefs(docShell));
+ loadContext = do_GetInterface(docShell);
+ }
+ loadContext.forget(aLoadContext);
+ return NS_OK;
+}
+
+void
+nsFrameLoader::InitializeBrowserAPI()
+{
+ if (!OwnerIsMozBrowserOrAppFrame()) {
+ return;
+ }
+ if (!IsRemoteFrame()) {
+ nsresult rv = EnsureMessageManager();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ if (mMessageManager) {
+ mMessageManager->LoadFrameScript(
+ NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js"),
+ /* allowDelayedLoad = */ true,
+ /* aRunInGlobalScope */ true);
+ }
+ }
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ if (browserFrame) {
+ browserFrame->InitializeBrowserAPI();
+ }
+}
+
+void
+nsFrameLoader::DestroyBrowserFrameScripts()
+{
+ if (!OwnerIsMozBrowserOrAppFrame()) {
+ return;
+ }
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+ if (browserFrame) {
+ browserFrame->DestroyBrowserFrameScripts();
+ }
+}
+
+NS_IMETHODIMP
+nsFrameLoader::StartPersistence(uint64_t aOuterWindowID,
+ nsIWebBrowserPersistDocumentReceiver* aRecv)
+{
+ if (!aRecv) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (mRemoteBrowser) {
+ return mRemoteBrowser->StartPersistence(aOuterWindowID, aRecv);
+ }
+
+ nsCOMPtr<nsIDocument> rootDoc =
+ mDocShell ? mDocShell->GetDocument() : nullptr;
+ nsCOMPtr<nsIDocument> foundDoc;
+ if (aOuterWindowID) {
+ foundDoc = nsContentUtils::GetSubdocumentWithOuterWindowId(rootDoc, aOuterWindowID);
+ } else {
+ foundDoc = rootDoc;
+ }
+
+ if (!foundDoc) {
+ aRecv->OnError(NS_ERROR_NO_CONTENT);
+ } else {
+ nsCOMPtr<nsIWebBrowserPersistDocument> pdoc =
+ new mozilla::WebBrowserPersistLocalDocument(foundDoc);
+ aRecv->OnDocumentReady(pdoc);
+ }
+ return NS_OK;
+}
+
+void
+nsFrameLoader::MaybeUpdatePrimaryTabParent(TabParentChange aChange)
+{
+ if (mRemoteBrowser && mOwnerContent) {
+ nsCOMPtr<nsIDocShell> docShell = mOwnerContent->OwnerDoc()->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ int32_t parentType = docShell->ItemType();
+ if (parentType != nsIDocShellTreeItem::typeChrome) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
+ docShell->GetTreeOwner(getter_AddRefs(parentTreeOwner));
+ if (!parentTreeOwner) {
+ return;
+ }
+
+ if (!mObservingOwnerContent) {
+ mOwnerContent->AddMutationObserver(this);
+ mObservingOwnerContent = true;
+ }
+
+ parentTreeOwner->TabParentRemoved(mRemoteBrowser);
+ if (aChange == eTabParentChanged) {
+ bool isPrimary =
+ mOwnerContent->AttrValueIs(kNameSpaceID_None,
+ TypeAttrName(),
+ NS_LITERAL_STRING("content-primary"),
+ eIgnoreCase);
+ parentTreeOwner->TabParentAdded(mRemoteBrowser, isPrimary);
+ }
+ }
+}
+
+nsresult
+nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext,
+ nsIURI* aURI)
+{
+ nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+ nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
+ DocShellOriginAttributes attrs;
+ attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
+ nsresult rv;
+
+ // Get the AppId from ownApp
+ uint32_t appId = nsIScriptSecurityManager::NO_APP_ID;
+ if (ownApp) {
+ rv = ownApp->GetLocalId(&appId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(appId != nsIScriptSecurityManager::NO_APP_ID);
+ } else if (containingApp) {
+ rv = containingApp->GetLocalId(&appId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(appId != nsIScriptSecurityManager::NO_APP_ID);
+ }
+ attrs.mAppId = appId;
+
+ // set the userContextId on the attrs before we pass them into
+ // the tab context
+ rv = PopulateUserContextIdFromAttribute(attrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString presentationURLStr;
+ mOwnerContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::mozpresentation,
+ presentationURLStr);
+
+ nsCOMPtr<nsIDocShell> docShell = mOwnerContent->OwnerDoc()->GetDocShell();
+ nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(docShell);
+ NS_ENSURE_STATE(parentContext);
+
+ bool isPrivate = parentContext->UsePrivateBrowsing();
+ attrs.SyncAttributesWithPrivateBrowsing(isPrivate);
+
+ UIStateChangeType showAccelerators = UIStateChangeType_NoChange;
+ UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
+ nsIDocument* doc = mOwnerContent->OwnerDoc();
+ if (doc) {
+ nsCOMPtr<nsPIWindowRoot> root = nsContentUtils::GetWindowRoot(doc);
+ if (root) {
+ showAccelerators =
+ root->ShowAccelerators() ? UIStateChangeType_Set : UIStateChangeType_Clear;
+ showFocusRings =
+ root->ShowFocusRings() ? UIStateChangeType_Set : UIStateChangeType_Clear;
+ }
+ }
+
+ bool tabContextUpdated =
+ aTabContext->SetTabContext(OwnerIsMozBrowserFrame(),
+ mIsPrerendered,
+ ownApp,
+ containingApp,
+ showAccelerators,
+ showFocusRings,
+ attrs,
+ presentationURLStr);
+ NS_ENSURE_STATE(tabContextUpdated);
+
+ return NS_OK;
+}
+
+nsresult
+nsFrameLoader::PopulateUserContextIdFromAttribute(DocShellOriginAttributes& aAttr)
+{
+ if (aAttr.mUserContextId ==
+ nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
+ // Grab the userContextId from owner if XUL
+ nsAutoString userContextIdStr;
+ int32_t namespaceID = mOwnerContent->GetNameSpaceID();
+ if ((namespaceID == kNameSpaceID_XUL) &&
+ mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid,
+ userContextIdStr) &&
+ !userContextIdStr.IsEmpty()) {
+ nsresult rv;
+ aAttr.mUserContextId = userContextIdStr.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsIMessageSender*
+nsFrameLoader::GetProcessMessageManager() const
+{
+ return mRemoteBrowser ? mRemoteBrowser->Manager()->GetMessageManager()
+ : nullptr;
+};
diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h
new file mode 100644
index 000000000..bbbd52f58
--- /dev/null
+++ b/dom/base/nsFrameLoader.h
@@ -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/. */
+
+/*
+ * 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 "nsIDocShell.h"
+#include "nsStringFwd.h"
+#include "nsIFrameLoader.h"
+#include "nsPoint.h"
+#include "nsSize.h"
+#include "nsIURI.h"
+#include "nsFrameMessageManager.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Attributes.h"
+#include "nsStubMutationObserver.h"
+#include "Units.h"
+#include "nsIWebBrowserPersistable.h"
+#include "nsIFrame.h"
+#include "nsIGroupedSHistory.h"
+
+class nsIURI;
+class nsSubDocumentFrame;
+class nsView;
+class nsIInProcessContentFrameMessageManager;
+class AutoResetInShow;
+class AutoResetInFrameSwap;
+class nsITabParent;
+class nsIDocShellTreeItem;
+class nsIDocShellTreeOwner;
+class mozIApplication;
+
+namespace mozilla {
+
+class DocShellOriginAttributes;
+
+namespace dom {
+class ContentParent;
+class PBrowserParent;
+class TabParent;
+class MutableTabContext;
+} // namespace dom
+
+namespace ipc {
+class StructuredCloneData;
+} // namespace ipc
+
+namespace layout {
+class RenderFrameParent;
+} // namespace layout
+} // namespace mozilla
+
+#if defined(MOZ_WIDGET_GTK)
+typedef struct _GtkWidget GtkWidget;
+#endif
+
+class nsFrameLoader final : public nsIFrameLoader,
+ public nsIWebBrowserPersistable,
+ public nsStubMutationObserver,
+ public mozilla::dom::ipc::MessageManagerCallback
+{
+ friend class AutoResetInShow;
+ friend class AutoResetInFrameSwap;
+ typedef mozilla::dom::PBrowserParent PBrowserParent;
+ typedef mozilla::dom::TabParent TabParent;
+ typedef mozilla::layout::RenderFrameParent RenderFrameParent;
+
+public:
+ static nsFrameLoader* Create(mozilla::dom::Element* aOwner,
+ nsPIDOMWindowOuter* aOpener,
+ bool aNetworkCreated);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFrameLoader, nsIFrameLoader)
+ NS_DECL_NSIFRAMELOADER
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIWEBBROWSERPERSISTABLE
+ nsresult CheckForRecursiveLoad(nsIURI* aURI);
+ nsresult ReallyStartLoading();
+ void StartDestroy();
+ void DestroyDocShell();
+ void DestroyComplete();
+ nsIDocShell* GetExistingDocShell() { return mDocShell; }
+ mozilla::dom::EventTarget* GetTabChildGlobalAsEventTarget();
+ nsresult CreateStaticClone(nsIFrameLoader* aDest);
+
+ /**
+ * MessageManagerCallback methods that we override.
+ */
+ virtual bool DoLoadMessageManagerScript(const nsAString& aURL,
+ bool aRunInGlobalScope) override;
+ virtual nsresult DoSendAsyncMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ mozilla::dom::ipc::StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal) override;
+ virtual bool CheckPermission(const nsAString& aPermission) override;
+ virtual bool CheckManifestURL(const nsAString& aManifestURL) override;
+ virtual bool CheckAppHasPermission(const nsAString& aPermission) override;
+
+ /**
+ * Called from the layout frame associated with this frame loader;
+ * this notifies us to hook up with the widget and view.
+ */
+ bool Show(int32_t marginWidth, int32_t marginHeight,
+ int32_t scrollbarPrefX, int32_t scrollbarPrefY,
+ nsSubDocumentFrame* frame);
+
+ /**
+ * Called when the margin properties of the containing frame are changed.
+ */
+ void MarginsChanged(uint32_t aMarginWidth, uint32_t aMarginHeight);
+
+ /**
+ * 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();
+
+ nsresult CloneForStatic(nsIFrameLoader* aOriginal);
+
+ // The guts of an nsIFrameLoaderOwner::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,
+ nsIFrameLoaderOwner* aThisOwner,
+ nsIFrameLoaderOwner* aOtherOwner);
+
+ nsresult SwapWithOtherRemoteLoader(nsFrameLoader* aOther,
+ nsIFrameLoaderOwner* aThisOwner,
+ nsIFrameLoaderOwner* aOtherOwner);
+
+ /**
+ * Return the primary frame for our owning content, or null if it
+ * can't be found.
+ */
+ nsIFrame* GetPrimaryFrameOfOwningContent() const
+ {
+ return mOwnerContent ? mOwnerContent->GetPrimaryFrame() : nullptr;
+ }
+
+ /**
+ * Return the document that owns this, or null if we don't have
+ * an owner.
+ */
+ nsIDocument* GetOwnerDoc() const
+ { return mOwnerContent ? mOwnerContent->OwnerDoc() : nullptr; }
+
+ PBrowserParent* GetRemoteBrowser() const;
+
+ /**
+ * The "current" render frame is the one on which the most recent
+ * remote layer-tree transaction was executed. If no content has
+ * been drawn yet, or the remote browser doesn't have any drawn
+ * content for whatever reason, return nullptr. The returned render
+ * frame has an associated shadow layer tree.
+ *
+ * Note that the returned render frame might not be a frame
+ * constructed for this->GetURL(). This can happen, e.g., if the
+ * <browser> was just navigated to a new URL, but hasn't painted the
+ * new page yet. A render frame for the previous page may be
+ * returned. (In-process <browser> behaves similarly, and this
+ * behavior seems desirable.)
+ */
+ RenderFrameParent* GetCurrentRenderFrame() const;
+
+ nsFrameMessageManager* GetFrameMessageManager() { return mMessageManager; }
+
+ mozilla::dom::Element* GetOwnerContent() { return mOwnerContent; }
+ bool ShouldClipSubdocument() { return mClipSubdocument; }
+
+ bool ShouldClampScrollPosition() { return mClampScrollPosition; }
+
+ /**
+ * Tell this FrameLoader to use a particular remote browser.
+ *
+ * This will assert if mRemoteBrowser is non-null. In practice,
+ * this means you can't have successfully run TryRemoteBrowser() on
+ * this object, which means you can't have called ShowRemoteFrame()
+ * or ReallyStartLoading().
+ */
+ void SetRemoteBrowser(nsITabParent* aTabParent);
+
+ /**
+ * 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,
+ nsIDocument* aContainerDoc);
+
+ /**
+ * Retrieves the detached nsIFrame and the document containing the nsIFrame,
+ * as set by SetDetachedSubdocFrame().
+ */
+ nsIFrame* GetDetachedSubdocFrame(nsIDocument** 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);
+
+ // Properly retrieves documentSize of any subdocument type.
+ nsresult GetWindowDimensions(nsIntRect& aRect);
+
+ virtual nsIMessageSender* GetProcessMessageManager() const override;
+
+ // public because a callback needs these.
+ RefPtr<nsFrameMessageManager> mMessageManager;
+ nsCOMPtr<nsIInProcessContentFrameMessageManager> mChildMessageManager;
+
+private:
+ nsFrameLoader(mozilla::dom::Element* aOwner,
+ nsPIDOMWindowOuter* aOpener,
+ bool aNetworkCreated);
+ ~nsFrameLoader();
+
+ void SetOwnerContent(mozilla::dom::Element* aContent);
+
+ bool ShouldUseRemoteProcess();
+
+ /**
+ * Return true if the frame is a remote frame. Return false otherwise
+ */
+ bool IsRemoteFrame();
+
+ /**
+ * Is this a frameloader for a bona fide <iframe mozbrowser> or
+ * <iframe mozapp>? (I.e., does the frame return true for
+ * nsIMozBrowserFrame::GetReallyIsBrowserOrApp()?)
+ * <xul:browser> is not a mozbrowser or app, so this is false for that case.
+ */
+ bool OwnerIsMozBrowserOrAppFrame();
+
+ /**
+ * Is this a frameloader for a bona fide <iframe mozapp>? (I.e., does the
+ * frame return true for nsIMozBrowserFrame::GetReallyIsApp()?)
+ */
+ bool OwnerIsAppFrame();
+
+ /**
+ * 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();
+
+ /**
+ * Is this a frame loader for an isolated <iframe mozbrowser>?
+ *
+ * By default, mozbrowser frames are isolated. Isolation can be disabled by
+ * setting the frame's noisolation attribute. Disabling isolation is
+ * only allowed if the containing document is chrome.
+ */
+ bool OwnerIsIsolatedMozBrowserFrame();
+
+ /**
+ * 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);
+
+ /**
+ * Get the app for our frame. This is the app whose manifest is returned by
+ * GetOwnerAppManifestURL.
+ */
+ already_AddRefed<mozIApplication> GetOwnApp();
+
+ /**
+ * Get the app which contains this frame. This is the app associated with
+ * the frame element's principal.
+ */
+ already_AddRefed<mozIApplication> GetContainingApp();
+
+ /**
+ * If we are an IPC frame, set mRemoteFrame. Otherwise, create and
+ * initialize mDocShell.
+ */
+ nsresult MaybeCreateDocShell();
+ nsresult EnsureMessageManager();
+ nsresult ReallyLoadFrameScripts();
+
+ // Updates the subdocument position and size. This gets called only
+ // when we have our own in-process DocShell.
+ void UpdateBaseWindowPositionAndSize(nsSubDocumentFrame *aIFrame);
+ nsresult CheckURILoad(nsIURI* aURI);
+ void FireErrorEvent();
+ nsresult ReallyStartLoadingInternal();
+
+ // Return true if remote browser created; nothing else to do
+ bool TryRemoteBrowser();
+
+ // Tell the remote browser that it's now "virtually visible"
+ bool ShowRemoteFrame(const mozilla::ScreenIntSize& size,
+ nsSubDocumentFrame *aFrame = nullptr);
+
+ bool AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem,
+ nsIDocShellTreeOwner* aOwner,
+ int32_t aParentType,
+ nsIDocShell* aParentNode);
+
+ nsIAtom* TypeAttrName() const {
+ return mOwnerContent->IsXULElement()
+ ? nsGkAtoms::type : nsGkAtoms::mozframetype;
+ }
+
+ void InitializeBrowserAPI();
+ void DestroyBrowserFrameScripts();
+
+ nsresult GetNewTabContext(mozilla::dom::MutableTabContext* aTabContext,
+ nsIURI* aURI = nullptr);
+
+ enum TabParentChange {
+ eTabParentRemoved,
+ eTabParentChanged
+ };
+ void MaybeUpdatePrimaryTabParent(TabParentChange aChange);
+
+ nsresult
+ PopulateUserContextIdFromAttribute(mozilla::DocShellOriginAttributes& aAttr);
+
+ nsCOMPtr<nsIDocShell> mDocShell;
+ nsCOMPtr<nsIURI> mURIToLoad;
+ 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.
+ nsWeakFrame 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.
+ nsCOMPtr<nsIDocument> mContainerDocWhileDetached;
+
+ // An opener window which should be used when the docshell is created.
+ nsCOMPtr<nsPIDOMWindowOuter> mOpener;
+
+ TabParent* mRemoteBrowser;
+ uint64_t mChildID;
+
+ // See nsIFrameLoader.idl. EVENT_MODE_NORMAL_DISPATCH automatically
+ // forwards some input events to out-of-process content.
+ uint32_t mEventMode;
+
+ // Holds the last known size of the frame.
+ mozilla::ScreenIntSize mLazySize;
+
+ nsCOMPtr<nsIPartialSHistory> mPartialSessionHistory;
+ nsCOMPtr<nsIGroupedSHistory> mGroupedSessionHistory;
+
+ bool mIsPrerendered : 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;
+
+ bool mRemoteBrowserShown : 1;
+ bool mRemoteFrame : 1;
+ bool mClipSubdocument : 1;
+ bool mClampScrollPosition : 1;
+ bool mObservingOwnerContent : 1;
+
+ // Backs nsIFrameLoader::{Get,Set}Visible. Visibility state here relates to
+ // whether this frameloader's <iframe mozbrowser> is setVisible(true)'ed, and
+ // doesn't necessarily correlate with docshell/document visibility.
+ bool mVisible : 1;
+ bool mFreshProcess : 1;
+};
+
+#endif
diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp
new file mode 100644
index 000000000..a4bba4856
--- /dev/null
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -0,0 +1,2281 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "base/basictypes.h"
+
+#include "nsFrameMessageManager.h"
+
+#include "AppProcessChecker.h"
+#include "ContentChild.h"
+#include "nsContentUtils.h"
+#include "nsDOMClassInfoID.h"
+#include "nsError.h"
+#include "nsIXPConnect.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsJSUtils.h"
+#include "nsJSPrincipals.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "nsFrameLoader.h"
+#include "nsIXULRuntime.h"
+#include "nsIScriptError.h"
+#include "nsIConsoleService.h"
+#include "nsIMemoryReporter.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIDOMClassInfo.h"
+#include "xpcpublic.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/IntentionalCrash.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/nsIContentParent.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/ProcessGlobal.h"
+#include "mozilla/dom/SameProcessMessageQueue.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+#include "nsQueryObject.h"
+#include <algorithm>
+#include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+#ifdef ANDROID
+#include <android/log.h>
+#endif
+#ifdef XP_WIN
+#include <windows.h>
+# if defined(SendMessage)
+# undef SendMessage
+# endif
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+
+nsFrameMessageManager::nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback,
+ nsFrameMessageManager* aParentManager,
+ /* mozilla::dom::ipc::MessageManagerFlags */ uint32_t aFlags)
+ : mChrome(!!(aFlags & mozilla::dom::ipc::MM_CHROME)),
+ mGlobal(!!(aFlags & mozilla::dom::ipc::MM_GLOBAL)),
+ mIsProcessManager(!!(aFlags & mozilla::dom::ipc::MM_PROCESSMANAGER)),
+ mIsBroadcaster(!!(aFlags & mozilla::dom::ipc::MM_BROADCASTER)),
+ mOwnsCallback(!!(aFlags & mozilla::dom::ipc::MM_OWNSCALLBACK)),
+ mHandlingMessage(false),
+ mClosed(false),
+ mDisconnected(false),
+ mCallback(aCallback),
+ mParentManager(aParentManager)
+{
+ NS_ASSERTION(mChrome || !aParentManager, "Should not set parent manager!");
+ NS_ASSERTION(!mIsBroadcaster || !mCallback,
+ "Broadcasters cannot have callbacks!");
+ if (mIsProcessManager && (!mChrome || IsBroadcaster())) {
+ mozilla::HoldJSObjects(this);
+ }
+ // This is a bit hackish. When parent manager is global, we want
+ // to attach the message manager to it immediately.
+ // Is it just the frame message manager which waits until the
+ // content process is running.
+ if (mParentManager && (mCallback || IsBroadcaster())) {
+ mParentManager->AddChildManager(this);
+ }
+ if (mOwnsCallback) {
+ mOwnedCallback = aCallback;
+ }
+}
+
+nsFrameMessageManager::~nsFrameMessageManager()
+{
+ if (mIsProcessManager && (!mChrome || IsBroadcaster())) {
+ mozilla::DropJSObjects(this);
+ }
+ for (int32_t i = mChildManagers.Count(); i > 0; --i) {
+ static_cast<nsFrameMessageManager*>(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;
+ }
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager)
+ for (auto iter = tmp->mListeners.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
+ uint32_t count = listeners->Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "listeners[i] mStrongListener");
+ cb.NoteXPCOMChild(listeners->ElementAt(i).mStrongListener.get());
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+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)
+ tmp->mListeners.Clear();
+ for (int32_t i = tmp->mChildManagers.Count(); i > 0; --i) {
+ static_cast<nsFrameMessageManager*>(tmp->mChildManagers[i - 1])->
+ Disconnect(false);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildManagers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentManager)
+ tmp->mInitialProcessData.setNull();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameMessageManager)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentFrameMessageManager)
+
+ /* nsFrameMessageManager implements nsIMessageSender and nsIMessageBroadcaster,
+ * both of which descend from nsIMessageListenerManager. QI'ing to
+ * nsIMessageListenerManager is therefore ambiguous and needs explicit casts
+ * depending on which child interface applies. */
+ NS_INTERFACE_MAP_ENTRY_AGGREGATED(nsIMessageListenerManager,
+ (mIsBroadcaster ?
+ static_cast<nsIMessageListenerManager*>(
+ static_cast<nsIMessageBroadcaster*>(this)) :
+ static_cast<nsIMessageListenerManager*>(
+ static_cast<nsIMessageSender*>(this))))
+
+ /* Message managers in child process implement nsIMessageSender and
+ nsISyncMessageSender. 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(nsISyncMessageSender, !mChrome)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageSender, !mChrome || !mIsBroadcaster)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageBroadcaster, mChrome && mIsBroadcaster)
+
+ /* nsIContentFrameMessageManager is accessible only in TabChildGlobal. */
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIContentFrameMessageManager,
+ !mChrome && !mIsProcessManager)
+
+ /* Frame message managers (non-process message managers) support nsIFrameScriptLoader. */
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFrameScriptLoader,
+ mChrome && !mIsProcessManager)
+
+ /* Process message managers (process message managers) support nsIProcessScriptLoader. */
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIProcessScriptLoader,
+ mChrome && mIsProcessManager)
+
+ /* Global process message managers (process message managers) support nsIGlobalProcessScriptLoader. */
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIGlobalProcessScriptLoader,
+ mChrome && mIsProcessManager && mIsBroadcaster)
+
+ /* Message senders in the chrome process support nsIProcessChecker. */
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIProcessChecker,
+ mChrome && !mIsBroadcaster)
+
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_CONDITIONAL(ChromeMessageBroadcaster,
+ mChrome && mIsBroadcaster)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO_CONDITIONAL(ChromeMessageSender,
+ mChrome && !mIsBroadcaster)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameMessageManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameMessageManager)
+
+enum ActorFlavorEnum {
+ Parent = 0,
+ Child
+};
+
+template <ActorFlavorEnum>
+struct BlobTraits
+{ };
+
+template <>
+struct BlobTraits<Parent>
+{
+ typedef mozilla::dom::BlobParent BlobType;
+ typedef mozilla::dom::PBlobParent ProtocolType;
+ typedef mozilla::dom::nsIContentParent ConcreteContentManagerType;
+};
+
+template <>
+struct BlobTraits<Child>
+{
+ typedef mozilla::dom::BlobChild BlobType;
+ typedef mozilla::dom::PBlobChild ProtocolType;
+ typedef mozilla::dom::nsIContentChild ConcreteContentManagerType;
+};
+
+template<ActorFlavorEnum>
+struct DataBlobs
+{ };
+
+template<>
+struct DataBlobs<Parent>
+{
+ typedef BlobTraits<Parent>::ProtocolType ProtocolType;
+
+ static InfallibleTArray<ProtocolType*>& Blobs(ClonedMessageData& aData)
+ {
+ return aData.blobsParent();
+ }
+
+ static const InfallibleTArray<ProtocolType*>& Blobs(const ClonedMessageData& aData)
+ {
+ return aData.blobsParent();
+ }
+};
+
+template<>
+struct DataBlobs<Child>
+{
+ typedef BlobTraits<Child>::ProtocolType ProtocolType;
+
+ static InfallibleTArray<ProtocolType*>& Blobs(ClonedMessageData& aData)
+ {
+ return aData.blobsChild();
+ }
+
+ static const InfallibleTArray<ProtocolType*>& Blobs(const ClonedMessageData& aData)
+ {
+ return aData.blobsChild();
+ }
+};
+
+template<ActorFlavorEnum Flavor>
+static bool
+BuildClonedMessageData(typename BlobTraits<Flavor>::ConcreteContentManagerType* aManager,
+ StructuredCloneData& aData,
+ ClonedMessageData& aClonedData)
+{
+ SerializedStructuredCloneBuffer& buffer = aClonedData.data();
+ auto iter = aData.Data().Iter();
+ size_t size = aData.Data().Size();
+ bool success;
+ buffer.data = aData.Data().Borrow<js::SystemAllocPolicy>(iter, size, &success);
+ if (NS_WARN_IF(!success)) {
+ return false;
+ }
+ aClonedData.identfiers().AppendElements(aData.PortIdentifiers());
+
+ const nsTArray<RefPtr<BlobImpl>>& blobImpls = aData.BlobImpls();
+
+ if (!blobImpls.IsEmpty()) {
+ typedef typename BlobTraits<Flavor>::ProtocolType ProtocolType;
+ InfallibleTArray<ProtocolType*>& blobList = DataBlobs<Flavor>::Blobs(aClonedData);
+ uint32_t length = blobImpls.Length();
+ blobList.SetCapacity(length);
+ for (uint32_t i = 0; i < length; ++i) {
+ typename BlobTraits<Flavor>::BlobType* protocolActor =
+ aManager->GetOrCreateActorForBlobImpl(blobImpls[i]);
+ if (!protocolActor) {
+ return false;
+ }
+ blobList.AppendElement(protocolActor);
+ }
+ }
+ return true;
+}
+
+bool
+MessageManagerCallback::BuildClonedMessageDataForParent(nsIContentParent* aParent,
+ StructuredCloneData& aData,
+ ClonedMessageData& aClonedData)
+{
+ return BuildClonedMessageData<Parent>(aParent, aData, aClonedData);
+}
+
+bool
+MessageManagerCallback::BuildClonedMessageDataForChild(nsIContentChild* aChild,
+ StructuredCloneData& aData,
+ ClonedMessageData& aClonedData)
+{
+ return BuildClonedMessageData<Child>(aChild, aData, aClonedData);
+}
+
+template<ActorFlavorEnum Flavor>
+static void
+UnpackClonedMessageData(const ClonedMessageData& aClonedData,
+ StructuredCloneData& aData)
+{
+ const SerializedStructuredCloneBuffer& buffer = aClonedData.data();
+ typedef typename BlobTraits<Flavor>::ProtocolType ProtocolType;
+ const InfallibleTArray<ProtocolType*>& blobs = DataBlobs<Flavor>::Blobs(aClonedData);
+ const InfallibleTArray<MessagePortIdentifier>& identifiers = aClonedData.identfiers();
+
+ aData.UseExternalData(buffer.data);
+
+ aData.PortIdentifiers().AppendElements(identifiers);
+
+ if (!blobs.IsEmpty()) {
+ uint32_t length = blobs.Length();
+ aData.BlobImpls().SetCapacity(length);
+ for (uint32_t i = 0; i < length; ++i) {
+ auto* blob =
+ static_cast<typename BlobTraits<Flavor>::BlobType*>(blobs[i]);
+ MOZ_ASSERT(blob);
+
+ RefPtr<BlobImpl> blobImpl = blob->GetBlobImpl();
+ MOZ_ASSERT(blobImpl);
+
+ aData.BlobImpls().AppendElement(blobImpl);
+ }
+ }
+}
+
+void
+mozilla::dom::ipc::UnpackClonedMessageDataForParent(const ClonedMessageData& aClonedData,
+ StructuredCloneData& aData)
+{
+ UnpackClonedMessageData<Parent>(aClonedData, aData);
+}
+
+void
+mozilla::dom::ipc::UnpackClonedMessageDataForChild(const ClonedMessageData& aClonedData,
+ StructuredCloneData& aData)
+{
+ UnpackClonedMessageData<Child>(aClonedData, aData);
+}
+
+bool
+SameProcessCpowHolder::ToObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aObjp)
+{
+ if (!mObj) {
+ return true;
+ }
+
+ aObjp.set(mObj);
+ return JS_WrapObject(aCx, aObjp);
+}
+
+// nsIMessageListenerManager
+
+NS_IMETHODIMP
+nsFrameMessageManager::AddMessageListener(const nsAString& aMessage,
+ nsIMessageListener* aListener,
+ bool aListenWhenClosed)
+{
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessage);
+ if (!listeners) {
+ listeners = new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
+ mListeners.Put(aMessage, listeners);
+ } else {
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ if (listeners->ElementAt(i).mStrongListener == aListener) {
+ return NS_OK;
+ }
+ }
+ }
+
+ nsMessageListenerInfo* entry = listeners->AppendElement();
+ NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
+ entry->mStrongListener = aListener;
+ entry->mListenWhenClosed = aListenWhenClosed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage,
+ nsIMessageListener* aListener)
+{
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessage);
+ if (!listeners) {
+ return NS_OK;
+ }
+
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ if (listeners->ElementAt(i).mStrongListener == aListener) {
+ listeners->RemoveElementAt(i);
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::AddWeakMessageListener(const nsAString& aMessage,
+ nsIMessageListener* aListener)
+{
+ nsWeakPtr weak = do_GetWeakReference(aListener);
+ NS_ENSURE_TRUE(weak, NS_ERROR_NO_INTERFACE);
+
+#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(aListener);
+ for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
+ 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
+
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessage);
+ if (!listeners) {
+ listeners = new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
+ mListeners.Put(aMessage, listeners);
+ } else {
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ if (listeners->ElementAt(i).mWeakListener == weak) {
+ return NS_OK;
+ }
+ }
+ }
+
+ nsMessageListenerInfo* entry = listeners->AppendElement();
+ entry->mWeakListener = weak;
+ entry->mListenWhenClosed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage,
+ nsIMessageListener* aListener)
+{
+ nsWeakPtr weak = do_GetWeakReference(aListener);
+ NS_ENSURE_TRUE(weak, NS_OK);
+
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ mListeners.Get(aMessage);
+ if (!listeners) {
+ return NS_OK;
+ }
+
+ uint32_t len = listeners->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ if (listeners->ElementAt(i).mWeakListener == weak) {
+ listeners->RemoveElementAt(i);
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsIFrameScriptLoader
+
+NS_IMETHODIMP
+nsFrameMessageManager::LoadScript(const nsAString& aURL,
+ bool aAllowDelayedLoad,
+ bool aRunInGlobalScope)
+{
+ 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
+ NS_ENSURE_TRUE(mCallback->DoLoadMessageManagerScript(aURL, aRunInGlobalScope),
+ NS_ERROR_FAILURE);
+ }
+
+ for (int32_t i = 0; i < mChildManagers.Count(); ++i) {
+ RefPtr<nsFrameMessageManager> mm =
+ static_cast<nsFrameMessageManager*>(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);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+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;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetDelayedScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList)
+{
+ // 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");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, mPendingScripts.Length()));
+ NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::Rooted<JSString*> url(aCx);
+ JS::Rooted<JSObject*> pair(aCx);
+ for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
+ url = JS_NewUCStringCopyN(aCx, mPendingScripts[i].get(), mPendingScripts[i].Length());
+ NS_ENSURE_TRUE(url, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::AutoValueArray<2> pairElts(aCx);
+ pairElts[0].setString(url);
+ pairElts[1].setBoolean(mPendingScriptsGlobalStates[i]);
+
+ pair = JS_NewArrayObject(aCx, pairElts);
+ NS_ENSURE_TRUE(pair, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ENSURE_TRUE(JS_DefineElement(aCx, array, i, pair, JSPROP_ENUMERATE),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ aList.setObject(*array);
+ return NS_OK;
+}
+
+// nsIFrameScriptLoader
+
+NS_IMETHODIMP
+nsFrameMessageManager::LoadFrameScript(const nsAString& aURL,
+ bool aAllowDelayedLoad,
+ bool aRunInGlobalScope)
+{
+ return LoadScript(aURL, aAllowDelayedLoad, aRunInGlobalScope);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::RemoveDelayedFrameScript(const nsAString& aURL)
+{
+ return RemoveDelayedScript(aURL);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetDelayedFrameScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList)
+{
+ return GetDelayedScripts(aCx, aList);
+}
+
+// nsIProcessScriptLoader
+
+NS_IMETHODIMP
+nsFrameMessageManager::LoadProcessScript(const nsAString& aURL,
+ bool aAllowDelayedLoad)
+{
+ return LoadScript(aURL, aAllowDelayedLoad, false);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::RemoveDelayedProcessScript(const nsAString& aURL)
+{
+ return RemoveDelayedScript(aURL);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetDelayedProcessScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList)
+{
+ return GetDelayedScripts(aCx, aList);
+}
+
+static bool
+JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData)
+{
+ nsAString* result = static_cast<nsAString*>(aData);
+ result->Append(static_cast<const char16_t*>(aBuf),
+ static_cast<uint32_t>(aLen));
+ return true;
+}
+
+static bool
+GetParamsForMessage(JSContext* aCx,
+ const JS::Value& aValue,
+ const JS::Value& aTransfer,
+ StructuredCloneData& aData)
+{
+ // First try to use structured clone on the whole thing.
+ JS::RootedValue v(aCx, aValue);
+ JS::RootedValue t(aCx, aTransfer);
+ ErrorResult rv;
+ aData.Write(aCx, v, t, 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(NS_LITERAL_STRING("Sending message that cannot be cloned. Are you trying to send an XPCOM object?"),
+ filename, EmptyString(), lineno, column,
+ nsIScriptError::warningFlag, "chrome javascript");
+ console->LogMessage(error);
+ }
+
+ // Not clonable, try JSON
+ //XXX 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(JS_Stringify(aCx, &v, nullptr, JS::NullHandleValue,
+ JSONCreator, &json), 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;
+}
+
+// nsISyncMessageSender
+
+static bool sSendingSyncMessage = false;
+
+NS_IMETHODIMP
+nsFrameMessageManager::SendSyncMessage(const nsAString& aMessageName,
+ JS::Handle<JS::Value> aJSON,
+ JS::Handle<JS::Value> aObjects,
+ nsIPrincipal* aPrincipal,
+ JSContext* aCx,
+ uint8_t aArgc,
+ JS::MutableHandle<JS::Value> aRetval)
+{
+ return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc,
+ aRetval, true);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::SendRpcMessage(const nsAString& aMessageName,
+ JS::Handle<JS::Value> aJSON,
+ JS::Handle<JS::Value> aObjects,
+ nsIPrincipal* aPrincipal,
+ JSContext* aCx,
+ uint8_t aArgc,
+ JS::MutableHandle<JS::Value> aRetval)
+{
+ return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc,
+ aRetval, false);
+}
+
+static bool
+AllowMessage(size_t aDataLength, const nsAString& aMessageName)
+{
+ static const size_t kMinTelemetryMessageSize = 8192;
+
+ if (aDataLength < kMinTelemetryMessageSize) {
+ return true;
+ }
+
+ NS_ConvertUTF16toUTF8 messageName(aMessageName);
+ messageName.StripChars("0123456789");
+
+ Telemetry::Accumulate(Telemetry::MESSAGE_MANAGER_MESSAGE_SIZE2, messageName,
+ aDataLength);
+
+ // A message includes more than structured clone data, so subtract
+ // 20KB to make it more likely that a message within this bound won't
+ // result in an overly large IPC message.
+ static const size_t kMaxMessageSize = IPC::Channel::kMaximumMessageSize - 20 * 1024;
+ if (aDataLength < kMaxMessageSize) {
+ return true;
+ }
+
+ Telemetry::Accumulate(Telemetry::REJECTED_MESSAGE_MANAGER_MESSAGE,
+ messageName);
+
+ return false;
+}
+
+nsresult
+nsFrameMessageManager::SendMessage(const nsAString& aMessageName,
+ JS::Handle<JS::Value> aJSON,
+ JS::Handle<JS::Value> aObjects,
+ nsIPrincipal* aPrincipal,
+ JSContext* aCx,
+ uint8_t aArgc,
+ JS::MutableHandle<JS::Value> aRetval,
+ bool aIsSync)
+{
+ NS_ASSERTION(!IsGlobal(), "Should not call SendSyncMessage in chrome");
+ NS_ASSERTION(!IsBroadcaster(), "Should not call SendSyncMessage in chrome");
+ NS_ASSERTION(!mParentManager, "Should not have parent manager in content!");
+
+ aRetval.setUndefined();
+ NS_ENSURE_TRUE(mCallback, NS_ERROR_NOT_INITIALIZED);
+
+ if (sSendingSyncMessage && aIsSync) {
+ // No kind of blocking send should be issued on top of a sync message.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ StructuredCloneData data;
+ if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, JS::UndefinedHandleValue, data)) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ if (!AllowMessage(data.DataLength(), aMessageName)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> objects(aCx);
+ if (aArgc >= 3 && aObjects.isObject()) {
+ objects = &aObjects.toObject();
+ }
+
+ nsTArray<StructuredCloneData> retval;
+
+ sSendingSyncMessage |= aIsSync;
+ bool ok = mCallback->DoSendBlockingMessage(aCx, aMessageName, data, objects,
+ aPrincipal, &retval, aIsSync);
+ if (aIsSync) {
+ sSendingSyncMessage = false;
+ }
+
+ if (!ok) {
+ return NS_OK;
+ }
+
+ uint32_t len = retval.Length();
+ JS::Rooted<JSObject*> dataArray(aCx, JS_NewArrayObject(aCx, len));
+ NS_ENSURE_TRUE(dataArray, NS_ERROR_OUT_OF_MEMORY);
+
+ for (uint32_t i = 0; i < len; ++i) {
+ JS::Rooted<JS::Value> ret(aCx);
+ ErrorResult rv;
+ retval[i].Read(aCx, &ret, rv);
+ if (rv.Failed()) {
+ MOZ_ASSERT(false, "Unable to read structured clone in SendMessage");
+ rv.SuppressException();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_ENSURE_TRUE(JS_DefineElement(aCx, dataArray, i, ret, JSPROP_ENUMERATE),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ aRetval.setObject(*dataArray);
+ return NS_OK;
+}
+
+nsresult
+nsFrameMessageManager::DispatchAsyncMessageInternal(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal)
+{
+ if (mIsBroadcaster) {
+ int32_t len = mChildManagers.Count();
+ for (int32_t i = 0; i < len; ++i) {
+ static_cast<nsFrameMessageManager*>(mChildManagers[i])->
+ DispatchAsyncMessageInternal(aCx, aMessage, aData, aCpows, aPrincipal);
+ }
+ return NS_OK;
+ }
+
+ if (!mCallback) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = mCallback->DoSendAsyncMessage(aCx, aMessage, aData, aCpows, aPrincipal);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsFrameMessageManager::DispatchAsyncMessage(const nsAString& aMessageName,
+ const JS::Value& aJSON,
+ const JS::Value& aObjects,
+ nsIPrincipal* aPrincipal,
+ const JS::Value& aTransfers,
+ JSContext* aCx,
+ uint8_t aArgc)
+{
+ StructuredCloneData data;
+ if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, aTransfers, data)) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ if (!AllowMessage(data.DataLength(), aMessageName)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> objects(aCx);
+ if (aArgc >= 3 && aObjects.isObject()) {
+ objects = &aObjects.toObject();
+ }
+
+ return DispatchAsyncMessageInternal(aCx, aMessageName, data, objects,
+ aPrincipal);
+}
+
+// nsIMessageSender
+
+NS_IMETHODIMP
+nsFrameMessageManager::SendAsyncMessage(const nsAString& aMessageName,
+ JS::Handle<JS::Value> aJSON,
+ JS::Handle<JS::Value> aObjects,
+ nsIPrincipal* aPrincipal,
+ JS::Handle<JS::Value> aTransfers,
+ JSContext* aCx,
+ uint8_t aArgc)
+{
+ return DispatchAsyncMessage(aMessageName, aJSON, aObjects, aPrincipal,
+ aTransfers, aCx, aArgc);
+}
+
+
+// nsIMessageBroadcaster
+
+NS_IMETHODIMP
+nsFrameMessageManager::BroadcastAsyncMessage(const nsAString& aMessageName,
+ JS::Handle<JS::Value> aJSON,
+ JS::Handle<JS::Value> aObjects,
+ JSContext* aCx,
+ uint8_t aArgc)
+{
+ return DispatchAsyncMessage(aMessageName, aJSON, aObjects, nullptr,
+ JS::UndefinedHandleValue, aCx, aArgc);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetChildCount(uint32_t* aChildCount)
+{
+ *aChildCount = static_cast<uint32_t>(mChildManagers.Count());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetChildAt(uint32_t aIndex,
+ nsIMessageListenerManager** aMM)
+{
+ *aMM = nullptr;
+ nsCOMPtr<nsIMessageListenerManager> mm =
+ do_QueryInterface(mChildManagers.SafeObjectAt(static_cast<uint32_t>(aIndex)));
+ mm.swap(*aMM);
+ return NS_OK;
+}
+
+
+// nsIContentFrameMessageManager
+
+NS_IMETHODIMP
+nsFrameMessageManager::Dump(const nsAString& aStr)
+{
+#ifdef ANDROID
+ __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", NS_ConvertUTF16toUTF8(aStr).get());
+#endif
+#ifdef XP_WIN
+ if (IsDebuggerPresent()) {
+ OutputDebugStringW(PromiseFlatString(aStr).get());
+ }
+#endif
+ fputs(NS_ConvertUTF16toUTF8(aStr).get(), stdout);
+ fflush(stdout);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::PrivateNoteIntentionalCrash()
+{
+ if (XRE_IsContentProcess()) {
+ mozilla::NoteIntentionalCrash("tab");
+ return NS_OK;
+ } else {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetContent(mozIDOMWindowProxy** aContent)
+{
+ *aContent = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetDocShell(nsIDocShell** aDocShell)
+{
+ *aDocShell = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::Btoa(const nsAString& aBinaryData,
+ nsAString& aAsciiBase64String)
+{
+ return nsContentUtils::Btoa(aBinaryData, aAsciiBase64String);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::Atob(const nsAString& aAsciiString,
+ nsAString& aBinaryData)
+{
+ return nsContentUtils::Atob(aAsciiString, aBinaryData);
+}
+
+// nsIProcessChecker
+
+NS_IMETHODIMP
+nsFrameMessageManager::KillChild(bool *aValid)
+{
+ if (!mCallback) {
+ *aValid = false;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aValid = mCallback->KillChild();
+ return NS_OK;
+}
+
+nsresult
+nsFrameMessageManager::AssertProcessInternal(ProcessCheckerType aType,
+ const nsAString& aCapability,
+ bool* aValid)
+{
+ *aValid = false;
+
+ // This API is only supported for message senders in the chrome process.
+ if (!mChrome || mIsBroadcaster) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ if (!mCallback) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ switch (aType) {
+ case PROCESS_CHECKER_PERMISSION:
+ *aValid = mCallback->CheckPermission(aCapability);
+ break;
+ case PROCESS_CHECKER_MANIFEST_URL:
+ *aValid = mCallback->CheckManifestURL(aCapability);
+ break;
+ case ASSERT_APP_HAS_PERMISSION:
+ *aValid = mCallback->CheckAppHasPermission(aCapability);
+ break;
+ default:
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::AssertPermission(const nsAString& aPermission,
+ bool* aHasPermission)
+{
+ return AssertProcessInternal(PROCESS_CHECKER_PERMISSION,
+ aPermission,
+ aHasPermission);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::AssertContainApp(const nsAString& aManifestURL,
+ bool* aHasManifestURL)
+{
+ return AssertProcessInternal(PROCESS_CHECKER_MANIFEST_URL,
+ aManifestURL,
+ aHasManifestURL);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::AssertAppHasPermission(const nsAString& aPermission,
+ bool* aHasPermission)
+{
+ return AssertProcessInternal(ASSERT_APP_HAS_PERMISSION,
+ aPermission,
+ aHasPermission);
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::AssertAppHasStatus(unsigned short aStatus,
+ bool* aHasStatus)
+{
+ *aHasStatus = false;
+
+ // This API is only supported for message senders in the chrome process.
+ if (!mChrome || mIsBroadcaster) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ if (!mCallback) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aHasStatus = mCallback->CheckAppHasStatus(aStatus);
+
+ return NS_OK;
+}
+
+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;
+};
+
+
+// nsIMessageListener
+
+nsresult
+nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
+ nsIFrameLoader* aTargetFrameLoader,
+ const nsAString& aMessage,
+ bool aIsSync,
+ StructuredCloneData* aCloneData,
+ mozilla::jsipc::CpowHolder* aCpows,
+ nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal)
+{
+ return ReceiveMessage(aTarget, aTargetFrameLoader, mClosed, aMessage, aIsSync,
+ aCloneData, aCpows, aPrincipal, aRetVal);
+}
+
+nsresult
+nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
+ nsIFrameLoader* aTargetFrameLoader,
+ bool aTargetClosed,
+ const nsAString& aMessage,
+ bool aIsSync,
+ StructuredCloneData* aCloneData,
+ mozilla::jsipc::CpowHolder* aCpows,
+ nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal)
+{
+ 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) {
+ listeners->RemoveElement(listener);
+ continue;
+ }
+ }
+
+ if (!listener.mListenWhenClosed && aTargetClosed) {
+ continue;
+ }
+
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS;
+ if (weakListener) {
+ wrappedJS = do_QueryInterface(weakListener);
+ } else {
+ wrappedJS = do_QueryInterface(listener.mStrongListener);
+ }
+
+ if (!wrappedJS) {
+ continue;
+ }
+
+ if (!wrappedJS->GetJSObject()) {
+ continue;
+ }
+
+ AutoEntryScript aes(wrappedJS->GetJSObject(), "message manager handler");
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> object(cx, wrappedJS->GetJSObject());
+
+ // The parameter for the listener function.
+ JS::Rooted<JSObject*> param(cx, JS_NewPlainObject(cx));
+ NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::Rooted<JS::Value> targetv(cx);
+ js::AssertSameCompartment(cx, object);
+ nsresult rv = nsContentUtils::WrapNative(cx, aTarget, &targetv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSObject*> cpows(cx);
+ if (aCpows) {
+ if (!aCpows->ToObject(cx, &cpows)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!cpows) {
+ cpows = JS_NewPlainObject(cx);
+ if (!cpows) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ JS::Rooted<JS::Value> cpowsv(cx, JS::ObjectValue(*cpows));
+
+ JS::Rooted<JS::Value> json(cx, JS::NullValue());
+ if (aCloneData && aCloneData->DataLength()) {
+ ErrorResult rv;
+ aCloneData->Read(cx, &json, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+ }
+
+ // Get cloned MessagePort from StructuredCloneData.
+ nsTArray<RefPtr<mozilla::dom::MessagePort>> ports;
+ if (aCloneData) {
+ ports = aCloneData->TakeTransferredPorts();
+ }
+
+ JS::Rooted<JS::Value> transferredList(cx);
+ if (NS_WARN_IF(!ToJSValue(cx, ports, &transferredList))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ JS::Rooted<JSString*> jsMessage(cx,
+ JS_NewUCStringCopyN(cx,
+ static_cast<const char16_t*>(aMessage.BeginReading()),
+ aMessage.Length()));
+ NS_ENSURE_TRUE(jsMessage, NS_ERROR_OUT_OF_MEMORY);
+ JS::Rooted<JS::Value> syncv(cx, JS::BooleanValue(aIsSync));
+ bool ok = JS_DefineProperty(cx, param, "target", targetv, JSPROP_ENUMERATE) &&
+ JS_DefineProperty(cx, param, "name", jsMessage, JSPROP_ENUMERATE) &&
+ JS_DefineProperty(cx, param, "sync", syncv, JSPROP_ENUMERATE) &&
+ JS_DefineProperty(cx, param, "json", json, JSPROP_ENUMERATE) && // deprecated
+ JS_DefineProperty(cx, param, "data", json, JSPROP_ENUMERATE) &&
+ JS_DefineProperty(cx, param, "objects", cpowsv, JSPROP_ENUMERATE) &&
+ JS_DefineProperty(cx, param, "ports", transferredList, JSPROP_ENUMERATE);
+
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+
+ if (aTargetFrameLoader) {
+ JS::Rooted<JS::Value> targetFrameLoaderv(cx);
+ nsresult rv = nsContentUtils::WrapNative(cx, aTargetFrameLoader, &targetFrameLoaderv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ok = JS_DefineProperty(cx, param, "targetFrameLoader", targetFrameLoaderv,
+ JSPROP_ENUMERATE);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+ }
+
+ // message.principal == null
+ if (!aPrincipal) {
+ bool ok = JS_DefineProperty(cx, param, "principal",
+ JS::UndefinedHandleValue, JSPROP_ENUMERATE);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+ }
+
+ // message.principal = the principal
+ else {
+ JS::Rooted<JS::Value> principalValue(cx);
+ nsresult rv = nsContentUtils::WrapNative(cx, aPrincipal,
+ &NS_GET_IID(nsIPrincipal),
+ &principalValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool ok = JS_DefineProperty(cx, param, "principal", principalValue,
+ JSPROP_ENUMERATE);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+ }
+
+ JS::Rooted<JS::Value> thisValue(cx, JS::UndefinedValue());
+
+ JS::Rooted<JS::Value> funval(cx);
+ if (JS::IsCallable(object)) {
+ // If the listener is a JS function:
+ funval.setObject(*object);
+
+ // A small hack to get 'this' value right on content side where
+ // messageManager is wrapped in TabChildGlobal.
+ nsCOMPtr<nsISupports> defaultThisValue;
+ if (mChrome) {
+ defaultThisValue = do_QueryObject(this);
+ } else {
+ defaultThisValue = aTarget;
+ }
+ js::AssertSameCompartment(cx, object);
+ nsresult rv = nsContentUtils::WrapNative(cx, defaultThisValue, &thisValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // If the listener is a JS object which has receiveMessage function:
+ if (!JS_GetProperty(cx, object, "receiveMessage", &funval) ||
+ !funval.isObject()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Check if the object is even callable.
+ NS_ENSURE_STATE(JS::IsCallable(&funval.toObject()));
+ thisValue.setObject(*object);
+ }
+
+ JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());
+ JS::Rooted<JS::Value> argv(cx, JS::ObjectValue(*param));
+
+ {
+ JS::Rooted<JSObject*> thisObject(cx, thisValue.toObjectOrNull());
+
+ JSAutoCompartment tac(cx, thisObject);
+ if (!JS_WrapValue(cx, &argv)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!JS_CallFunctionValue(cx, thisObject, funval,
+ JS::HandleValueArray(argv), &rval)) {
+ continue;
+ }
+ if (aRetVal) {
+ ErrorResult rv;
+ StructuredCloneData* data = aRetVal->AppendElement();
+ data->Write(cx, rval, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ aRetVal->RemoveElementAt(aRetVal->Length() - 1);
+ nsString msg = aMessage + NS_LITERAL_STRING(": 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, EmptyString(), EmptyString(),
+ 0, 0, nsIScriptError::warningFlag, "chrome javascript");
+ console->LogMessage(error);
+ }
+
+ JS_ClearPendingException(cx);
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ RefPtr<nsFrameMessageManager> kungFuDeathGrip = mParentManager;
+ if (kungFuDeathGrip) {
+ return kungFuDeathGrip->ReceiveMessage(aTarget, aTargetFrameLoader,
+ aTargetClosed, aMessage,
+ aIsSync, aCloneData,
+ aCpows, aPrincipal,
+ aRetVal);
+ }
+ return NS_OK;
+}
+
+void
+nsFrameMessageManager::AddChildManager(nsFrameMessageManager* aManager)
+{
+ mChildManagers.AppendObject(aManager);
+
+ RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
+ RefPtr<nsFrameMessageManager> kungfuDeathGrip2 = aManager;
+
+ LoadPendingScripts(this, aManager);
+}
+
+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.
+ if (aManager->mParentManager) {
+ LoadPendingScripts(aManager->mParentManager, aChildMM);
+ }
+
+ for (uint32_t i = 0; i < aManager->mPendingScripts.Length(); ++i) {
+ aChildMM->LoadFrameScript(aManager->mPendingScripts[i],
+ false,
+ aManager->mPendingScriptsGlobalStates[i]);
+ }
+}
+
+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 = aCallback;
+ }
+ }
+}
+
+void
+nsFrameMessageManager::InitWithCallback(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) {
+ LoadFrameScript(mPendingScripts[i], false, mPendingScriptsGlobalStates[i]);
+ }
+}
+
+void
+nsFrameMessageManager::RemoveFromParent()
+{
+ if (mParentManager) {
+ mParentManager->RemoveChildManager(this);
+ }
+ mParentManager = nullptr;
+ mCallback = nullptr;
+ mOwnedCallback = nullptr;
+}
+
+void
+nsFrameMessageManager::Close()
+{
+ if (!mClosed) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, 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) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(NS_ISUPPORTS_CAST(nsIContentFrameMessageManager*, this),
+ "message-manager-disconnect", nullptr);
+ }
+ }
+ if (mParentManager && aRemoveFromParent) {
+ mParentManager->RemoveChildManager(this);
+ }
+ mDisconnected = true;
+ mParentManager = nullptr;
+ if (!mHandlingMessage) {
+ mListeners.Clear();
+ }
+}
+
+void
+nsFrameMessageManager::SetInitialProcessData(JS::HandleValue aInitialData)
+{
+ MOZ_ASSERT(!mChrome);
+ MOZ_ASSERT(mIsProcessManager);
+ mInitialProcessData = aInitialData;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetInitialProcessData(JSContext* aCx, JS::MutableHandleValue aResult)
+{
+ MOZ_ASSERT(mIsProcessManager);
+ MOZ_ASSERT_IF(mChrome, IsBroadcaster());
+
+ JS::RootedValue init(aCx, mInitialProcessData);
+ if (mChrome && init.isUndefined()) {
+ // We create the initial object in the junk scope. If we created it in a
+ // normal compartment, that compartment would leak until shutdown.
+ JS::RootedObject global(aCx, xpc::PrivilegedJunkScope());
+ JSAutoCompartment ac(aCx, global);
+
+ JS::RootedObject obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ 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.
+ nsCOMPtr<nsIGlobalProcessScriptLoader> ppmm =
+ do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+ ppmm->GetInitialProcessData(aCx, &init);
+ mInitialProcessData = init;
+ }
+
+ if (!JS_WrapValue(aCx, &init)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aResult.set(init);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameMessageManager::GetProcessMessageManager(nsIMessageSender** aPMM)
+{
+ *aPMM = nullptr;
+ if (mCallback) {
+ nsCOMPtr<nsIMessageSender> pmm = mCallback->GetProcessMessageManager();
+ pmm.swap(*aPMM);
+ }
+ return NS_OK;
+}
+
+namespace {
+
+struct MessageManagerReferentCount
+{
+ MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {}
+ size_t mStrong;
+ size_t mWeakAlive;
+ size_t mWeakDead;
+ nsTArray<nsString> mSuspectMessages;
+ nsDataHashtable<nsStringHashKey, uint32_t> mMessageCounter;
+};
+
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+class MessageManagerReporter final : public nsIMemoryReporter
+{
+ ~MessageManagerReporter() {}
+
+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 (auto it = aMessageManager->mListeners.Iter(); !it.Done(); it.Next()) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
+ it.UserData();
+ uint32_t listenerCount = listeners->Length();
+ if (listenerCount == 0) {
+ continue;
+ }
+
+ nsString key(it.Key());
+ uint32_t oldCount = 0;
+ aReferentCount->mMessageCounter.Get(key, &oldCount);
+ uint32_t currentCount = oldCount + listenerCount;
+ aReferentCount->mMessageCounter.Put(key, currentCount);
+
+ // 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 =
+ static_cast<nsFrameMessageManager*>(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(EmptyCString(), _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++) {
+ uint32_t totalReferentCount = 0;
+ aReferentCount.mMessageCounter.Get(aReferentCount.mSuspectMessages[i],
+ &totalReferentCount);
+ 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
+}
+
+NS_IMETHODIMP
+MessageManagerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIMessageBroadcaster> globalmm =
+ do_GetService("@mozilla.org/globalmessagemanager;1");
+ if (globalmm) {
+ RefPtr<nsFrameMessageManager> mm =
+ static_cast<nsFrameMessageManager*>(globalmm.get());
+ MessageManagerReferentCount count;
+ CountReferents(mm, &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 dom
+} // namespace mozilla
+
+nsresult
+NS_NewGlobalMessageManager(nsIMessageBroadcaster** aResult)
+{
+ NS_ENSURE_TRUE(XRE_IsParentProcess(),
+ NS_ERROR_NOT_AVAILABLE);
+ RefPtr<nsFrameMessageManager> mm = new nsFrameMessageManager(nullptr,
+ nullptr,
+ MM_CHROME | MM_GLOBAL | MM_BROADCASTER);
+ RegisterStrongMemoryReporter(new MessageManagerReporter());
+ mm.forget(aResult);
+ return NS_OK;
+}
+
+nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>*
+ nsMessageManagerScriptExecutor::sCachedScripts = nullptr;
+StaticRefPtr<nsScriptCacheCleaner> nsMessageManagerScriptExecutor::sScriptCacheCleaner;
+
+void
+nsMessageManagerScriptExecutor::DidCreateGlobal()
+{
+ NS_ASSERTION(mGlobal, "Should have mGlobal!");
+ if (!sCachedScripts) {
+ sCachedScripts =
+ new nsDataHashtable<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;
+ }
+}
+
+void
+nsMessageManagerScriptExecutor::LoadScriptInternal(const nsAString& aURL,
+ bool aRunInGlobalScope)
+{
+ if (!mGlobal || !sCachedScripts) {
+ return;
+ }
+
+ JS::RootingContext* rcx = RootingCx();
+ JS::Rooted<JSScript*> script(rcx);
+
+ nsMessageManagerScriptHolder* holder = sCachedScripts->Get(aURL);
+ if (holder && holder->WillRunInGlobalScope() == aRunInGlobalScope) {
+ script = holder->mScript;
+ } else {
+ // Don't put anything in the cache if we already have an entry
+ // with a different WillRunInGlobalScope() value.
+ bool shouldCache = !holder;
+ TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope,
+ shouldCache, &script);
+ }
+
+ JS::Rooted<JSObject*> global(rcx, mGlobal->GetJSObject());
+ if (global) {
+ AutoEntryScript aes(global, "message manager script load");
+ JSContext* cx = aes.cx();
+ if (script) {
+ if (aRunInGlobalScope) {
+ JS::RootedValue rval(cx);
+ JS::CloneAndExecuteScript(cx, script, &rval);
+ } else {
+ JS::Rooted<JSObject*> scope(cx);
+ bool ok = js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope);
+ if (ok) {
+ // Force the scope to stay alive.
+ mAnonymousGlobalScopes.AppendElement(scope);
+ }
+ }
+ }
+ }
+}
+
+void
+nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
+ const nsAString& aURL,
+ bool aRunInGlobalScope,
+ bool aShouldCache,
+ JS::MutableHandle<JSScript*> aScriptp)
+{
+ nsCString url = NS_ConvertUTF16toUTF8(aURL);
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ 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;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (!channel) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> input;
+ rv = channel->Open2(getter_AddRefs(input));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ nsString dataString;
+ char16_t* dataStringBuf = nullptr;
+ size_t dataStringLength = 0;
+ uint64_t avail64 = 0;
+ if (input && NS_SUCCEEDED(input->Available(&avail64)) && avail64) {
+ if (avail64 > UINT32_MAX) {
+ return;
+ }
+ nsCString buffer;
+ uint32_t avail = (uint32_t)std::min(avail64, (uint64_t)UINT32_MAX);
+ if (NS_FAILED(NS_ReadInputStreamToString(input, buffer, avail))) {
+ return;
+ }
+ nsScriptLoader::ConvertToUTF16(channel, (uint8_t*)buffer.get(), avail,
+ EmptyString(), nullptr,
+ dataStringBuf, dataStringLength);
+ }
+
+ JS::SourceBufferHolder srcBuf(dataStringBuf, dataStringLength,
+ JS::SourceBufferHolder::GiveOwnership);
+
+ if (dataStringBuf && dataStringLength > 0) {
+ // Compile the script in the compilation scope instead of the current global
+ // to avoid keeping the current compartment alive.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::CompileOptions options(cx, JSVERSION_LATEST);
+ options.setFileAndLine(url.get(), 1);
+ options.setNoScriptRval(true);
+ JS::Rooted<JSScript*> script(cx);
+
+ if (aRunInGlobalScope) {
+ if (!JS::Compile(cx, options, srcBuf, &script)) {
+ return;
+ }
+ // We're going to run these against some non-global scope.
+ } else if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
+ return;
+ }
+
+ MOZ_ASSERT(script);
+ aScriptp.set(script);
+
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ // We don't cache data: scripts!
+ if (aShouldCache && !scheme.EqualsLiteral("data")) {
+ // Root the object also for caching.
+ nsMessageManagerScriptHolder* holder =
+ new nsMessageManagerScriptHolder(cx, script, aRunInGlobalScope);
+ sCachedScripts->Put(aURL, holder);
+ }
+ }
+}
+
+void
+nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
+ const nsAString& aURL,
+ bool aRunInGlobalScope)
+{
+ JS::Rooted<JSScript*> script(RootingCx());
+ TryCacheLoadAndCompileScript(aURL, aRunInGlobalScope, true, &script);
+}
+
+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);
+ }
+}
+
+bool
+nsMessageManagerScriptExecutor::InitChildGlobalInternal(
+ nsISupports* aScope,
+ const nsACString& aID)
+{
+ AutoSafeJSContext cx;
+ nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(mPrincipal));
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ const uint32_t flags = nsIXPConnect::INIT_JS_STANDARD_CLASSES;
+
+ JS::CompartmentOptions options;
+ options.creationOptions().setZone(JS::SystemZone);
+ options.behaviors().setVersion(JSVERSION_LATEST);
+
+ if (xpc::SharedMemoryEnabled()) {
+ options.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
+ }
+
+ nsresult rv =
+ xpc->InitClassesWithNewWrappedGlobal(cx, aScope, mPrincipal,
+ flags, options, getter_AddRefs(mGlobal));
+ NS_ENSURE_SUCCESS(rv, false);
+
+
+ JS::Rooted<JSObject*> global(cx, mGlobal->GetJSObject());
+ NS_ENSURE_TRUE(global, false);
+
+ // Set the location information for the new global, so that tools like
+ // about:memory may use that information.
+ xpc::SetLocationForGlobal(global, aID);
+
+ DidCreateGlobal();
+ return true;
+}
+
+void
+nsMessageManagerScriptExecutor::MarkScopesForCC()
+{
+ for (uint32_t i = 0; i < mAnonymousGlobalScopes.Length(); ++i) {
+ mAnonymousGlobalScopes[i].exposeToActiveJS();
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsScriptCacheCleaner, nsIObserver)
+
+nsFrameMessageManager* nsFrameMessageManager::sChildProcessManager = nullptr;
+nsFrameMessageManager* nsFrameMessageManager::sParentProcessManager = nullptr;
+nsFrameMessageManager* nsFrameMessageManager::sSameProcessParentManager = nullptr;
+
+class nsAsyncMessageToSameProcessChild : public nsSameProcessAsyncMessageBase,
+ public Runnable
+{
+public:
+ nsAsyncMessageToSameProcessChild(JS::RootingContext* aRootingCx,
+ JS::Handle<JSObject*> aCpows)
+ : nsSameProcessAsyncMessageBase(aRootingCx, aCpows)
+ { }
+ NS_IMETHOD Run() override
+ {
+ nsFrameMessageManager* ppm = nsFrameMessageManager::GetChildProcessManager();
+ ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(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);
+ }
+ virtual ~SameParentProcessMessageManagerCallback()
+ {
+ MOZ_COUNT_DTOR(SameParentProcessMessageManagerCallback);
+ }
+
+ virtual bool DoLoadMessageManagerScript(const nsAString& aURL,
+ bool aRunInGlobalScope) override
+ {
+ ProcessGlobal* global = ProcessGlobal::Get();
+ MOZ_ASSERT(!aRunInGlobalScope);
+ global->LoadScript(aURL);
+ return true;
+ }
+
+ virtual nsresult DoSendAsyncMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal) override
+ {
+ JS::RootingContext* rcx = JS::RootingContext::get(aCx);
+ RefPtr<nsAsyncMessageToSameProcessChild> ev =
+ new nsAsyncMessageToSameProcessChild(rcx, aCpows);
+
+ nsresult rv = ev->Init(aMessage, aData, aPrincipal);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = NS_DispatchToCurrentThread(ev);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ bool CheckPermission(const nsAString& aPermission) override
+ {
+ // In a single-process scenario, the child always has all capabilities.
+ return true;
+ }
+
+ bool CheckManifestURL(const nsAString& aManifestURL) override
+ {
+ // In a single-process scenario, the child always has all capabilities.
+ return true;
+ }
+
+ bool CheckAppHasPermission(const nsAString& aPermission) override
+ {
+ // In a single-process scenario, the child always has all capabilities.
+ return true;
+ }
+
+ virtual bool CheckAppHasStatus(unsigned short aStatus) override
+ {
+ // In a single-process scenario, the child always has all capabilities.
+ return true;
+ }
+};
+
+
+/**
+ * Send messages to the parent process.
+ */
+class ChildProcessMessageManagerCallback : public MessageManagerCallback
+{
+public:
+ ChildProcessMessageManagerCallback()
+ {
+ MOZ_COUNT_CTOR(ChildProcessMessageManagerCallback);
+ }
+ virtual ~ChildProcessMessageManagerCallback()
+ {
+ MOZ_COUNT_DTOR(ChildProcessMessageManagerCallback);
+ }
+
+ virtual bool DoSendBlockingMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal,
+ bool aIsSync) override
+ {
+ mozilla::dom::ContentChild* cc =
+ mozilla::dom::ContentChild::GetSingleton();
+ if (!cc) {
+ return true;
+ }
+ ClonedMessageData data;
+ if (!BuildClonedMessageDataForChild(cc, aData, data)) {
+ return false;
+ }
+ InfallibleTArray<mozilla::jsipc::CpowEntry> cpows;
+ if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
+ return false;
+ }
+ if (aIsSync) {
+ return cc->SendSyncMessage(PromiseFlatString(aMessage), data, cpows,
+ IPC::Principal(aPrincipal), aRetVal);
+ }
+ return cc->SendRpcMessage(PromiseFlatString(aMessage), data, cpows,
+ IPC::Principal(aPrincipal), aRetVal);
+ }
+
+ virtual nsresult DoSendAsyncMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal) override
+ {
+ mozilla::dom::ContentChild* cc =
+ mozilla::dom::ContentChild::GetSingleton();
+ if (!cc) {
+ return NS_OK;
+ }
+ ClonedMessageData data;
+ if (!BuildClonedMessageDataForChild(cc, aData, data)) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+ InfallibleTArray<mozilla::jsipc::CpowEntry> cpows;
+ if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!cc->SendAsyncMessage(PromiseFlatString(aMessage), cpows,
+ IPC::Principal(aPrincipal), data)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+ }
+};
+
+
+class nsAsyncMessageToSameProcessParent : public nsSameProcessAsyncMessageBase,
+ public SameProcessMessageQueue::Runnable
+{
+public:
+ nsAsyncMessageToSameProcessParent(JS::RootingContext* aRootingCx,
+ JS::Handle<JSObject*> aCpows)
+ : nsSameProcessAsyncMessageBase(aRootingCx, aCpows)
+ { }
+ virtual nsresult HandleMessage() override
+ {
+ nsFrameMessageManager* ppm = nsFrameMessageManager::sSameProcessParentManager;
+ ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(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);
+ }
+ virtual ~SameChildProcessMessageManagerCallback()
+ {
+ MOZ_COUNT_DTOR(SameChildProcessMessageManagerCallback);
+ }
+
+ virtual bool DoSendBlockingMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal,
+ bool aIsSync) override
+ {
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ queue->Flush();
+
+ if (nsFrameMessageManager::sSameProcessParentManager) {
+ SameProcessCpowHolder cpows(JS::RootingContext::get(aCx), aCpows);
+ RefPtr<nsFrameMessageManager> ppm = nsFrameMessageManager::sSameProcessParentManager;
+ ppm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm.get()), nullptr, aMessage,
+ true, &aData, &cpows, aPrincipal, aRetVal);
+ }
+ return true;
+ }
+
+ virtual nsresult DoSendAsyncMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal) override
+ {
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ JS::RootingContext* rcx = JS::RootingContext::get(aCx);
+ RefPtr<nsAsyncMessageToSameProcessParent> ev =
+ new nsAsyncMessageToSameProcessParent(rcx, aCpows);
+ nsresult rv = ev->Init(aMessage, aData, aPrincipal);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ queue->Push(ev);
+ return NS_OK;
+ }
+
+};
+
+
+// This creates the global parent process message manager.
+nsresult
+NS_NewParentProcessMessageManager(nsIMessageBroadcaster** aResult)
+{
+ NS_ASSERTION(!nsFrameMessageManager::sParentProcessManager,
+ "Re-creating sParentProcessManager");
+ RefPtr<nsFrameMessageManager> mm = new nsFrameMessageManager(nullptr,
+ nullptr,
+ MM_CHROME | MM_PROCESSMANAGER | MM_BROADCASTER);
+ nsFrameMessageManager::sParentProcessManager = mm;
+ nsFrameMessageManager::NewProcessMessageManager(false); // Create same process message manager.
+ mm.forget(aResult);
+ return NS_OK;
+}
+
+
+nsFrameMessageManager*
+nsFrameMessageManager::NewProcessMessageManager(bool aIsRemote)
+{
+ if (!nsFrameMessageManager::sParentProcessManager) {
+ nsCOMPtr<nsIMessageBroadcaster> dummy =
+ do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+ }
+
+ MOZ_ASSERT(nsFrameMessageManager::sParentProcessManager,
+ "parent process manager not created");
+ nsFrameMessageManager* mm;
+ if (aIsRemote) {
+ // Callback is set in ContentParent::InitInternal so that the process has
+ // already started when we send pending scripts.
+ mm = new nsFrameMessageManager(nullptr,
+ nsFrameMessageManager::sParentProcessManager,
+ MM_CHROME | MM_PROCESSMANAGER);
+ } else {
+ mm = new nsFrameMessageManager(new SameParentProcessMessageManagerCallback(),
+ nsFrameMessageManager::sParentProcessManager,
+ MM_CHROME | MM_PROCESSMANAGER | MM_OWNSCALLBACK);
+ sSameProcessParentManager = mm;
+ }
+ return mm;
+}
+
+nsresult
+NS_NewChildProcessMessageManager(nsISyncMessageSender** aResult)
+{
+ NS_ASSERTION(!nsFrameMessageManager::GetChildProcessManager(),
+ "Re-creating sChildProcessManager");
+
+ MessageManagerCallback* cb;
+ if (XRE_IsParentProcess()) {
+ cb = new SameChildProcessMessageManagerCallback();
+ } else {
+ cb = new ChildProcessMessageManagerCallback();
+ RegisterStrongMemoryReporter(new MessageManagerReporter());
+ }
+ nsFrameMessageManager* mm = new nsFrameMessageManager(cb,
+ nullptr,
+ MM_PROCESSMANAGER | MM_OWNSCALLBACK);
+ nsFrameMessageManager::SetChildProcessManager(mm);
+ RefPtr<ProcessGlobal> global = new ProcessGlobal(mm);
+ NS_ENSURE_TRUE(global->Init(), NS_ERROR_UNEXPECTED);
+ global.forget(aResult);
+ return NS_OK;
+}
+
+bool
+nsFrameMessageManager::MarkForCC()
+{
+ for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
+ uint32_t count = listeners->Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIMessageListener> strongListener =
+ listeners->ElementAt(i).mStrongListener;
+ if (strongListener) {
+ xpc_TryUnmarkWrappedGrayObject(strongListener);
+ }
+ }
+ }
+
+ if (mRefCnt.IsPurple()) {
+ mRefCnt.RemovePurple();
+ }
+ return true;
+}
+
+nsSameProcessAsyncMessageBase::nsSameProcessAsyncMessageBase(JS::RootingContext* aRootingCx,
+ JS::Handle<JSObject*> aCpows)
+ : mRootingCx(aRootingCx)
+ , mCpows(aRootingCx, aCpows)
+#ifdef DEBUG
+ , mCalledInit(false)
+#endif
+{ }
+
+
+nsresult
+nsSameProcessAsyncMessageBase::Init(const nsAString& aMessage,
+ StructuredCloneData& aData,
+ nsIPrincipal* aPrincipal)
+{
+ if (!mData.Copy(aData)) {
+ Telemetry::Accumulate(Telemetry::IPC_SAME_PROCESS_MESSAGE_COPY_OOM_KB, aData.DataLength());
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mMessage = aMessage;
+ mPrincipal = aPrincipal;
+#ifdef DEBUG
+ mCalledInit = true;
+#endif
+
+ return NS_OK;
+}
+
+void
+nsSameProcessAsyncMessageBase::ReceiveMessage(nsISupports* aTarget,
+ nsIFrameLoader* aTargetFrameLoader,
+ nsFrameMessageManager* aManager)
+{
+ // Make sure that we have called Init() and it has succeeded.
+ MOZ_ASSERT(mCalledInit);
+ if (aManager) {
+ SameProcessCpowHolder cpows(mRootingCx, mCpows);
+
+ RefPtr<nsFrameMessageManager> mm = aManager;
+ mm->ReceiveMessage(aTarget, aTargetFrameLoader, mMessage, false, &mData,
+ &cpows, mPrincipal, nullptr);
+ }
+}
diff --git a/dom/base/nsFrameMessageManager.h b/dom/base/nsFrameMessageManager.h
new file mode 100644
index 000000000..077389a49
--- /dev/null
+++ b/dom/base/nsFrameMessageManager.h
@@ -0,0 +1,450 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIMessageManager.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsIAtom.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "nsIPrincipal.h"
+#include "nsIXPConnect.h"
+#include "nsDataHashtable.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "nsWeakPtr.h"
+#include "mozilla/Attributes.h"
+#include "js/RootingAPI.h"
+#include "nsTObserverArray.h"
+#include "mozilla/dom/SameProcessMessageQueue.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/jsipc/CpowHolder.h"
+
+class nsIFrameLoader;
+
+namespace mozilla {
+namespace dom {
+
+class nsIContentParent;
+class nsIContentChild;
+class ClonedMessageData;
+class MessageManagerReporter;
+
+namespace ipc {
+
+enum MessageManagerFlags {
+ MM_CHILD = 0,
+ MM_CHROME = 1,
+ MM_GLOBAL = 2,
+ MM_PROCESSMANAGER = 4,
+ MM_BROADCASTER = 8,
+ MM_OWNSCALLBACK = 16
+};
+
+class MessageManagerCallback
+{
+public:
+ virtual ~MessageManagerCallback() {}
+
+ virtual bool DoLoadMessageManagerScript(const nsAString& aURL, bool aRunInGlobalScope)
+ {
+ return true;
+ }
+
+ virtual bool DoSendBlockingMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject*> aCpows,
+ nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal,
+ bool aIsSync)
+ {
+ return true;
+ }
+
+ virtual nsresult DoSendAsyncMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject*> aCpows,
+ nsIPrincipal* aPrincipal)
+ {
+ return NS_OK;
+ }
+
+ virtual bool CheckPermission(const nsAString& aPermission)
+ {
+ return false;
+ }
+
+ virtual bool CheckManifestURL(const nsAString& aManifestURL)
+ {
+ return false;
+ }
+
+ virtual bool CheckAppHasPermission(const nsAString& aPermission)
+ {
+ return false;
+ }
+
+ virtual bool CheckAppHasStatus(unsigned short aStatus)
+ {
+ return false;
+ }
+
+ virtual bool KillChild()
+ {
+ // By default, does nothing.
+ return false;
+ }
+
+ virtual nsIMessageSender* GetProcessMessageManager() const
+ {
+ return nullptr;
+ }
+
+protected:
+ bool BuildClonedMessageDataForParent(nsIContentParent* aParent,
+ StructuredCloneData& aData,
+ ClonedMessageData& aClonedData);
+ bool BuildClonedMessageDataForChild(nsIContentChild* aChild,
+ StructuredCloneData& aData,
+ ClonedMessageData& aClonedData);
+};
+
+void UnpackClonedMessageDataForParent(const ClonedMessageData& aClonedData,
+ StructuredCloneData& aData);
+
+void UnpackClonedMessageDataForChild(const ClonedMessageData& aClonedData,
+ StructuredCloneData& aData);
+
+} // namespace ipc
+} // namespace dom
+} // namespace mozilla
+
+struct nsMessageListenerInfo
+{
+ bool operator==(const nsMessageListenerInfo& aOther) const
+ {
+ return &aOther == this;
+ }
+
+ // Exactly one of mStrongListener and mWeakListener must be non-null.
+ nsCOMPtr<nsIMessageListener> mStrongListener;
+ nsWeakPtr mWeakListener;
+ bool mListenWhenClosed;
+};
+
+
+class MOZ_STACK_CLASS SameProcessCpowHolder : public mozilla::jsipc::CpowHolder
+{
+public:
+ SameProcessCpowHolder(JS::RootingContext* aRootingCx, JS::Handle<JSObject*> aObj)
+ : mObj(aRootingCx, aObj)
+ {
+ }
+
+ virtual bool ToObject(JSContext* aCx, JS::MutableHandle<JSObject*> aObjp)
+ override;
+
+private:
+ JS::Rooted<JSObject*> mObj;
+};
+
+class nsFrameMessageManager final : public nsIContentFrameMessageManager,
+ public nsIMessageBroadcaster,
+ public nsIFrameScriptLoader,
+ public nsIGlobalProcessScriptLoader,
+ public nsIProcessChecker
+{
+ friend class mozilla::dom::MessageManagerReporter;
+ typedef mozilla::dom::ipc::StructuredCloneData StructuredCloneData;
+public:
+ nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback,
+ nsFrameMessageManager* aParentManager,
+ /* mozilla::dom::ipc::MessageManagerFlags */ uint32_t aFlags);
+
+private:
+ ~nsFrameMessageManager();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsFrameMessageManager,
+ nsIContentFrameMessageManager)
+ NS_DECL_NSIMESSAGELISTENERMANAGER
+ NS_DECL_NSIMESSAGESENDER
+ NS_DECL_NSIMESSAGEBROADCASTER
+ NS_DECL_NSISYNCMESSAGESENDER
+ NS_DECL_NSIMESSAGEMANAGERGLOBAL
+ NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER
+ NS_DECL_NSIFRAMESCRIPTLOADER
+ NS_DECL_NSIPROCESSSCRIPTLOADER
+ NS_DECL_NSIGLOBALPROCESSSCRIPTLOADER
+ NS_DECL_NSIPROCESSCHECKER
+
+ static nsFrameMessageManager*
+ NewProcessMessageManager(bool aIsRemote);
+
+ nsresult ReceiveMessage(nsISupports* aTarget, nsIFrameLoader* aTargetFrameLoader,
+ const nsAString& aMessage,
+ bool aIsSync, StructuredCloneData* aCloneData,
+ mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal);
+
+ void AddChildManager(nsFrameMessageManager* aManager);
+ void RemoveChildManager(nsFrameMessageManager* aManager)
+ {
+ mChildManagers.RemoveObject(aManager);
+ }
+ void Disconnect(bool aRemoveFromParent = true);
+ void Close();
+
+ void InitWithCallback(mozilla::dom::ipc::MessageManagerCallback* aCallback);
+ void SetCallback(mozilla::dom::ipc::MessageManagerCallback* aCallback);
+
+ mozilla::dom::ipc::MessageManagerCallback* GetCallback()
+ {
+ return mCallback;
+ }
+
+ nsresult DispatchAsyncMessage(const nsAString& aMessageName,
+ const JS::Value& aJSON,
+ const JS::Value& aObjects,
+ nsIPrincipal* aPrincipal,
+ const JS::Value& aTransfers,
+ JSContext* aCx,
+ uint8_t aArgc);
+
+ nsresult DispatchAsyncMessageInternal(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject*> aCpows,
+ nsIPrincipal* aPrincipal);
+ void RemoveFromParent();
+ nsFrameMessageManager* GetParentManager() { return mParentManager; }
+ void SetParentManager(nsFrameMessageManager* aParent)
+ {
+ NS_ASSERTION(!mParentManager, "We have parent manager already!");
+ NS_ASSERTION(mChrome, "Should not set parent manager!");
+ mParentManager = aParent;
+ }
+ bool IsGlobal() { return mGlobal; }
+ bool IsBroadcaster() { return mIsBroadcaster; }
+
+ static nsFrameMessageManager* GetParentProcessManager()
+ {
+ return sParentProcessManager;
+ }
+ static nsFrameMessageManager* GetChildProcessManager()
+ {
+ return sChildProcessManager;
+ }
+ static void SetChildProcessManager(nsFrameMessageManager* aManager)
+ {
+ sChildProcessManager = aManager;
+ }
+
+ void SetInitialProcessData(JS::HandleValue aInitialData);
+
+ void LoadPendingScripts();
+
+private:
+ nsresult SendMessage(const nsAString& aMessageName,
+ JS::Handle<JS::Value> aJSON,
+ JS::Handle<JS::Value> aObjects,
+ nsIPrincipal* aPrincipal,
+ JSContext* aCx,
+ uint8_t aArgc,
+ JS::MutableHandle<JS::Value> aRetval,
+ bool aIsSync);
+
+ nsresult ReceiveMessage(nsISupports* aTarget, nsIFrameLoader* aTargetFrameLoader,
+ bool aTargetClosed, const nsAString& aMessage,
+ bool aIsSync, StructuredCloneData* aCloneData,
+ mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal);
+
+ NS_IMETHOD LoadScript(const nsAString& aURL,
+ bool aAllowDelayedLoad,
+ bool aRunInGlobalScope);
+ NS_IMETHOD RemoveDelayedScript(const nsAString& aURL);
+ NS_IMETHOD GetDelayedScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList);
+
+protected:
+ friend class MMListenerRemover;
+ // 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;
+ nsCOMArray<nsIContentFrameMessageManager> 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;
+ nsAutoPtr<mozilla::dom::ipc::MessageManagerCallback> mOwnedCallback;
+ RefPtr<nsFrameMessageManager> mParentManager;
+ nsTArray<nsString> mPendingScripts;
+ nsTArray<bool> mPendingScriptsGlobalStates;
+ JS::Heap<JS::Value> mInitialProcessData;
+
+ void LoadPendingScripts(nsFrameMessageManager* aManager,
+ nsFrameMessageManager* aChildMM);
+public:
+ static nsFrameMessageManager* sParentProcessManager;
+ static nsFrameMessageManager* sSameProcessParentManager;
+ static nsTArray<nsCOMPtr<nsIRunnable> >* sPendingSameProcessAsyncMessages;
+private:
+ static nsFrameMessageManager* sChildProcessManager;
+ enum ProcessCheckerType {
+ PROCESS_CHECKER_PERMISSION,
+ PROCESS_CHECKER_MANIFEST_URL,
+ ASSERT_APP_HAS_PERMISSION
+ };
+ nsresult AssertProcessInternal(ProcessCheckerType aType,
+ const nsAString& aCapability,
+ bool* aValid);
+};
+
+/* 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() override {
+ ReceiveMessage(..., ...);
+ return NS_OK;
+ }
+ };
+
+
+ RefPtr<nsSameProcessAsyncMessageBase> ev = new MyAsyncMessage();
+ nsresult rv = ev->Init(...);
+ if (NS_SUCCEEDED(rv)) {
+ NS_DispatchToMainThread(ev);
+ }
+*/
+class nsSameProcessAsyncMessageBase
+{
+public:
+ typedef mozilla::dom::ipc::StructuredCloneData StructuredCloneData;
+
+ nsSameProcessAsyncMessageBase(JS::RootingContext* aRootingCx,
+ JS::Handle<JSObject*> aCpows);
+ nsresult Init(const nsAString& aMessage,
+ StructuredCloneData& aData,
+ nsIPrincipal* aPrincipal);
+
+ void ReceiveMessage(nsISupports* aTarget, nsIFrameLoader* aTargetFrameLoader,
+ nsFrameMessageManager* aManager);
+private:
+ nsSameProcessAsyncMessageBase(const nsSameProcessAsyncMessageBase&);
+
+ JS::RootingContext* mRootingCx;
+ nsString mMessage;
+ StructuredCloneData mData;
+ JS::PersistentRooted<JSObject*> mCpows;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+#ifdef DEBUG
+ bool mCalledInit;
+#endif
+};
+
+class nsScriptCacheCleaner;
+
+struct nsMessageManagerScriptHolder
+{
+ nsMessageManagerScriptHolder(JSContext* aCx,
+ JSScript* aScript,
+ bool aRunInGlobalScope)
+ : mScript(aCx, aScript), mRunInGlobalScope(aRunInGlobalScope)
+ { MOZ_COUNT_CTOR(nsMessageManagerScriptHolder); }
+
+ ~nsMessageManagerScriptHolder()
+ { MOZ_COUNT_DTOR(nsMessageManagerScriptHolder); }
+
+ bool WillRunInGlobalScope() { return mRunInGlobalScope; }
+
+ JS::PersistentRooted<JSScript*> mScript;
+ bool mRunInGlobalScope;
+};
+
+class nsMessageManagerScriptExecutor
+{
+public:
+ static void PurgeCache();
+ static void Shutdown();
+ already_AddRefed<nsIXPConnectJSObjectHolder> GetGlobal()
+ {
+ nsCOMPtr<nsIXPConnectJSObjectHolder> ref = mGlobal;
+ return ref.forget();
+ }
+
+ void MarkScopesForCC();
+protected:
+ friend class nsMessageManagerScriptCx;
+ nsMessageManagerScriptExecutor() { MOZ_COUNT_CTOR(nsMessageManagerScriptExecutor); }
+ ~nsMessageManagerScriptExecutor() { MOZ_COUNT_DTOR(nsMessageManagerScriptExecutor); }
+
+ void DidCreateGlobal();
+ void LoadScriptInternal(const nsAString& aURL, bool aRunInGlobalScope);
+ void TryCacheLoadAndCompileScript(const nsAString& aURL,
+ bool aRunInGlobalScope,
+ bool aShouldCache,
+ JS::MutableHandle<JSScript*> aScriptp);
+ void TryCacheLoadAndCompileScript(const nsAString& aURL,
+ bool aRunInGlobalScope);
+ bool InitChildGlobalInternal(nsISupports* aScope, const nsACString& aID);
+ void Trace(const TraceCallbacks& aCallbacks, void* aClosure);
+ nsCOMPtr<nsIXPConnectJSObjectHolder> mGlobal;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ AutoTArray<JS::Heap<JSObject*>, 2> mAnonymousGlobalScopes;
+
+ static nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>* sCachedScripts;
+ static mozilla::StaticRefPtr<nsScriptCacheCleaner> sScriptCacheCleaner;
+};
+
+class nsScriptCacheCleaner final : public nsIObserver
+{
+ ~nsScriptCacheCleaner() {}
+
+ NS_DECL_ISUPPORTS
+
+ nsScriptCacheCleaner()
+ {
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->AddObserver(this, "message-manager-flush-caches", false);
+ obsSvc->AddObserver(this, "xpcom-shutdown", false);
+ }
+ }
+
+ NS_IMETHOD Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData) override
+ {
+ if (strcmp("message-manager-flush-caches", aTopic) == 0) {
+ nsMessageManagerScriptExecutor::PurgeCache();
+ } else if (strcmp("xpcom-shutdown", aTopic) == 0) {
+ nsMessageManagerScriptExecutor::Shutdown();
+ }
+ return NS_OK;
+ }
+};
+
+#endif
diff --git a/dom/base/nsGenConImageContent.cpp b/dom/base/nsGenConImageContent.cpp
new file mode 100644
index 000000000..e1b1f5a17
--- /dev/null
+++ b/dom/base/nsGenConImageContent.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/. */
+
+/**
+ * A fake content node class so that the image frame for
+ * content: url(foo);
+ * in CSS can have an nsIImageLoadingContent but use an
+ * imgIRequest that's already been loaded from the style system.
+ */
+
+#include "nsContentCreatorFunctions.h"
+#include "nsXMLElement.h"
+#include "nsImageLoadingContent.h"
+#include "imgIRequest.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+
+using namespace mozilla;
+
+class nsGenConImageContent final : public nsXMLElement,
+ public nsImageLoadingContent
+{
+public:
+ explicit nsGenConImageContent(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsXMLElement(aNodeInfo)
+ {
+ // nsImageLoadingContent starts out broken, so we start out
+ // suppressed to match it.
+ AddStatesSilently(NS_EVENT_STATE_SUPPRESSED);
+ }
+
+ nsresult Init(imgRequestProxy* aImageRequest)
+ {
+ // No need to notify, since we have no frame.
+ return UseAsPrimaryRequest(aImageRequest, false, eImageLoadType_Normal);
+ }
+
+ // nsIContent overrides
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
+ virtual EventStates IntrinsicState() const override;
+
+ virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override
+ {
+ MOZ_ASSERT(IsInNativeAnonymousSubtree());
+ if (aVisitor.mEvent->mMessage == eLoad ||
+ aVisitor.mEvent->mMessage == eLoadError) {
+ // Don't propagate the events to the parent.
+ return NS_OK;
+ }
+ return nsXMLElement::PreHandleEvent(aVisitor);
+ }
+
+private:
+ virtual ~nsGenConImageContent();
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsGenConImageContent,
+ nsXMLElement,
+ nsIImageLoadingContent,
+ imgINotificationObserver,
+ imgIOnloadBlocker)
+
+nsresult
+NS_NewGenConImageContent(nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ imgRequestProxy* aImageRequest)
+{
+ NS_PRECONDITION(aImageRequest, "Must have request!");
+ nsGenConImageContent *it = new nsGenConImageContent(aNodeInfo);
+ NS_ADDREF(*aResult = it);
+ nsresult rv = it->Init(aImageRequest);
+ if (NS_FAILED(rv))
+ NS_RELEASE(*aResult);
+ return rv;
+}
+
+nsGenConImageContent::~nsGenConImageContent()
+{
+ DestroyImageLoadingContent();
+}
+
+nsresult
+nsGenConImageContent::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv;
+ rv = nsXMLElement::BindToTree(aDocument, aParent, aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
+ aCompileEventHandlers);
+ return NS_OK;
+}
+
+void
+nsGenConImageContent::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
+ nsXMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+EventStates
+nsGenConImageContent::IntrinsicState() const
+{
+ EventStates state = nsXMLElement::IntrinsicState();
+
+ EventStates imageState = nsImageLoadingContent::ImageState();
+ if (imageState.HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED)) {
+ // We should never be in an error state; if the image fails to load, we
+ // just go to the suppressed state.
+ imageState |= NS_EVENT_STATE_SUPPRESSED;
+ imageState &= ~NS_EVENT_STATE_BROKEN;
+ }
+ imageState &= ~NS_EVENT_STATE_LOADING;
+ return state | imageState;
+}
diff --git a/dom/base/nsGenericDOMDataNode.cpp b/dom/base/nsGenericDOMDataNode.cpp
new file mode 100644
index 000000000..9688588e0
--- /dev/null
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -0,0 +1,1148 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMComment, nsIDOMDocumentType, nsIDOMText,
+ * nsIDOMCDATASection, and nsIDOMProcessingInstruction nodes.
+ */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsGenericDOMDataNode.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsReadableUtils.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "nsIURI.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMText.h"
+#include "nsCOMPtr.h"
+#include "nsDOMString.h"
+#include "nsChangeHint.h"
+#include "nsCOMArray.h"
+#include "nsNodeUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsBindingManager.h"
+#include "nsCCUncollectableMarker.h"
+#include "mozAutoDocUpdate.h"
+#include "nsTextNode.h"
+
+#include "PLDHashTable.h"
+#include "mozilla/Sprintf.h"
+#include "nsWrapperCacheInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsGenericDOMDataNode::nsGenericDOMDataNode(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsIContent(aNodeInfo)
+{
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::TEXT_NODE ||
+ mNodeInfo->NodeType() == nsIDOMNode::CDATA_SECTION_NODE ||
+ mNodeInfo->NodeType() == nsIDOMNode::COMMENT_NODE ||
+ mNodeInfo->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE ||
+ mNodeInfo->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE,
+ "Bad NodeType in aNodeInfo");
+}
+
+nsGenericDOMDataNode::nsGenericDOMDataNode(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsIContent(aNodeInfo)
+{
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::TEXT_NODE ||
+ mNodeInfo->NodeType() == nsIDOMNode::CDATA_SECTION_NODE ||
+ mNodeInfo->NodeType() == nsIDOMNode::COMMENT_NODE ||
+ mNodeInfo->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE ||
+ mNodeInfo->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE,
+ "Bad NodeType in aNodeInfo");
+}
+
+nsGenericDOMDataNode::~nsGenericDOMDataNode()
+{
+ NS_PRECONDITION(!IsInUncomposedDoc(),
+ "Please remove this from the document properly");
+ if (GetParent()) {
+ NS_RELEASE(mParent);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericDOMDataNode)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsGenericDOMDataNode)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGenericDOMDataNode)
+ return Element::CanSkip(tmp, aRemovingAllowed);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGenericDOMDataNode)
+ return Element::CanSkipInCC(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGenericDOMDataNode)
+ return Element::CanSkipThis(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGenericDOMDataNode)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[40];
+ SprintfLiteral(name, "nsGenericDOMDataNode (len=%d)",
+ tmp->mText.GetLength());
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGenericDOMDataNode, tmp->mRefCnt.get())
+ }
+
+ // Always need to traverse script objects, so do that before we check
+ // if we're uncollectable.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+
+ if (!nsINode::Traverse(tmp, cb)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+
+ nsDataSlots *slots = tmp->GetExistingDataSlots();
+ if (slots) {
+ slots->Traverse(cb);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGenericDOMDataNode)
+ nsINode::Unlink(tmp);
+
+ // Clear flag here because unlinking slots will clear the
+ // containing shadow root pointer.
+ tmp->UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ nsDataSlots *slots = tmp->GetExistingDataSlots();
+ if (slots) {
+ slots->Unlink();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN(nsGenericDOMDataNode)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsGenericDOMDataNode)
+ NS_INTERFACE_MAP_ENTRY(nsIContent)
+ NS_INTERFACE_MAP_ENTRY(nsINode)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
+ 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_CYCLE_COLLECTING_ADDREF(nsGenericDOMDataNode)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsGenericDOMDataNode,
+ nsNodeUtils::LastRelease(this))
+
+
+void
+nsGenericDOMDataNode::GetNodeValueInternal(nsAString& aNodeValue)
+{
+ DebugOnly<nsresult> rv = GetData(aNodeValue);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetData() failed!");
+}
+
+void
+nsGenericDOMDataNode::SetNodeValueInternal(const nsAString& aNodeValue,
+ ErrorResult& aError)
+{
+ aError = SetTextInternal(0, mText.GetLength(), aNodeValue.BeginReading(),
+ aNodeValue.Length(), true);
+}
+
+//----------------------------------------------------------------------
+
+// Implementation of nsIDOMCharacterData
+
+nsresult
+nsGenericDOMDataNode::GetData(nsAString& aData) const
+{
+ if (mText.Is2b()) {
+ aData.Assign(mText.Get2b(), mText.GetLength());
+ } 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();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsGenericDOMDataNode::SetData(const nsAString& aData)
+{
+ return SetTextInternal(0, mText.GetLength(), aData.BeginReading(),
+ aData.Length(), true);
+}
+
+nsresult
+nsGenericDOMDataNode::GetLength(uint32_t* aLength)
+{
+ *aLength = mText.GetLength();
+ return NS_OK;
+}
+
+nsresult
+nsGenericDOMDataNode::SubstringData(uint32_t aStart, uint32_t aCount,
+ nsAString& aReturn)
+{
+ ErrorResult rv;
+ SubstringData(aStart, aCount, aReturn, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsGenericDOMDataNode::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);
+ }
+}
+
+NS_IMETHODIMP
+nsGenericDOMDataNode::MozRemove()
+{
+ Remove();
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+nsGenericDOMDataNode::AppendData(const nsAString& aData)
+{
+ return SetTextInternal(mText.GetLength(), 0, aData.BeginReading(),
+ aData.Length(), true);
+}
+
+nsresult
+nsGenericDOMDataNode::InsertData(uint32_t aOffset,
+ const nsAString& aData)
+{
+ return SetTextInternal(aOffset, 0, aData.BeginReading(),
+ aData.Length(), true);
+}
+
+nsresult
+nsGenericDOMDataNode::DeleteData(uint32_t aOffset, uint32_t aCount)
+{
+ return SetTextInternal(aOffset, aCount, nullptr, 0, true);
+}
+
+nsresult
+nsGenericDOMDataNode::ReplaceData(uint32_t aOffset, uint32_t aCount,
+ const nsAString& aData)
+{
+ return SetTextInternal(aOffset, aCount, aData.BeginReading(),
+ aData.Length(), true);
+}
+
+nsresult
+nsGenericDOMDataNode::SetTextInternal(uint32_t aOffset, uint32_t aCount,
+ const char16_t* aBuffer,
+ uint32_t aLength, bool aNotify,
+ CharacterDataChangeInfo::Details* aDetails)
+{
+ NS_PRECONDITION(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;
+ }
+
+ nsIDocument *document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
+
+ bool haveMutationListeners = aNotify &&
+ nsContentUtils::HasMutationListeners(this,
+ NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED,
+ this);
+
+ nsCOMPtr<nsIAtom> oldValue;
+ if (haveMutationListeners) {
+ oldValue = GetCurrentValueAtom();
+ }
+
+ if (aNotify) {
+ CharacterDataChangeInfo info = {
+ aOffset == textLength,
+ aOffset,
+ endOffset,
+ aLength,
+ aDetails
+ };
+ nsNodeUtils::CharacterDataWillChange(this, &info);
+ }
+
+ Directionality oldDir = eDir_NotSet;
+ bool dirAffectsAncestor = (NodeType() == nsIDOMNode::TEXT_NODE &&
+ TextNodeWillChangeDirection(this, &oldDir, aOffset));
+
+ if (aOffset == 0 && endOffset == textLength) {
+ // Replacing whole text or old text was empty. Don't bother to check for
+ // bidi in this string if the document already has bidi enabled.
+ bool ok = mText.SetTo(aBuffer, aLength, !document || !document->GetBidiEnabled());
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (aOffset == textLength) {
+ // Appending to existing
+ bool ok = mText.Append(aBuffer, aLength, !document || !document->GetBidiEnabled());
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ // Merging old and new
+
+ // Allocate new buffer
+ int32_t newLength = textLength - aCount + aLength;
+ char16_t* to = new char16_t[newLength];
+
+ // Copy over appropriate data
+ if (aOffset) {
+ mText.CopyTo(to, 0, aOffset);
+ }
+ if (aLength) {
+ memcpy(to + aOffset, aBuffer, aLength * sizeof(char16_t));
+ }
+ if (endOffset != textLength) {
+ mText.CopyTo(to + aOffset + aLength, endOffset, textLength - endOffset);
+ }
+
+ bool ok = mText.SetTo(to, newLength, !document || !document->GetBidiEnabled());
+
+ delete [] to;
+
+ 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() == nsIDOMNode::TEXT_NODE);
+ TextNodeChangedDirection(static_cast<nsTextNode*>(this), oldDir, aNotify);
+ }
+
+ // Notify observers
+ if (aNotify) {
+ CharacterDataChangeInfo info = {
+ aOffset == textLength,
+ aOffset,
+ endOffset,
+ aLength,
+ aDetails
+ };
+ nsNodeUtils::CharacterDataChanged(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);
+ (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe();
+ }
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+// Implementation of nsIContent
+
+#ifdef DEBUG
+void
+nsGenericDOMDataNode::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)) {
+ char buf[10];
+ SprintfLiteral(buf, "\\u%04x", ch);
+ AppendASCIItoUTF16(buf, aBuf);
+ } 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)) {
+ char buf[10];
+ SprintfLiteral(buf, "\\u%04x", ch);
+ AppendASCIItoUTF16(buf, aBuf);
+ } else {
+ aBuf.Append(ch);
+ }
+ }
+ }
+}
+#endif
+
+
+nsresult
+nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!");
+ NS_PRECONDITION(NODE_FROM(aParent, aDocument)->OwnerDoc() == OwnerDoc(),
+ "Must have the same owner document");
+ NS_PRECONDITION(!aParent || aDocument == aParent->GetUncomposedDoc(),
+ "aDocument must be current doc of aParent");
+ NS_PRECONDITION(!GetUncomposedDoc() && !IsInUncomposedDoc(),
+ "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.
+ NS_PRECONDITION(!GetParent() || aParent == GetParent(),
+ "Already have a parent. Unbind first!");
+ NS_PRECONDITION(!GetBindingParent() ||
+ aBindingParent == GetBindingParent() ||
+ (!aBindingParent && aParent &&
+ aParent->GetBindingParent() == GetBindingParent()),
+ "Already have a binding parent. Unbind first!");
+ NS_PRECONDITION(aBindingParent != this,
+ "Content must not be its own binding parent");
+ NS_PRECONDITION(!IsRootOfNativeAnonymousSubtree() ||
+ aBindingParent == aParent,
+ "Native anonymous content must have its parent as its "
+ "own binding parent");
+
+ if (!aBindingParent && aParent) {
+ aBindingParent = aParent->GetBindingParent();
+ }
+
+ // First set the binding parent
+ if (aBindingParent) {
+ NS_ASSERTION(IsRootOfNativeAnonymousSubtree() ||
+ !HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE) ||
+ (aParent && aParent->IsInNativeAnonymousSubtree()),
+ "Trying to re-bind content from native anonymous subtree to "
+ "non-native anonymous parent!");
+ DataSlots()->mBindingParent = aBindingParent; // Weak, so no addref happens.
+ if (aParent->IsInNativeAnonymousSubtree()) {
+ SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+ }
+ if (aParent->HasFlag(NODE_CHROME_ONLY_ACCESS)) {
+ SetFlags(NODE_CHROME_ONLY_ACCESS);
+ }
+ if (aParent->IsInShadowTree()) {
+ ClearSubtreeRootPointer();
+ SetFlags(NODE_IS_IN_SHADOW_TREE);
+ }
+ ShadowRoot* parentContainingShadow = aParent->GetContainingShadow();
+ if (parentContainingShadow) {
+ DataSlots()->mContainingShadow = parentContainingShadow;
+ }
+ }
+
+ bool hadParent = !!GetParentNode();
+
+ // Set parent
+ if (aParent) {
+ if (!GetParent()) {
+ NS_ADDREF(aParent);
+ }
+ mParent = aParent;
+ }
+ else {
+ mParent = aDocument;
+ }
+ SetParentIsContent(aParent);
+
+ // XXXbz sXBL/XBL2 issue!
+
+ // Set document
+ if (aDocument) {
+ // We no longer need to track the subtree pointer (and in fact we'll assert
+ // if we do this any later).
+ ClearSubtreeRootPointer();
+
+ // XXX See the comment in Element::BindToTree
+ SetIsInDocument();
+ if (mText.IsBidi()) {
+ aDocument->SetBidiEnabled();
+ }
+ // Clear the lazy frame construction bits.
+ UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
+ } else if (!IsInShadowTree()) {
+ // If we're not in the doc and not in a shadow tree,
+ // update our subtree pointer.
+ SetSubtreeRootPointer(aParent->SubtreeRoot());
+ }
+
+ nsNodeUtils::ParentChainChanged(this);
+ if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
+ nsNodeUtils::NativeAnonymousChildListChange(this, false);
+ }
+
+ UpdateEditableState(false);
+
+ // It would be cleanest to mark nodes as dirty when (a) they're created and
+ // (b) they're unbound from a tree. However, we can't easily do (a) right now,
+ // because IsStyledByServo() is not always easy to check at node creation time,
+ // and the bits have different meaning in the non-IsStyledByServo case.
+ //
+ // So for now, we just mark nodes as dirty when they're inserted into a
+ // document or shadow tree.
+ if (IsStyledByServo() && IsInComposedDoc()) {
+ MOZ_ASSERT(!HasServoData());
+ SetIsDirtyForServo();
+ }
+
+ NS_POSTCONDITION(aDocument == GetUncomposedDoc(), "Bound to wrong document");
+ NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
+ NS_POSTCONDITION(aBindingParent == GetBindingParent(),
+ "Bound to wrong binding parent");
+
+ return NS_OK;
+}
+
+void
+nsGenericDOMDataNode::UnbindFromTree(bool aDeep, 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);
+
+ nsIDocument* document =
+ HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetComposedDoc();
+
+ if (aNullParent) {
+ if (this->IsRootOfNativeAnonymousSubtree()) {
+ nsNodeUtils::NativeAnonymousChildListChange(this, true);
+ }
+ if (GetParent()) {
+ NS_RELEASE(mParent);
+ } else {
+ mParent = nullptr;
+ }
+ SetParentIsContent(false);
+ }
+ ClearInDocument();
+
+ // Computed styled data isn't useful for detached nodes, and we'll need to
+ // recomputed it anyway if we ever insert the nodes back into a document.
+ if (IsStyledByServo()) {
+ ClearServoData();
+ } else {
+#ifdef MOZ_STYLO
+ MOZ_ASSERT(!HasServoData());
+#endif
+ }
+
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ // Begin keeping track of our subtree root.
+ SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+ }
+
+ if (document && !GetContainingShadow()) {
+ // Notify XBL- & nsIAnonymousContentCreator-generated
+ // anonymous content that the document is changing.
+ // Unlike XBL, bindings for web components shadow DOM
+ // do not get uninstalled.
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsContentUtils::AddScriptRunner(
+ new RemoveFromBindingManagerRunnable(document->BindingManager(), this,
+ document));
+ }
+ }
+
+ nsDataSlots *slots = GetExistingDataSlots();
+ if (slots) {
+ slots->mBindingParent = nullptr;
+ if (aNullParent || !mParent->IsInShadowTree()) {
+ slots->mContainingShadow = nullptr;
+ }
+ }
+
+ nsNodeUtils::ParentChainChanged(this);
+}
+
+already_AddRefed<nsINodeList>
+nsGenericDOMDataNode::GetChildren(uint32_t aFilter)
+{
+ return nullptr;
+}
+
+nsresult
+nsGenericDOMDataNode::SetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify)
+{
+ return NS_OK;
+}
+
+nsresult
+nsGenericDOMDataNode::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
+ bool aNotify)
+{
+ return NS_OK;
+}
+
+const nsAttrName*
+nsGenericDOMDataNode::GetAttrNameAt(uint32_t aIndex) const
+{
+ return nullptr;
+}
+
+BorrowedAttrInfo
+nsGenericDOMDataNode::GetAttrInfoAt(uint32_t aIndex) const
+{
+ return BorrowedAttrInfo(nullptr, nullptr);
+}
+
+uint32_t
+nsGenericDOMDataNode::GetAttrCount() const
+{
+ return 0;
+}
+
+uint32_t
+nsGenericDOMDataNode::GetChildCount() const
+{
+ return 0;
+}
+
+nsIContent *
+nsGenericDOMDataNode::GetChildAt(uint32_t aIndex) const
+{
+ return nullptr;
+}
+
+nsIContent * const *
+nsGenericDOMDataNode::GetChildArray(uint32_t* aChildCount) const
+{
+ *aChildCount = 0;
+ return nullptr;
+}
+
+int32_t
+nsGenericDOMDataNode::IndexOf(const nsINode* aPossibleChild) const
+{
+ return -1;
+}
+
+nsresult
+nsGenericDOMDataNode::InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify)
+{
+ return NS_OK;
+}
+
+void
+nsGenericDOMDataNode::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+}
+
+nsIContent *
+nsGenericDOMDataNode::GetBindingParent() const
+{
+ nsDataSlots *slots = GetExistingDataSlots();
+ return slots ? slots->mBindingParent : nullptr;
+}
+
+ShadowRoot *
+nsGenericDOMDataNode::GetContainingShadow() const
+{
+ nsDataSlots *slots = GetExistingDataSlots();
+ if (!slots) {
+ return nullptr;
+ }
+ return slots->mContainingShadow;
+}
+
+void
+nsGenericDOMDataNode::SetShadowRoot(ShadowRoot* aShadowRoot)
+{
+}
+
+nsTArray<nsIContent*>&
+nsGenericDOMDataNode::DestInsertionPoints()
+{
+ nsDataSlots *slots = DataSlots();
+ return slots->mDestInsertionPoints;
+}
+
+nsTArray<nsIContent*>*
+nsGenericDOMDataNode::GetExistingDestInsertionPoints() const
+{
+ nsDataSlots *slots = GetExistingDataSlots();
+ if (slots) {
+ return &slots->mDestInsertionPoints;
+ }
+ return nullptr;
+}
+
+nsXBLBinding *
+nsGenericDOMDataNode::GetXBLBinding() const
+{
+ return nullptr;
+}
+
+void
+nsGenericDOMDataNode::SetXBLBinding(nsXBLBinding* aBinding,
+ nsBindingManager* aOldBindingManager)
+{
+}
+
+nsIContent *
+nsGenericDOMDataNode::GetXBLInsertionParent() const
+{
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsDataSlots *slots = GetExistingDataSlots();
+ if (slots) {
+ return slots->mXBLInsertionParent;
+ }
+ }
+
+ return nullptr;
+}
+
+void
+nsGenericDOMDataNode::SetXBLInsertionParent(nsIContent* aContent)
+{
+ if (aContent) {
+ nsDataSlots *slots = DataSlots();
+ SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
+ slots->mXBLInsertionParent = aContent;
+ } else {
+ nsDataSlots *slots = GetExistingDataSlots();
+ if (slots) {
+ slots->mXBLInsertionParent = nullptr;
+ }
+ }
+}
+
+CustomElementData *
+nsGenericDOMDataNode::GetCustomElementData() const
+{
+ return nullptr;
+}
+
+void
+nsGenericDOMDataNode::SetCustomElementData(CustomElementData* aData)
+{
+}
+
+bool
+nsGenericDOMDataNode::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~(eCONTENT | eDATA_NODE));
+}
+
+void
+nsGenericDOMDataNode::SaveSubtreeState()
+{
+}
+
+#ifdef DEBUG
+void
+nsGenericDOMDataNode::List(FILE* out, int32_t aIndent) const
+{
+}
+
+void
+nsGenericDOMDataNode::DumpContent(FILE* out, int32_t aIndent,
+ bool aDumpAll) const
+{
+}
+#endif
+
+bool
+nsGenericDOMDataNode::IsLink(nsIURI** aURI) const
+{
+ *aURI = nullptr;
+ return false;
+}
+
+nsINode::nsSlots*
+nsGenericDOMDataNode::CreateSlots()
+{
+ return new nsDataSlots();
+}
+
+nsGenericDOMDataNode::nsDataSlots::nsDataSlots()
+ : nsINode::nsSlots(), mBindingParent(nullptr)
+{
+}
+
+void
+nsGenericDOMDataNode::nsDataSlots::Traverse(nsCycleCollectionTraversalCallback &cb)
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLInsertionParent");
+ cb.NoteXPCOMChild(mXBLInsertionParent.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mContainingShadow");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow));
+}
+
+void
+nsGenericDOMDataNode::nsDataSlots::Unlink()
+{
+ mXBLInsertionParent = nullptr;
+ mContainingShadow = nullptr;
+}
+
+//----------------------------------------------------------------------
+
+// Implementation of the nsIDOMText interface
+
+nsresult
+nsGenericDOMDataNode::SplitData(uint32_t aOffset, nsIContent** aReturn,
+ bool aCloneAfterOriginal)
+{
+ *aReturn = nullptr;
+ nsresult rv = NS_OK;
+ nsAutoString cutText;
+ uint32_t length = TextLength();
+
+ if (aOffset > length) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ uint32_t cutStartOffset = aCloneAfterOriginal ? aOffset : 0;
+ uint32_t cutLength = aCloneAfterOriginal ? length - aOffset : aOffset;
+ rv = SubstringData(cutStartOffset, cutLength, cutText);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsIDocument* document = GetComposedDoc();
+ mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, true);
+
+ // Use Clone for creating the new node so that the new node is of same class
+ // as this node!
+ nsCOMPtr<nsIContent> newContent = CloneDataNode(mNodeInfo, false);
+ if (!newContent) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ newContent->SetText(cutText, true); // XXX should be false?
+
+ CharacterDataChangeInfo::Details details = {
+ CharacterDataChangeInfo::Details::eSplit, newContent
+ };
+ rv = SetTextInternal(cutStartOffset, cutLength, nullptr, 0, true,
+ aCloneAfterOriginal ? &details : nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsINode> parent = GetParentNode();
+ if (parent) {
+ int32_t insertionIndex = parent->IndexOf(this);
+ if (aCloneAfterOriginal) {
+ ++insertionIndex;
+ }
+ parent->InsertChildAt(newContent, insertionIndex, true);
+ }
+
+ newContent.swap(*aReturn);
+ return rv;
+}
+
+nsresult
+nsGenericDOMDataNode::SplitText(uint32_t aOffset, nsIDOMText** aReturn)
+{
+ nsCOMPtr<nsIContent> newChild;
+ nsresult rv = SplitData(aOffset, getter_AddRefs(newChild));
+ if (NS_SUCCEEDED(rv)) {
+ rv = CallQueryInterface(newChild, aReturn);
+ }
+ return rv;
+}
+
+/* static */ int32_t
+nsGenericDOMDataNode::FirstLogicallyAdjacentTextNode(nsIContent* aParent,
+ int32_t aIndex)
+{
+ while (aIndex-- > 0) {
+ nsIContent* sibling = aParent->GetChildAt(aIndex);
+ if (!sibling->IsNodeOfType(nsINode::eTEXT))
+ return aIndex + 1;
+ }
+ return 0;
+}
+
+/* static */ int32_t
+nsGenericDOMDataNode::LastLogicallyAdjacentTextNode(nsIContent* aParent,
+ int32_t aIndex,
+ uint32_t aCount)
+{
+ while (++aIndex < int32_t(aCount)) {
+ nsIContent* sibling = aParent->GetChildAt(aIndex);
+ if (!sibling->IsNodeOfType(nsINode::eTEXT))
+ return aIndex - 1;
+ }
+ return aCount - 1;
+}
+
+nsresult
+nsGenericDOMDataNode::GetWholeText(nsAString& aWholeText)
+{
+ nsIContent* parent = GetParent();
+
+ // Handle parent-less nodes
+ if (!parent)
+ return GetData(aWholeText);
+
+ int32_t index = parent->IndexOf(this);
+ NS_WARNING_ASSERTION(index >= 0,
+ "Trying to use .wholeText with an anonymous"
+ "text node child of a binding parent?");
+ NS_ENSURE_TRUE(index >= 0, NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ int32_t first =
+ FirstLogicallyAdjacentTextNode(parent, index);
+ int32_t last =
+ LastLogicallyAdjacentTextNode(parent, index, parent->GetChildCount());
+
+ aWholeText.Truncate();
+
+ nsCOMPtr<nsIDOMText> node;
+ nsAutoString tmp;
+ do {
+ node = do_QueryInterface(parent->GetChildAt(first));
+ node->GetData(tmp);
+ aWholeText.Append(tmp);
+ } while (first++ < last);
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+// Implementation of the nsIContent interface text functions
+
+const nsTextFragment *
+nsGenericDOMDataNode::GetText()
+{
+ return &mText;
+}
+
+uint32_t
+nsGenericDOMDataNode::TextLength() const
+{
+ return mText.GetLength();
+}
+
+nsresult
+nsGenericDOMDataNode::SetText(const char16_t* aBuffer,
+ uint32_t aLength,
+ bool aNotify)
+{
+ return SetTextInternal(0, mText.GetLength(), aBuffer, aLength, aNotify);
+}
+
+nsresult
+nsGenericDOMDataNode::AppendText(const char16_t* aBuffer,
+ uint32_t aLength,
+ bool aNotify)
+{
+ return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify);
+}
+
+bool
+nsGenericDOMDataNode::TextIsOnlyWhitespace()
+{
+ // 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.
+ 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;
+
+ if (!dom::IsSpaceCharacter(ch)) {
+ UnsetFlags(NS_TEXT_IS_ONLY_WHITESPACE);
+ SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE);
+ return false;
+ }
+
+ ++cp;
+ }
+
+ SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE);
+ return true;
+}
+
+bool
+nsGenericDOMDataNode::HasTextForTranslation()
+{
+ if (NodeType() != nsIDOMNode::TEXT_NODE &&
+ NodeType() != nsIDOMNode::CDATA_SECTION_NODE) {
+ return false;
+ }
+
+ 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;
+}
+
+void
+nsGenericDOMDataNode::AppendTextTo(nsAString& aResult)
+{
+ mText.AppendTo(aResult);
+}
+
+bool
+nsGenericDOMDataNode::AppendTextTo(nsAString& aResult,
+ const mozilla::fallible_t& aFallible)
+{
+ return mText.AppendTo(aResult, aFallible);
+}
+
+already_AddRefed<nsIAtom>
+nsGenericDOMDataNode::GetCurrentValueAtom()
+{
+ nsAutoString val;
+ GetData(val);
+ return NS_Atomize(val);
+}
+
+NS_IMETHODIMP
+nsGenericDOMDataNode::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsGenericDOMDataNode::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ return false;
+}
+
+nsChangeHint
+nsGenericDOMDataNode::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ NS_NOTREACHED("Shouldn't be calling this!");
+ return nsChangeHint(0);
+}
+
+size_t
+nsGenericDOMDataNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = nsIContent::SizeOfExcludingThis(aMallocSizeOf);
+ n += mText.SizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
diff --git a/dom/base/nsGenericDOMDataNode.h b/dom/base/nsGenericDOMDataNode.h
new file mode 100644
index 000000000..63aa64e74
--- /dev/null
+++ b/dom/base/nsGenericDOMDataNode.h
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMComment, nsIDOMDocumentType, nsIDOMText,
+ * nsIDOMCDATASection, and nsIDOMProcessingInstruction nodes.
+ */
+
+#ifndef nsGenericDOMDataNode_h___
+#define nsGenericDOMDataNode_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIContent.h"
+
+#include "nsTextFragment.h"
+#include "nsError.h"
+#include "mozilla/dom/Element.h"
+#include "nsCycleCollectionParticipant.h"
+
+#include "nsISMILAttr.h"
+#include "mozilla/dom/ShadowRoot.h"
+
+class nsIDocument;
+class nsIDOMText;
+
+#define DATA_NODE_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 = DATA_NODE_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 = DATA_NODE_FLAG_BIT(1),
+
+ // This bit is set to indicate that we have a cached
+ // TextIsOnlyWhitespace value
+ NS_CACHED_TEXT_IS_ONLY_WHITESPACE = DATA_NODE_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 = DATA_NODE_FLAG_BIT(3),
+};
+
+// Make sure we have enough space for those bits
+ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET + 4);
+
+#undef DATA_NODE_FLAG_BIT
+
+class nsGenericDOMDataNode : public nsIContent
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ NS_DECL_SIZEOF_EXCLUDING_THIS
+
+ explicit nsGenericDOMDataNode(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+ explicit nsGenericDOMDataNode(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
+
+ virtual void GetNodeValueInternal(nsAString& aNodeValue) override;
+ virtual void SetNodeValueInternal(const nsAString& aNodeValue,
+ mozilla::ErrorResult& aError) override;
+
+ // Implementation for nsIDOMCharacterData
+ nsresult GetData(nsAString& aData) const;
+ nsresult SetData(const nsAString& aData);
+ nsresult GetLength(uint32_t* aLength);
+ nsresult SubstringData(uint32_t aOffset, uint32_t aCount,
+ nsAString& aReturn);
+ nsresult AppendData(const nsAString& aArg);
+ nsresult InsertData(uint32_t aOffset, const nsAString& aArg);
+ nsresult DeleteData(uint32_t aOffset, uint32_t aCount);
+ nsresult ReplaceData(uint32_t aOffset, uint32_t aCount,
+ const nsAString& aArg);
+ NS_IMETHOD MozRemove();
+
+ // nsINode methods
+ virtual uint32_t GetChildCount() const override;
+ virtual nsIContent *GetChildAt(uint32_t aIndex) const override;
+ virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const override;
+ virtual int32_t IndexOf(const nsINode* aPossibleChild) const override;
+ virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify) override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+ virtual void GetTextContentInternal(nsAString& aTextContent,
+ mozilla::ErrorResult& aError) override
+ {
+ GetNodeValue(aTextContent);
+ }
+ virtual void SetTextContentInternal(const nsAString& aTextContent,
+ mozilla::ErrorResult& aError) override
+ {
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(OwnerDoc(), nullptr);
+ return SetNodeValue(aTextContent, aError);
+ }
+
+ // Implementation for nsIContent
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ virtual already_AddRefed<nsINodeList> GetChildren(uint32_t aFilter) override;
+
+
+ nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAString& aValue, bool aNotify)
+ {
+ return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
+ }
+ virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) override;
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+ virtual const nsAttrName* GetAttrNameAt(uint32_t aIndex) const override;
+ virtual mozilla::dom::BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const override;
+ virtual uint32_t GetAttrCount() const override;
+ virtual const nsTextFragment *GetText() override;
+ virtual uint32_t TextLength() const override;
+ virtual nsresult SetText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify) override;
+ // Need to implement this here too to avoid hiding.
+ nsresult SetText(const nsAString& aStr, bool aNotify)
+ {
+ return SetText(aStr.BeginReading(), aStr.Length(), aNotify);
+ }
+ virtual nsresult AppendText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify) override;
+ virtual bool TextIsOnlyWhitespace() override;
+ virtual bool HasTextForTranslation() override;
+ virtual void AppendTextTo(nsAString& aResult) override;
+ MOZ_MUST_USE
+ virtual bool AppendTextTo(nsAString& aResult,
+ const mozilla::fallible_t&) override;
+ virtual void SaveSubtreeState() override;
+
+#ifdef DEBUG
+ virtual void List(FILE* out, int32_t aIndent) const override;
+ virtual void DumpContent(FILE* out, int32_t aIndent, bool aDumpAll) const override;
+#endif
+
+ virtual nsIContent *GetBindingParent() const override;
+ virtual nsXBLBinding *GetXBLBinding() const override;
+ virtual void SetXBLBinding(nsXBLBinding* aBinding,
+ nsBindingManager* aOldBindingManager = nullptr) override;
+ virtual mozilla::dom::ShadowRoot *GetContainingShadow() const override;
+ virtual nsTArray<nsIContent*> &DestInsertionPoints() override;
+ virtual nsTArray<nsIContent*> *GetExistingDestInsertionPoints() const override;
+ virtual void SetShadowRoot(mozilla::dom::ShadowRoot* aShadowRoot) override;
+ virtual nsIContent *GetXBLInsertionParent() const override;
+ virtual void SetXBLInsertionParent(nsIContent* aContent) override;
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+ virtual bool IsLink(nsIURI** aURI) const override;
+
+ virtual mozilla::dom::CustomElementData* GetCustomElementData() const override;
+ virtual void SetCustomElementData(mozilla::dom::CustomElementData* aData) override;
+
+ NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const;
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const;
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override
+ {
+ nsCOMPtr<nsINode> result = CloneDataNode(aNodeInfo, true);
+ result.forget(aResult);
+
+ if (!*aResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+ }
+
+ nsresult SplitData(uint32_t aOffset, nsIContent** aReturn,
+ bool aCloneAfterOriginal = true);
+
+ // WebIDL API
+ // Our XPCOM GetData is just fine for WebIDL
+ virtual void SetData(const nsAString& aData, mozilla::ErrorResult& rv)
+ {
+ rv = SetData(aData);
+ }
+ // nsINode::Length() returns the right thing for our length attribute
+ void SubstringData(uint32_t aStart, uint32_t aCount, nsAString& aReturn,
+ mozilla::ErrorResult& rv);
+ void AppendData(const nsAString& aData, mozilla::ErrorResult& rv)
+ {
+ rv = AppendData(aData);
+ }
+ void InsertData(uint32_t aOffset, const nsAString& aData,
+ mozilla::ErrorResult& rv)
+ {
+ rv = InsertData(aOffset, aData);
+ }
+ void DeleteData(uint32_t aOffset, uint32_t aCount, mozilla::ErrorResult& rv)
+ {
+ rv = DeleteData(aOffset, aCount);
+ }
+ void ReplaceData(uint32_t aOffset, uint32_t aCount, const nsAString& aData,
+ mozilla::ErrorResult& rv)
+ {
+ rv = ReplaceData(aOffset, aCount, aData);
+ }
+
+ //----------------------------------------
+
+#ifdef DEBUG
+ void ToCString(nsAString& aBuf, int32_t aOffset, int32_t aLen) const;
+#endif
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(nsGenericDOMDataNode)
+
+protected:
+ virtual ~nsGenericDOMDataNode();
+
+ virtual mozilla::dom::Element* GetNameSpaceElement() override
+ {
+ nsINode *parent = GetParentNode();
+
+ return parent && parent->IsElement() ? parent->AsElement() : nullptr;
+ }
+
+ /**
+ * 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 nsDataSlots : public nsINode::nsSlots
+ {
+ public:
+ nsDataSlots();
+
+ void Traverse(nsCycleCollectionTraversalCallback &cb);
+ void Unlink();
+
+ /**
+ * The nearest enclosing content node with a binding that created us.
+ * @see nsIContent::GetBindingParent
+ */
+ nsIContent* mBindingParent; // [Weak]
+
+ /**
+ * @see nsIContent::GetXBLInsertionParent
+ */
+ nsCOMPtr<nsIContent> mXBLInsertionParent;
+
+ /**
+ * @see nsIContent::GetContainingShadow
+ */
+ RefPtr<mozilla::dom::ShadowRoot> mContainingShadow;
+
+ /**
+ * @see nsIContent::GetDestInsertionPoints
+ */
+ nsTArray<nsIContent*> mDestInsertionPoints;
+ };
+
+ // Override from nsINode
+ virtual nsINode::nsSlots* CreateSlots() override;
+
+ nsDataSlots* DataSlots()
+ {
+ return static_cast<nsDataSlots*>(Slots());
+ }
+
+ nsDataSlots *GetExistingDataSlots() const
+ {
+ return static_cast<nsDataSlots*>(GetExistingSlots());
+ }
+
+ nsresult SplitText(uint32_t aOffset, nsIDOMText** aReturn);
+
+ nsresult GetWholeText(nsAString& aWholeText);
+
+ static int32_t FirstLogicallyAdjacentTextNode(nsIContent* aParent,
+ int32_t aIndex);
+
+ static int32_t LastLogicallyAdjacentTextNode(nsIContent* aParent,
+ int32_t aIndex,
+ uint32_t aCount);
+
+ 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 nsGenericDOMDataNode *CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo,
+ bool aCloneText) const = 0;
+
+ nsTextFragment mText;
+
+public:
+ virtual bool OwnedOnlyByTheDOMTree() override
+ {
+ return GetParent() && mRefCnt.get() == 1;
+ }
+
+ virtual bool IsPurple() override
+ {
+ return mRefCnt.IsPurple();
+ }
+ virtual void RemovePurple() override
+ {
+ mRefCnt.RemovePurple();
+ }
+
+private:
+ already_AddRefed<nsIAtom> GetCurrentValueAtom();
+};
+
+#endif /* nsGenericDOMDataNode_h___ */
diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h
new file mode 100644
index 000000000..7827ad66b
--- /dev/null
+++ b/dom/base/nsGkAtomList.h
@@ -0,0 +1,2484 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsGkAtoms.h"
+
+/*
+ This file contains the list of all atoms used by gklayout;
+ see nsGkAtoms for access to the atoms.
+*/
+
+/*
+ This file is designed to be used as inline input to nsGkAtoms.cpp and nsGkAtoms.h
+ *only* through the magic of C preprocessing.
+
+ All entries must be enclosed in the macro GK_ATOM which will have cruel
+ and unusual things done to it
+
+ The first argument to GK_ATOM is the C++ name of the atom
+ The second argument it GK_ATOM is the string value of the atom
+*/
+
+// OUTPUT_CLASS=nsGkAtoms
+// MACRO_NAME=GK_ATOM
+#ifdef small
+#undef small
+#endif
+
+//---------------------------------------------------------------------------
+// Generic atoms
+//---------------------------------------------------------------------------
+
+GK_ATOM(_empty, "")
+GK_ATOM(moz, "_moz")
+GK_ATOM(mozframetype, "mozframetype")
+GK_ATOM(_moz_abspos, "_moz_abspos")
+GK_ATOM(_moz_activated, "_moz_activated")
+GK_ATOM(_moz_resizing, "_moz_resizing")
+GK_ATOM(mozallowfullscreen, "mozallowfullscreen")
+GK_ATOM(moztype, "_moz-type")
+GK_ATOM(mozdirty, "_moz_dirty")
+GK_ATOM(mozdisallowselectionprint, "mozdisallowselectionprint")
+GK_ATOM(mozdonotsend, "moz-do-not-send")
+GK_ATOM(mozeditorbogusnode, "_moz_editor_bogus_node")
+GK_ATOM(mozgeneratedcontentbefore, "_moz_generated_content_before")
+GK_ATOM(mozgeneratedcontentafter, "_moz_generated_content_after")
+GK_ATOM(mozgeneratedcontentimage, "_moz_generated_content_image")
+GK_ATOM(mozquote, "_moz_quote")
+GK_ATOM(mozsignature, "moz-signature")
+GK_ATOM(_moz_is_glyph, "-moz-is-glyph")
+GK_ATOM(_moz_original_size, "_moz_original_size")
+GK_ATOM(_moz_target, "_moz_target")
+GK_ATOM(menuactive, "_moz-menuactive")
+GK_ATOM(_poundDefault, "#default")
+GK_ATOM(_asterisk, "*")
+GK_ATOM(a, "a")
+GK_ATOM(abbr, "abbr")
+GK_ATOM(abort, "abort")
+GK_ATOM(above, "above")
+GK_ATOM(acceltext, "acceltext")
+GK_ATOM(accept, "accept")
+GK_ATOM(acceptcharset, "accept-charset")
+GK_ATOM(accesskey, "accesskey")
+GK_ATOM(acronym, "acronym")
+GK_ATOM(action, "action")
+GK_ATOM(active, "active")
+GK_ATOM(activetitlebarcolor, "activetitlebarcolor")
+GK_ATOM(activateontab, "activateontab")
+GK_ATOM(actuate, "actuate")
+GK_ATOM(address, "address")
+GK_ATOM(after, "after")
+GK_ATOM(after_end, "after_end")
+GK_ATOM(after_start, "after_start")
+GK_ATOM(align, "align")
+GK_ATOM(alink, "alink")
+GK_ATOM(all, "all")
+GK_ATOM(allowdirs, "allowdirs")
+GK_ATOM(allowevents, "allowevents")
+GK_ATOM(allownegativeassertions, "allownegativeassertions")
+GK_ATOM(allowforms,"allow-forms")
+GK_ATOM(allowfullscreen, "allowfullscreen")
+GK_ATOM(allowmodals, "allow-modals")
+GK_ATOM(alloworientationlock,"allow-orientation-lock")
+GK_ATOM(allowpointerlock,"allow-pointer-lock")
+GK_ATOM(allowpopupstoescapesandbox,"allow-popups-to-escape-sandbox")
+GK_ATOM(allowpopups,"allow-popups")
+GK_ATOM(allowpresentation,"allow-presentation")
+GK_ATOM(allowsameorigin,"allow-same-origin")
+GK_ATOM(allowscripts,"allow-scripts")
+GK_ATOM(allowtopnavigation,"allow-top-navigation")
+GK_ATOM(allowuntrusted, "allowuntrusted")
+GK_ATOM(alt, "alt")
+GK_ATOM(alternate, "alternate")
+GK_ATOM(always, "always")
+GK_ATOM(ancestor, "ancestor")
+GK_ATOM(ancestorOrSelf, "ancestor-or-self")
+GK_ATOM(anchor, "anchor")
+GK_ATOM(_and, "and")
+GK_ATOM(animations, "animations")
+GK_ATOM(anonid, "anonid")
+GK_ATOM(anonlocation, "anonlocation")
+GK_ATOM(any, "any")
+GK_ATOM(mozapp, "mozapp")
+GK_ATOM(applet, "applet")
+GK_ATOM(applyImports, "apply-imports")
+GK_ATOM(applyTemplates, "apply-templates")
+GK_ATOM(mozapptype, "mozapptype")
+GK_ATOM(archive, "archive")
+GK_ATOM(area, "area")
+GK_ATOM(arrow, "arrow")
+GK_ATOM(article, "article")
+GK_ATOM(ascending, "ascending")
+GK_ATOM(aside, "aside")
+GK_ATOM(aspectRatio, "aspect-ratio")
+GK_ATOM(assign, "assign")
+GK_ATOM(async, "async")
+GK_ATOM(attribute, "attribute")
+GK_ATOM(attributes, "attributes")
+GK_ATOM(attributeSet, "attribute-set")
+GK_ATOM(aural, "aural")
+GK_ATOM(_auto, "auto")
+GK_ATOM(autocheck, "autocheck")
+GK_ATOM(autocomplete, "autocomplete")
+GK_ATOM(autofocus, "autofocus")
+GK_ATOM(autoplay, "autoplay")
+GK_ATOM(autorepeatbutton, "autorepeatbutton")
+GK_ATOM(axis, "axis")
+GK_ATOM(b, "b")
+GK_ATOM(backdropFrame, "BackdropFrame")
+GK_ATOM(background, "background")
+GK_ATOM(base, "base")
+GK_ATOM(basefont, "basefont")
+GK_ATOM(baseline, "baseline")
+GK_ATOM(bdi, "bdi")
+GK_ATOM(bdo, "bdo")
+GK_ATOM(before, "before")
+GK_ATOM(before_end, "before_end")
+GK_ATOM(before_start, "before_start")
+GK_ATOM(below, "below")
+GK_ATOM(bgcolor, "bgcolor")
+GK_ATOM(bgsound, "bgsound")
+GK_ATOM(big, "big")
+GK_ATOM(binding, "binding")
+GK_ATOM(bindings, "bindings")
+GK_ATOM(bindToUntrustedContent, "bindToUntrustedContent")
+GK_ATOM(blankrow, "blankrow")
+GK_ATOM(block, "block")
+GK_ATOM(blockquote, "blockquote")
+GK_ATOM(blur, "blur")
+GK_ATOM(body, "body")
+GK_ATOM(boolean, "boolean")
+GK_ATOM(border, "border")
+GK_ATOM(bordercolor, "bordercolor")
+GK_ATOM(both, "both")
+GK_ATOM(bottom, "bottom")
+GK_ATOM(bottomend, "bottomend")
+GK_ATOM(bottomstart, "bottomstart")
+GK_ATOM(bottomleft, "bottomleft")
+GK_ATOM(bottommargin, "bottommargin")
+GK_ATOM(bottompadding, "bottompadding")
+GK_ATOM(bottomright, "bottomright")
+GK_ATOM(box, "box")
+GK_ATOM(br, "br")
+GK_ATOM(braille, "braille")
+GK_ATOM(broadcast, "broadcast")
+GK_ATOM(broadcaster, "broadcaster")
+GK_ATOM(broadcasterset, "broadcasterset")
+GK_ATOM(browser, "browser")
+GK_ATOM(mozbrowser, "mozbrowser")
+GK_ATOM(bulletinboard, "bulletinboard")
+GK_ATOM(button, "button")
+GK_ATOM(brighttitlebarforeground, "brighttitlebarforeground")
+GK_ATOM(callTemplate, "call-template")
+GK_ATOM(cancel, "cancel")
+GK_ATOM(canvas, "canvas")
+GK_ATOM(caption, "caption")
+GK_ATOM(capture, "capture")
+GK_ATOM(caseOrder, "case-order")
+GK_ATOM(cdataSectionElements, "cdata-section-elements")
+GK_ATOM(ceiling, "ceiling")
+GK_ATOM(cell, "cell")
+GK_ATOM(cellpadding, "cellpadding")
+GK_ATOM(cellspacing, "cellspacing")
+GK_ATOM(center, "center")
+GK_ATOM(ch, "ch")
+GK_ATOM(change, "change")
+GK_ATOM(_char, "char")
+GK_ATOM(characterData, "characterData")
+GK_ATOM(charcode, "charcode")
+GK_ATOM(charoff, "charoff")
+GK_ATOM(charset, "charset")
+GK_ATOM(checkbox, "checkbox")
+GK_ATOM(checked, "checked")
+GK_ATOM(child, "child")
+GK_ATOM(children, "children")
+GK_ATOM(childList, "childList")
+GK_ATOM(choose, "choose")
+GK_ATOM(chromemargin, "chromemargin")
+GK_ATOM(chromeOnlyContent, "chromeOnlyContent")
+GK_ATOM(exposeToUntrustedContent, "exposeToUntrustedContent")
+GK_ATOM(circ, "circ")
+GK_ATOM(circle, "circle")
+GK_ATOM(cite, "cite")
+GK_ATOM(_class, "class")
+GK_ATOM(classid, "classid")
+GK_ATOM(clear, "clear")
+GK_ATOM(click, "click")
+GK_ATOM(clickcount, "clickcount")
+GK_ATOM(clickthrough, "clickthrough")
+GK_ATOM(movetoclick, "movetoclick")
+GK_ATOM(clip, "clip")
+GK_ATOM(close, "close")
+GK_ATOM(closed, "closed")
+GK_ATOM(closemenu, "closemenu")
+GK_ATOM(coalesceduplicatearcs, "coalesceduplicatearcs")
+GK_ATOM(code, "code")
+GK_ATOM(codebase, "codebase")
+GK_ATOM(codetype, "codetype")
+GK_ATOM(col, "col")
+GK_ATOM(colgroup, "colgroup")
+GK_ATOM(collapse, "collapse")
+GK_ATOM(collapsed, "collapsed")
+GK_ATOM(color, "color")
+GK_ATOM(colorIndex, "color-index")
+GK_ATOM(cols, "cols")
+GK_ATOM(colspan, "colspan")
+GK_ATOM(column, "column")
+GK_ATOM(columns, "columns")
+GK_ATOM(combobox, "combobox")
+GK_ATOM(command, "command")
+GK_ATOM(commands, "commands")
+GK_ATOM(commandset, "commandset")
+GK_ATOM(commandupdate, "commandupdate")
+GK_ATOM(commandupdater, "commandupdater")
+GK_ATOM(comment, "comment")
+GK_ATOM(compact, "compact")
+GK_ATOM(concat, "concat")
+GK_ATOM(conditions, "conditions")
+GK_ATOM(constructor, "constructor")
+GK_ATOM(consumeoutsideclicks, "consumeoutsideclicks")
+GK_ATOM(container, "container")
+GK_ATOM(containment, "containment")
+GK_ATOM(contains, "contains")
+GK_ATOM(content, "content")
+GK_ATOM(contenteditable, "contenteditable")
+GK_ATOM(headerContentDisposition, "content-disposition")
+GK_ATOM(headerContentLanguage, "content-language")
+GK_ATOM(contentLocation, "content-location")
+GK_ATOM(headerContentScriptType, "content-script-type")
+GK_ATOM(headerContentStyleType, "content-style-type")
+GK_ATOM(headerContentType, "content-type")
+GK_ATOM(consumeanchor, "consumeanchor")
+GK_ATOM(context, "context")
+GK_ATOM(contextmenu, "contextmenu")
+GK_ATOM(control, "control")
+GK_ATOM(controls, "controls")
+GK_ATOM(coords, "coords")
+GK_ATOM(copy, "copy")
+GK_ATOM(copyOf, "copy-of")
+GK_ATOM(count, "count")
+GK_ATOM(crop, "crop")
+GK_ATOM(crossorigin, "crossorigin")
+GK_ATOM(curpos, "curpos")
+GK_ATOM(current, "current")
+GK_ATOM(cutoutregion, "cutoutregion")
+GK_ATOM(cycler, "cycler")
+GK_ATOM(data, "data")
+GK_ATOM(datalist, "datalist")
+GK_ATOM(dataType, "data-type")
+GK_ATOM(dateTime, "date-time")
+GK_ATOM(datasources, "datasources")
+GK_ATOM(datetime, "datetime")
+GK_ATOM(datetimebox, "datetimebox")
+GK_ATOM(dblclick, "dblclick")
+GK_ATOM(dd, "dd")
+GK_ATOM(debug, "debug")
+GK_ATOM(decimalFormat, "decimal-format")
+GK_ATOM(decimalSeparator, "decimal-separator")
+GK_ATOM(deck, "deck")
+GK_ATOM(declare, "declare")
+GK_ATOM(decoderDoctor, "decoder-doctor")
+GK_ATOM(decrement, "decrement")
+GK_ATOM(_default, "default")
+GK_ATOM(headerDefaultStyle, "default-style")
+GK_ATOM(defaultAction, "defaultAction")
+GK_ATOM(defaultchecked, "defaultchecked")
+GK_ATOM(defaultLabel, "defaultLabel")
+GK_ATOM(defaultselected, "defaultselected")
+GK_ATOM(defaultvalue, "defaultvalue")
+GK_ATOM(defaultplaybackrate, "defaultplaybackrate")
+GK_ATOM(defer, "defer")
+GK_ATOM(del, "del")
+GK_ATOM(descendant, "descendant")
+GK_ATOM(descendantOrSelf, "descendant-or-self")
+GK_ATOM(descending, "descending")
+GK_ATOM(description, "description")
+GK_ATOM(destructor, "destructor")
+GK_ATOM(details, "details")
+GK_ATOM(deviceAspectRatio, "device-aspect-ratio")
+GK_ATOM(deviceHeight, "device-height")
+GK_ATOM(devicePixelRatio, "device-pixel-ratio")
+GK_ATOM(deviceWidth, "device-width")
+GK_ATOM(dfn, "dfn")
+GK_ATOM(dialog, "dialog")
+GK_ATOM(difference, "difference")
+GK_ATOM(digit, "digit")
+GK_ATOM(dir, "dir")
+GK_ATOM(dirAutoSetBy, "dirAutoSetBy")
+GK_ATOM(directionality, "directionality")
+GK_ATOM(directory, "directory")
+GK_ATOM(disableOutputEscaping, "disable-output-escaping")
+GK_ATOM(disabled, "disabled")
+GK_ATOM(disableglobalhistory, "disableglobalhistory")
+GK_ATOM(disablehistory, "disablehistory")
+GK_ATOM(disablefullscreen, "disablefullscreen")
+GK_ATOM(display, "display")
+GK_ATOM(displayMode, "display-mode")
+GK_ATOM(distinct, "distinct")
+GK_ATOM(div, "div")
+GK_ATOM(dl, "dl")
+GK_ATOM(doctypePublic, "doctype-public")
+GK_ATOM(doctypeSystem, "doctype-system")
+GK_ATOM(document, "document")
+GK_ATOM(download, "download")
+GK_ATOM(DOMAttrModified, "DOMAttrModified")
+GK_ATOM(DOMCharacterDataModified, "DOMCharacterDataModified")
+GK_ATOM(DOMNodeInserted, "DOMNodeInserted")
+GK_ATOM(DOMNodeInsertedIntoDocument, "DOMNodeInsertedIntoDocument")
+GK_ATOM(DOMNodeRemoved, "DOMNodeRemoved")
+GK_ATOM(DOMNodeRemovedFromDocument, "DOMNodeRemovedFromDocument")
+GK_ATOM(DOMSubtreeModified, "DOMSubtreeModified")
+GK_ATOM(double_, "double")
+GK_ATOM(drag, "drag")
+GK_ATOM(dragend, "dragend")
+GK_ATOM(dragenter, "dragenter")
+GK_ATOM(dragevent, "dragevent")
+GK_ATOM(dragexit, "dragexit")
+GK_ATOM(draggable, "draggable")
+GK_ATOM(dragging, "dragging")
+GK_ATOM(dragleave, "dragleave")
+GK_ATOM(dragover, "dragover")
+GK_ATOM(dragSession, "dragSession")
+GK_ATOM(dragstart, "dragstart")
+GK_ATOM(drawintitlebar, "drawintitlebar")
+GK_ATOM(drawtitle, "drawtitle")
+GK_ATOM(drop, "drop")
+GK_ATOM(dropAfter, "dropAfter")
+GK_ATOM(dropBefore, "dropBefore")
+GK_ATOM(dropOn, "dropOn")
+GK_ATOM(dropMarker, "dropmarker")
+GK_ATOM(dt, "dt")
+GK_ATOM(editable, "editable")
+GK_ATOM(editing, "editing")
+GK_ATOM(editor, "editor")
+GK_ATOM(editorDisplayList, "EditorDisplay-List")
+GK_ATOM(element, "element")
+GK_ATOM(elementAvailable, "element-available")
+GK_ATOM(elements, "elements")
+GK_ATOM(em, "em")
+GK_ATOM(embed, "embed")
+GK_ATOM(embossed, "embossed")
+GK_ATOM(empty, "empty")
+GK_ATOM(encoding, "encoding")
+GK_ATOM(enctype, "enctype")
+GK_ATOM(end, "end")
+GK_ATOM(endEvent, "endEvent")
+GK_ATOM(end_after, "end_after")
+GK_ATOM(end_before, "end_before")
+GK_ATOM(equalsize, "equalsize")
+GK_ATOM(error, "error")
+GK_ATOM(even, "even")
+GK_ATOM(event, "event")
+GK_ATOM(events, "events")
+GK_ATOM(excludeResultPrefixes, "exclude-result-prefixes")
+GK_ATOM(excludes, "excludes")
+GK_ATOM(expr, "expr")
+GK_ATOM(extends, "extends")
+GK_ATOM(extensionElementPrefixes, "extension-element-prefixes")
+GK_ATOM(face, "face")
+GK_ATOM(fallback, "fallback")
+GK_ATOM(_false, "false")
+GK_ATOM(farthest, "farthest")
+GK_ATOM(field, "field")
+GK_ATOM(fieldset, "fieldset")
+GK_ATOM(figcaption, "figcaption")
+GK_ATOM(figure, "figure")
+GK_ATOM(fixed, "fixed")
+GK_ATOM(flags, "flags")
+GK_ATOM(flex, "flex")
+GK_ATOM(flexgroup, "flexgroup")
+GK_ATOM(flip, "flip")
+GK_ATOM(floating, "floating")
+GK_ATOM(floor, "floor")
+GK_ATOM(flowlength, "flowlength")
+GK_ATOM(focus, "focus")
+GK_ATOM(focused, "focused")
+GK_ATOM(following, "following")
+GK_ATOM(followingSibling, "following-sibling")
+GK_ATOM(font, "font")
+GK_ATOM(fontWeight, "font-weight")
+GK_ATOM(fontpicker, "fontpicker")
+GK_ATOM(footer, "footer")
+GK_ATOM(_for, "for")
+GK_ATOM(forEach, "for-each")
+GK_ATOM(forceOwnRefreshDriver, "forceOwnRefreshDriver")
+GK_ATOM(form, "form")
+GK_ATOM(formaction, "formaction")
+GK_ATOM(format, "format")
+GK_ATOM(formatNumber, "format-number")
+GK_ATOM(formenctype, "formenctype")
+GK_ATOM(formmethod, "formmethod")
+GK_ATOM(formnovalidate, "formnovalidate")
+GK_ATOM(formtarget, "formtarget")
+GK_ATOM(frame, "frame")
+GK_ATOM(frameborder, "frameborder")
+GK_ATOM(frameset, "frameset")
+GK_ATOM(from, "from")
+GK_ATOM(fullscreenchange, "fullscreenchange")
+GK_ATOM(fullscreenerror, "fullscreenerror")
+GK_ATOM(functionAvailable, "function-available")
+GK_ATOM(freshProcess, "freshProcess")
+GK_ATOM(generateId, "generate-id")
+GK_ATOM(getter, "getter")
+GK_ATOM(glyphchar, "glyphchar")
+GK_ATOM(glyphid, "glyphid")
+GK_ATOM(grid, "grid")
+GK_ATOM(grippy, "grippy")
+GK_ATOM(group, "group")
+GK_ATOM(groupingSeparator, "grouping-separator")
+GK_ATOM(groupingSize, "grouping-size")
+GK_ATOM(grow, "grow")
+GK_ATOM(gutter, "gutter")
+GK_ATOM(h1, "h1")
+GK_ATOM(h2, "h2")
+GK_ATOM(h3, "h3")
+GK_ATOM(h4, "h4")
+GK_ATOM(h5, "h5")
+GK_ATOM(h6, "h6")
+GK_ATOM(handheld, "handheld")
+GK_ATOM(handheldFriendly, "HandheldFriendly")
+GK_ATOM(handler, "handler")
+GK_ATOM(handlers, "handlers")
+GK_ATOM(HARD, "HARD")
+GK_ATOM(hasSameNode, "has-same-node")
+GK_ATOM(hbox, "hbox")
+GK_ATOM(head, "head")
+GK_ATOM(header, "header")
+GK_ATOM(headers, "headers")
+GK_ATOM(height, "height")
+GK_ATOM(hgroup, "hgroup")
+GK_ATOM(hidden, "hidden")
+GK_ATOM(hidechrome, "hidechrome")
+GK_ATOM(hidecolumnpicker, "hidecolumnpicker")
+GK_ATOM(high, "high")
+GK_ATOM(highest, "highest")
+GK_ATOM(horizontal, "horizontal")
+GK_ATOM(hover, "hover")
+GK_ATOM(hr, "hr")
+GK_ATOM(href, "href")
+GK_ATOM(hreflang, "hreflang")
+GK_ATOM(hspace, "hspace")
+GK_ATOM(html, "html")
+GK_ATOM(httpEquiv, "http-equiv")
+GK_ATOM(i, "i")
+GK_ATOM(icon, "icon")
+GK_ATOM(id, "id")
+GK_ATOM(_if, "if")
+GK_ATOM(iframe, "iframe")
+GK_ATOM(ignorecase, "ignorecase")
+GK_ATOM(ignorekeys, "ignorekeys")
+GK_ATOM(ignoreuserfocus, "ignoreuserfocus")
+GK_ATOM(ilayer, "ilayer")
+GK_ATOM(image, "image")
+GK_ATOM(imageClickedPoint, "image-clicked-point")
+GK_ATOM(img, "img")
+GK_ATOM(implementation, "implementation")
+GK_ATOM(implements, "implements")
+GK_ATOM(import, "import")
+GK_ATOM(inactivetitlebarcolor, "inactivetitlebarcolor")
+GK_ATOM(include, "include")
+GK_ATOM(includes, "includes")
+GK_ATOM(increment, "increment")
+GK_ATOM(indent, "indent")
+GK_ATOM(indeterminate, "indeterminate")
+GK_ATOM(index, "index")
+GK_ATOM(infer, "infer")
+GK_ATOM(infinity, "infinity")
+GK_ATOM(inherit, "inherit")
+GK_ATOM(inherits, "inherits")
+GK_ATOM(inheritstyle, "inheritstyle")
+GK_ATOM(initial_scale, "initial-scale")
+GK_ATOM(input, "input")
+GK_ATOM(inputmode, "inputmode")
+GK_ATOM(ins, "ins")
+GK_ATOM(insertafter, "insertafter")
+GK_ATOM(insertbefore, "insertbefore")
+GK_ATOM(install, "install")
+GK_ATOM(instanceOf, "instanceOf")
+GK_ATOM(int32, "int32")
+GK_ATOM(int64, "int64")
+GK_ATOM(integer, "integer")
+GK_ATOM(integrity, "integrity")
+GK_ATOM(intersection, "intersection")
+GK_ATOM(is, "is")
+GK_ATOM(iscontainer, "iscontainer")
+GK_ATOM(isempty, "isempty")
+GK_ATOM(ismap, "ismap")
+GK_ATOM(itemid, "itemid")
+GK_ATOM(itemprop, "itemprop")
+GK_ATOM(itemref, "itemref")
+GK_ATOM(itemscope, "itemscope")
+GK_ATOM(itemtype, "itemtype")
+GK_ATOM(kbd, "kbd")
+GK_ATOM(keepcurrentinview, "keepcurrentinview")
+GK_ATOM(keepobjectsalive, "keepobjectsalive")
+GK_ATOM(key, "key")
+GK_ATOM(keycode, "keycode")
+GK_ATOM(keystatuseschange, "keystatuseschange")
+GK_ATOM(keydown, "keydown")
+GK_ATOM(keygen, "keygen")
+GK_ATOM(keypress, "keypress")
+GK_ATOM(keyset, "keyset")
+GK_ATOM(keysystem, "keysystem")
+GK_ATOM(keytext, "keytext")
+GK_ATOM(keyup, "keyup")
+GK_ATOM(kind, "kind")
+GK_ATOM(label, "label")
+GK_ATOM(lang, "lang")
+GK_ATOM(language, "language")
+GK_ATOM(last, "last")
+GK_ATOM(layer, "layer")
+GK_ATOM(LayerActivity, "LayerActivity")
+GK_ATOM(layout, "layout")
+GK_ATOM(leading, "leading")
+GK_ATOM(leaf, "leaf")
+GK_ATOM(left, "left")
+GK_ATOM(leftmargin, "leftmargin")
+GK_ATOM(leftpadding, "leftpadding")
+GK_ATOM(legend, "legend")
+GK_ATOM(length, "length")
+GK_ATOM(letterValue, "letter-value")
+GK_ATOM(level, "level")
+GK_ATOM(li, "li")
+GK_ATOM(line, "line")
+GK_ATOM(link, "link")
+GK_ATOM(list, "list")
+GK_ATOM(listbox, "listbox")
+GK_ATOM(listboxbody, "listboxbody")
+GK_ATOM(listcell, "listcell")
+GK_ATOM(listcol, "listcol")
+GK_ATOM(listcols, "listcols")
+GK_ATOM(listener, "listener")
+GK_ATOM(listhead, "listhead")
+GK_ATOM(listheader, "listheader")
+GK_ATOM(listing, "listing")
+GK_ATOM(listitem, "listitem")
+GK_ATOM(listrows, "listrows")
+GK_ATOM(load, "load")
+GK_ATOM(loadingprincipal, "loadingprincipal")
+GK_ATOM(localedir, "localedir")
+GK_ATOM(localName, "local-name")
+GK_ATOM(longdesc, "longdesc")
+GK_ATOM(loop, "loop")
+GK_ATOM(low, "low")
+GK_ATOM(lowerFirst, "lower-first")
+GK_ATOM(lowest, "lowest")
+GK_ATOM(lowsrc, "lowsrc")
+GK_ATOM(ltr, "ltr")
+GK_ATOM(lwtheme, "lwtheme")
+GK_ATOM(lwthemetextcolor, "lwthemetextcolor")
+GK_ATOM(main, "main")
+GK_ATOM(map, "map")
+GK_ATOM(manifest, "manifest")
+GK_ATOM(marginBottom, "margin-bottom")
+GK_ATOM(marginLeft, "margin-left")
+GK_ATOM(marginRight, "margin-right")
+GK_ATOM(marginTop, "margin-top")
+GK_ATOM(marginheight, "marginheight")
+GK_ATOM(marginwidth, "marginwidth")
+GK_ATOM(mark, "mark")
+GK_ATOM(marquee, "marquee")
+GK_ATOM(match, "match")
+GK_ATOM(max, "max")
+GK_ATOM(maxheight, "maxheight")
+GK_ATOM(maximum_scale, "maximum-scale")
+GK_ATOM(maxlength, "maxlength")
+GK_ATOM(maxpos, "maxpos")
+GK_ATOM(maxwidth, "maxwidth")
+GK_ATOM(mayscript, "mayscript")
+GK_ATOM(media, "media")
+GK_ATOM(mediaType, "media-type")
+GK_ATOM(member, "member")
+GK_ATOM(menu, "menu")
+GK_ATOM(menubar, "menubar")
+GK_ATOM(menubutton, "menubutton")
+GK_ATOM(menuButton, "menu-button")
+GK_ATOM(menugroup, "menugroup")
+GK_ATOM(menuitem, "menuitem")
+GK_ATOM(menulist, "menulist")
+GK_ATOM(menupopup, "menupopup")
+GK_ATOM(menuseparator, "menuseparator")
+GK_ATOM(message, "message")
+GK_ATOM(meta, "meta")
+GK_ATOM(referrer, "referrer")
+GK_ATOM(referrerpolicy, "referrerpolicy")
+GK_ATOM(headerReferrerPolicy, "referrer-policy")
+GK_ATOM(meter, "meter")
+GK_ATOM(method, "method")
+GK_ATOM(middle, "middle")
+GK_ATOM(min, "min")
+GK_ATOM(minheight, "minheight")
+GK_ATOM(minimum_scale, "minimum-scale")
+GK_ATOM(minlength, "minlength")
+GK_ATOM(minpos, "minpos")
+GK_ATOM(minusSign, "minus-sign")
+GK_ATOM(minwidth, "minwidth")
+GK_ATOM(_mixed, "mixed")
+GK_ATOM(messagemanagergroup, "messagemanagergroup")
+GK_ATOM(mod, "mod")
+GK_ATOM(mode, "mode")
+GK_ATOM(modifiers, "modifiers")
+GK_ATOM(monochrome, "monochrome")
+GK_ATOM(mousedown, "mousedown")
+GK_ATOM(mousemove, "mousemove")
+GK_ATOM(mouseout, "mouseout")
+GK_ATOM(mouseover, "mouseover")
+GK_ATOM(mousethrough, "mousethrough")
+GK_ATOM(mouseup, "mouseup")
+GK_ATOM(mozaudiochannel, "mozaudiochannel")
+GK_ATOM(mozfullscreenchange, "mozfullscreenchange")
+GK_ATOM(mozfullscreenerror, "mozfullscreenerror")
+GK_ATOM(mozpointerlockchange, "mozpointerlockchange")
+GK_ATOM(mozpointerlockerror, "mozpointerlockerror")
+GK_ATOM(mozprivatebrowsing, "mozprivatebrowsing")
+GK_ATOM(moz_opaque, "moz-opaque")
+GK_ATOM(moz_action_hint, "mozactionhint")
+GK_ATOM(x_moz_errormessage, "x-moz-errormessage")
+GK_ATOM(msthemecompatible, "msthemecompatible")
+GK_ATOM(multicol, "multicol")
+GK_ATOM(multiple, "multiple")
+GK_ATOM(muted, "muted")
+GK_ATOM(name, "name")
+GK_ATOM(_namespace, "namespace")
+GK_ATOM(namespaceAlias, "namespace-alias")
+GK_ATOM(namespaceUri, "namespace-uri")
+GK_ATOM(NaN, "NaN")
+GK_ATOM(nativeAnonymousChildList, "nativeAnonymousChildList")
+GK_ATOM(nav, "nav")
+GK_ATOM(negate, "negate")
+GK_ATOM(never, "never")
+GK_ATOM(_new, "new")
+GK_ATOM(newline, "newline")
+GK_ATOM(nextBidi, "NextBidi")
+GK_ATOM(no, "no")
+GK_ATOM(noautofocus, "noautofocus")
+GK_ATOM(noautohide, "noautohide")
+GK_ATOM(norolluponanchor, "norolluponanchor")
+GK_ATOM(nobr, "nobr")
+GK_ATOM(node, "node")
+GK_ATOM(nodefaultsrc, "nodefaultsrc")
+GK_ATOM(nodeSet, "node-set")
+GK_ATOM(noembed, "noembed")
+GK_ATOM(noframes, "noframes")
+GK_ATOM(nohref, "nohref")
+GK_ATOM(noisolation, "noisolation")
+GK_ATOM(nonce, "nonce")
+GK_ATOM(none, "none")
+GK_ATOM(noresize, "noresize")
+GK_ATOM(normal, "normal")
+GK_ATOM(normalizeSpace, "normalize-space")
+GK_ATOM(noscript, "noscript")
+GK_ATOM(noshade, "noshade")
+GK_ATOM(novalidate, "novalidate")
+GK_ATOM(_not, "not")
+GK_ATOM(nowrap, "nowrap")
+GK_ATOM(number, "number")
+GK_ATOM(null, "null")
+GK_ATOM(object, "object")
+GK_ATOM(objectType, "object-type")
+GK_ATOM(observer, "observer")
+GK_ATOM(observes, "observes")
+GK_ATOM(odd, "odd")
+GK_ATOM(OFF, "OFF")
+GK_ATOM(ol, "ol")
+GK_ATOM(omitXmlDeclaration, "omit-xml-declaration")
+GK_ATOM(ona2dpstatuschanged, "ona2dpstatuschanged")
+GK_ATOM(onabort, "onabort")
+GK_ATOM(onmozaccesskeynotfound, "onmozaccesskeynotfound")
+GK_ATOM(onactivate, "onactivate")
+GK_ATOM(onadapteradded, "onadapteradded")
+GK_ATOM(onadapterremoved, "onadapterremoved")
+GK_ATOM(onafterprint, "onafterprint")
+GK_ATOM(onafterscriptexecute, "onafterscriptexecute")
+GK_ATOM(onalerting, "onalerting")
+GK_ATOM(onanimationend, "onanimationend")
+GK_ATOM(onanimationiteration, "onanimationiteration")
+GK_ATOM(onanimationstart, "onanimationstart")
+GK_ATOM(onantennaavailablechange, "onantennaavailablechange")
+GK_ATOM(onAppCommand, "onAppCommand")
+GK_ATOM(onappinstalled, "onappinstalled")
+GK_ATOM(onattributechanged, "onattributechanged")
+GK_ATOM(onattributereadreq, "onattributereadreq")
+GK_ATOM(onattributewritereq, "onattributewritereq")
+GK_ATOM(onaudioprocess, "onaudioprocess")
+GK_ATOM(onbeforecopy, "onbeforecopy")
+GK_ATOM(onbeforecut, "onbeforecut")
+GK_ATOM(onbeforepaste, "onbeforepaste")
+GK_ATOM(onbeforeevicted, "onbeforeevicted")
+GK_ATOM(onbeforeprint, "onbeforeprint")
+GK_ATOM(onbeforescriptexecute, "onbeforescriptexecute")
+GK_ATOM(onbeforeunload, "onbeforeunload")
+GK_ATOM(onblocked, "onblocked")
+GK_ATOM(onblur, "onblur")
+GK_ATOM(onbroadcast, "onbroadcast")
+GK_ATOM(onbusy, "onbusy")
+GK_ATOM(onbufferedamountlow, "onbufferedamountlow")
+GK_ATOM(oncached, "oncached")
+GK_ATOM(oncallschanged, "oncallschanged")
+GK_ATOM(oncancel, "oncancel")
+GK_ATOM(oncardstatechange, "oncardstatechange")
+GK_ATOM(oncfstatechange, "oncfstatechange")
+GK_ATOM(onchange, "onchange")
+GK_ATOM(oncharacteristicchanged, "oncharacteristicchanged")
+GK_ATOM(onchargingchange, "onchargingchange")
+GK_ATOM(onchargingtimechange, "onchargingtimechange")
+GK_ATOM(onchecking, "onchecking")
+GK_ATOM(onclick, "onclick")
+GK_ATOM(onclirmodechange, "onclirmodechange")
+GK_ATOM(onclose, "onclose")
+GK_ATOM(oncommand, "oncommand")
+GK_ATOM(oncommandupdate, "oncommandupdate")
+GK_ATOM(oncomplete, "oncomplete")
+GK_ATOM(oncompositionend, "oncompositionend")
+GK_ATOM(oncompositionstart, "oncompositionstart")
+GK_ATOM(oncompositionupdate, "oncompositionupdate")
+GK_ATOM(onconnect, "onconnect")
+GK_ATOM(onconnected, "onconnected")
+GK_ATOM(onconnecting, "onconnecting")
+GK_ATOM(onconnectionavailable, "onconnectionavailable")
+GK_ATOM(onconnectionstatechanged, "onconnectionstatechanged")
+GK_ATOM(oncontextmenu, "oncontextmenu")
+GK_ATOM(oncopy, "oncopy")
+GK_ATOM(oncurrentchannelchanged, "oncurrentchannelchanged")
+GK_ATOM(oncurrentsourcechanged, "oncurrentsourcechanged")
+GK_ATOM(oncut, "oncut")
+GK_ATOM(ondatachange, "ondatachange")
+GK_ATOM(ondataerror, "ondataerror")
+GK_ATOM(ondblclick, "ondblclick")
+GK_ATOM(ondeleted, "ondeleted")
+GK_ATOM(ondeliverysuccess, "ondeliverysuccess")
+GK_ATOM(ondeliveryerror, "ondeliveryerror")
+GK_ATOM(ondevicefound, "ondevicefound")
+GK_ATOM(ondevicepaired, "ondevicepaired")
+GK_ATOM(ondeviceunpaired, "ondeviceunpaired")
+GK_ATOM(ondialing, "ondialing")
+GK_ATOM(ondisabled, "ondisabled")
+GK_ATOM(ondischargingtimechange, "ondischargingtimechange")
+GK_ATOM(ondisconnect, "ondisconnect")
+GK_ATOM(ondisconnected, "ondisconnected")
+GK_ATOM(ondisconnecting, "ondisconnecting")
+GK_ATOM(ondisplaypasskeyreq, "ondisplaypasskeyreq")
+GK_ATOM(ondownloading, "ondownloading")
+GK_ATOM(onDOMActivate, "onDOMActivate")
+GK_ATOM(onDOMAttrModified, "onDOMAttrModified")
+GK_ATOM(onDOMCharacterDataModified, "onDOMCharacterDataModified")
+GK_ATOM(onDOMFocusIn, "onDOMFocusIn")
+GK_ATOM(onDOMFocusOut, "onDOMFocusOut")
+GK_ATOM(onDOMMouseScroll, "onDOMMouseScroll")
+GK_ATOM(onDOMNodeInserted, "onDOMNodeInserted")
+GK_ATOM(onDOMNodeInsertedIntoDocument, "onDOMNodeInsertedIntoDocument")
+GK_ATOM(onDOMNodeRemoved, "onDOMNodeRemoved")
+GK_ATOM(onDOMNodeRemovedFromDocument, "onDOMNodeRemovedFromDocument")
+GK_ATOM(onDOMSubtreeModified, "onDOMSubtreeModified")
+GK_ATOM(ondata, "ondata")
+GK_ATOM(ondrag, "ondrag")
+GK_ATOM(ondragdrop, "ondragdrop")
+GK_ATOM(ondragend, "ondragend")
+GK_ATOM(ondragenter, "ondragenter")
+GK_ATOM(ondragexit, "ondragexit")
+GK_ATOM(ondraggesture, "ondraggesture")
+GK_ATOM(ondragleave, "ondragleave")
+GK_ATOM(ondragover, "ondragover")
+GK_ATOM(ondragstart, "ondragstart")
+GK_ATOM(ondrain, "ondrain")
+GK_ATOM(ondrop, "ondrop")
+GK_ATOM(oneitbroadcasted, "oneitbroadcasted")
+GK_ATOM(onenabled, "onenabled")
+GK_ATOM(onenterpincodereq, "onenterpincodereq")
+GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
+GK_ATOM(onerror, "onerror")
+GK_ATOM(onevicted, "onevicted")
+GK_ATOM(onfailed, "onfailed")
+GK_ATOM(onfetch, "onfetch")
+GK_ATOM(onfinish, "onfinish")
+GK_ATOM(onfocus, "onfocus")
+GK_ATOM(onfocusin, "onfocusin")
+GK_ATOM(onfocusout, "onfocusout")
+GK_ATOM(onfrequencychange, "onfrequencychange")
+GK_ATOM(onfullscreenchange, "onfullscreenchange")
+GK_ATOM(onfullscreenerror, "onfullscreenerror")
+GK_ATOM(onspeakerforcedchange, "onspeakerforcedchange")
+GK_ATOM(onget, "onget")
+GK_ATOM(ongroupchange, "ongroupchange")
+GK_ATOM(onhashchange, "onhashchange")
+GK_ATOM(onheadphoneschange, "onheadphoneschange")
+GK_ATOM(onheld, "onheld")
+GK_ATOM(onhfpstatuschanged, "onhfpstatuschanged")
+GK_ATOM(onhidstatuschanged, "onhidstatuschanged")
+GK_ATOM(onholding, "onholding")
+GK_ATOM(oniccchange, "oniccchange")
+GK_ATOM(oniccdetected, "oniccdetected")
+GK_ATOM(oniccinfochange, "oniccinfochange")
+GK_ATOM(oniccundetected, "oniccundetected")
+GK_ATOM(onincoming, "onincoming")
+GK_ATOM(oninput, "oninput")
+GK_ATOM(oninstall, "oninstall")
+GK_ATOM(oninvalid, "oninvalid")
+GK_ATOM(onkeydown, "onkeydown")
+GK_ATOM(onkeypress, "onkeypress")
+GK_ATOM(onkeyup, "onkeyup")
+GK_ATOM(onlanguagechange, "onlanguagechange")
+GK_ATOM(onlevelchange, "onlevelchange")
+GK_ATOM(onLoad, "onLoad")
+GK_ATOM(onload, "onload")
+GK_ATOM(onloading, "onloading")
+GK_ATOM(onloadingdone, "onloadingdone")
+GK_ATOM(onloadingerror, "onloadingerror")
+GK_ATOM(onpopstate, "onpopstate")
+GK_ATOM(only, "only") // this one is not an event
+GK_ATOM(onmessage, "onmessage")
+GK_ATOM(onmousedown, "onmousedown")
+GK_ATOM(onmouseenter, "onmouseenter")
+GK_ATOM(onmouseleave, "onmouseleave")
+GK_ATOM(onmouselongtap, "onmouselongtap")
+GK_ATOM(onmousemove, "onmousemove")
+GK_ATOM(onmouseout, "onmouseout")
+GK_ATOM(onmouseover, "onmouseover")
+GK_ATOM(onMozMouseHittest, "onMozMouseHittest")
+GK_ATOM(onmouseup, "onmouseup")
+GK_ATOM(onMozAfterPaint, "onMozAfterPaint")
+GK_ATOM(onmozbrowserafterkeydown, "onmozbrowserafterkeydown")
+GK_ATOM(onmozbrowserafterkeyup, "onmozbrowserafterkeyup")
+GK_ATOM(onmozbrowserbeforekeydown, "onmozbrowserbeforekeydown")
+GK_ATOM(onmozbrowserbeforekeyup, "onmozbrowserbeforekeyup")
+GK_ATOM(onmozfullscreenchange, "onmozfullscreenchange")
+GK_ATOM(onmozfullscreenerror, "onmozfullscreenerror")
+GK_ATOM(onmozkeydownonplugin, "onmozkeydownonplugin")
+GK_ATOM(onmozkeyuponplugin, "onmozkeyuponplugin")
+GK_ATOM(onmozpointerlockchange, "onmozpointerlockchange")
+GK_ATOM(onmozpointerlockerror, "onmozpointerlockerror")
+GK_ATOM(onmoztimechange, "onmoztimechange")
+GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
+GK_ATOM(onMozScrolledAreaChanged, "onMozScrolledAreaChanged")
+GK_ATOM(onmoznetworkupload, "onmoznetworkupload")
+GK_ATOM(onmoznetworkdownload, "onmoznetworkdownload")
+GK_ATOM(onmapfolderlistingreq, "onmapfolderlistingreq")
+GK_ATOM(onmapmessageslistingreq, "onmapmessageslistingreq")
+GK_ATOM(onmapgetmessagereq, "onmapgetmessagereq")
+GK_ATOM(onmapsetmessagestatusreq, "onmapsetmessagestatusreq")
+GK_ATOM(onmapsendmessagereq, "onmapsendmessagereq")
+GK_ATOM(onmapmessageupdatereq, "onmapmessageupdatereq")
+GK_ATOM(onnewrdsgroup, "onnewrdsgroup")
+GK_ATOM(onnotificationclick, "onnotificationclick")
+GK_ATOM(onnotificationclose, "onnotificationclose")
+GK_ATOM(onnoupdate, "onnoupdate")
+GK_ATOM(onobexpasswordreq, "onobexpasswordreq")
+GK_ATOM(onobsolete, "onobsolete")
+GK_ATOM(ononline, "ononline")
+GK_ATOM(onoffline, "onoffline")
+GK_ATOM(onopen, "onopen")
+GK_ATOM(onorientationchange, "onorientationchange")
+GK_ATOM(onotastatuschange, "onotastatuschange")
+GK_ATOM(onoverflow, "onoverflow")
+GK_ATOM(onoverflowchanged, "onoverflowchanged")
+GK_ATOM(onpagehide, "onpagehide")
+GK_ATOM(onpageshow, "onpageshow")
+GK_ATOM(onpaint, "onpaint")
+GK_ATOM(onpairingaborted, "onpairingaborted")
+GK_ATOM(onpairingconfirmationreq, "onpairingconfirmationreq")
+GK_ATOM(onpairingconsentreq, "onpairingconsentreq")
+GK_ATOM(onpaste, "onpaste")
+GK_ATOM(onpendingchange, "onpendingchange")
+GK_ATOM(onpichange, "onpichange")
+GK_ATOM(onpointerlockchange, "onpointerlockchange")
+GK_ATOM(onpointerlockerror, "onpointerlockerror")
+GK_ATOM(onpopuphidden, "onpopuphidden")
+GK_ATOM(onpopuphiding, "onpopuphiding")
+GK_ATOM(onpopuppositioned, "onpopuppositioned")
+GK_ATOM(onpopupshowing, "onpopupshowing")
+GK_ATOM(onpopupshown, "onpopupshown")
+GK_ATOM(onpullphonebookreq, "onpullphonebookreq")
+GK_ATOM(onpullvcardentryreq, "onpullvcardentryreq")
+GK_ATOM(onpullvcardlistingreq, "onpullvcardlistingreq")
+GK_ATOM(onpush, "onpush")
+GK_ATOM(onpushsubscriptionchange, "onpushsubscriptionchange")
+GK_ATOM(onpschange, "onpschange")
+GK_ATOM(onptychange, "onptychange")
+GK_ATOM(onradiostatechange, "onradiostatechange")
+GK_ATOM(onrdsdisabled, "onrdsdisabled")
+GK_ATOM(onrdsenabled, "onrdsenabled")
+GK_ATOM(onreaderror, "onreaderror")
+GK_ATOM(onreadsuccess, "onreadsuccess")
+GK_ATOM(onready, "onready")
+GK_ATOM(onreadystatechange, "onreadystatechange")
+GK_ATOM(onreceived, "onreceived")
+GK_ATOM(onremoteheld, "onremoteheld")
+GK_ATOM(onremoteresumed, "onremoteresumed")
+GK_ATOM(onresourcetimingbufferfull, "onresourcetimingbufferfull")
+GK_ATOM(onretrieving, "onretrieving")
+GK_ATOM(onRequest, "onRequest")
+GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")
+GK_ATOM(onreset, "onreset")
+GK_ATOM(onresuming, "onresuming")
+GK_ATOM(onresize, "onresize")
+GK_ATOM(onrtchange, "onrtchange")
+GK_ATOM(onscanningstatechanged, "onscanningstatechanged")
+GK_ATOM(onscostatuschanged, "onscostatuschanged")
+GK_ATOM(onscroll, "onscroll")
+GK_ATOM(onselect, "onselect")
+GK_ATOM(onselectionchange, "onselectionchange")
+GK_ATOM(onselectstart, "onselectstart")
+GK_ATOM(onsending, "onsending")
+GK_ATOM(onsent, "onsent")
+GK_ATOM(onset, "onset")
+GK_ATOM(onshow, "onshow")
+GK_ATOM(onstatechange, "onstatechange")
+GK_ATOM(onstatuschanged, "onstatuschanged")
+GK_ATOM(onstkcommand, "onstkcommand")
+GK_ATOM(onstksessionend, "onstksessionend")
+GK_ATOM(onstorage, "onstorage")
+GK_ATOM(onstorageareachanged, "onstorageareachanged")
+GK_ATOM(onsubmit, "onsubmit")
+GK_ATOM(onsuccess, "onsuccess")
+GK_ATOM(ontypechange, "ontypechange")
+GK_ATOM(onterminate, "onterminate")
+GK_ATOM(ontext, "ontext")
+GK_ATOM(ontoggle, "ontoggle")
+GK_ATOM(ontouchstart, "ontouchstart")
+GK_ATOM(ontouchend, "ontouchend")
+GK_ATOM(ontouchmove, "ontouchmove")
+GK_ATOM(ontouchcancel, "ontouchcancel")
+GK_ATOM(ontransitionend, "ontransitionend")
+GK_ATOM(ontransitionrun, "ontransitionrun")
+GK_ATOM(ontransitionstart, "ontransitionstart")
+GK_ATOM(onunderflow, "onunderflow")
+GK_ATOM(onunload, "onunload")
+GK_ATOM(onupdatefound, "onupdatefound")
+GK_ATOM(onupdateready, "onupdateready")
+GK_ATOM(onupgradeneeded, "onupgradeneeded")
+GK_ATOM(onussdreceived, "onussdreceived")
+GK_ATOM(onversionchange, "onversionchange")
+GK_ATOM(onvoicechange, "onvoicechange")
+GK_ATOM(onvoiceschanged, "onvoiceschanged")
+GK_ATOM(onvrdisplayconnect, "onvrdisplayconnect")
+GK_ATOM(onvrdisplaydisconnect, "onvrdisplaydisconnect")
+GK_ATOM(onvrdisplaypresentchange, "onvrdisplaypresentchange")
+GK_ATOM(onwebkitAnimationEnd, "onwebkitAnimationEnd")
+GK_ATOM(onwebkitAnimationIteration, "onwebkitAnimationIteration")
+GK_ATOM(onwebkitAnimationStart, "onwebkitAnimationStart")
+GK_ATOM(onwebkitTransitionEnd, "onwebkitTransitionEnd")
+GK_ATOM(onwebkitanimationend, "onwebkitanimationend")
+GK_ATOM(onwebkitanimationiteration, "onwebkitanimationiteration")
+GK_ATOM(onwebkitanimationstart, "onwebkitanimationstart")
+GK_ATOM(onwebkittransitionend, "onwebkittransitionend")
+GK_ATOM(onwebsocket, "onwebsocket")
+GK_ATOM(onwheel, "onwheel")
+GK_ATOM(open, "open")
+GK_ATOM(optgroup, "optgroup")
+GK_ATOM(optimum, "optimum")
+GK_ATOM(option, "option")
+GK_ATOM(_or, "or")
+GK_ATOM(order, "order")
+GK_ATOM(ordinal, "ordinal")
+GK_ATOM(orient, "orient")
+GK_ATOM(orientation, "orientation")
+GK_ATOM(otherwise, "otherwise")
+GK_ATOM(output, "output")
+GK_ATOM(overflow, "overflow")
+GK_ATOM(overflowchanged, "overflowchanged")
+GK_ATOM(overlay, "overlay")
+GK_ATOM(overlap, "overlap")
+GK_ATOM(p, "p")
+GK_ATOM(pack, "pack")
+GK_ATOM(page, "page")
+GK_ATOM(pageincrement, "pageincrement")
+GK_ATOM(pagex, "pagex")
+GK_ATOM(pagey, "pagey")
+GK_ATOM(paint_order, "paint-order")
+GK_ATOM(palettename, "palettename")
+GK_ATOM(panel, "panel")
+GK_ATOM(param, "param")
+GK_ATOM(parameter, "parameter")
+GK_ATOM(parent, "parent")
+GK_ATOM(parentapp, "parentapp")
+GK_ATOM(parentfocused, "parentfocused")
+GK_ATOM(parsetype, "parsetype")
+GK_ATOM(password, "password")
+GK_ATOM(pattern, "pattern")
+GK_ATOM(patternSeparator, "pattern-separator")
+GK_ATOM(perMille, "per-mille")
+GK_ATOM(percent, "percent")
+GK_ATOM(persist, "persist")
+GK_ATOM(phase, "phase")
+GK_ATOM(picture, "picture")
+GK_ATOM(ping, "ping")
+GK_ATOM(pinned,"pinned")
+GK_ATOM(placeholder, "placeholder")
+GK_ATOM(plaintext, "plaintext")
+GK_ATOM(playbackrate, "playbackrate")
+GK_ATOM(pointSize, "point-size")
+GK_ATOM(pointerlockchange, "pointerlockchange")
+GK_ATOM(pointerlockerror, "pointerlockerror")
+GK_ATOM(poly, "poly")
+GK_ATOM(polygon, "polygon")
+GK_ATOM(popup, "popup")
+GK_ATOM(popupalign, "popupalign")
+GK_ATOM(popupanchor, "popupanchor")
+GK_ATOM(popupgroup, "popupgroup")
+GK_ATOM(popuphidden, "popuphidden")
+GK_ATOM(popuphiding, "popuphiding")
+GK_ATOM(popupset, "popupset")
+GK_ATOM(popupshowing, "popupshowing")
+GK_ATOM(popupshown, "popupshown")
+GK_ATOM(popupsinherittooltip, "popupsinherittooltip")
+GK_ATOM(position, "position")
+GK_ATOM(poster, "poster")
+GK_ATOM(pre, "pre")
+GK_ATOM(preceding, "preceding")
+GK_ATOM(precedingSibling, "preceding-sibling")
+GK_ATOM(predicate, "predicate")
+GK_ATOM(prefix, "prefix")
+GK_ATOM(preload, "preload")
+GK_ATOM(prerendered, "prerendered")
+GK_ATOM(mozpresentation, "mozpresentation")
+GK_ATOM(preserve, "preserve")
+GK_ATOM(preserveSpace, "preserve-space")
+GK_ATOM(preventdefault, "preventdefault")
+GK_ATOM(primary, "primary")
+GK_ATOM(print, "print")
+GK_ATOM(priority, "priority")
+GK_ATOM(processingInstruction, "processing-instruction")
+GK_ATOM(profile, "profile")
+GK_ATOM(progress, "progress")
+GK_ATOM(progressmeter, "progressmeter")
+GK_ATOM(progressNormal, "progressNormal")
+GK_ATOM(progressUndetermined, "progressUndetermined")
+GK_ATOM(projection, "projection")
+GK_ATOM(prompt, "prompt")
+GK_ATOM(propagate, "propagate")
+GK_ATOM(properties, "properties")
+GK_ATOM(property, "property")
+GK_ATOM(pubdate, "pubdate")
+GK_ATOM(q, "q")
+GK_ATOM(query, "query")
+GK_ATOM(queryset, "queryset")
+GK_ATOM(querytype, "querytype")
+GK_ATOM(radio, "radio")
+GK_ATOM(radiogroup, "radiogroup")
+GK_ATOM(range, "range")
+GK_ATOM(readonly, "readonly")
+GK_ATOM(rect, "rect")
+GK_ATOM(rectangle, "rectangle")
+GK_ATOM(ref, "ref")
+GK_ATOM(refresh, "refresh")
+GK_ATOM(rel, "rel")
+GK_ATOM(onreloadpage, "onreloadpage")
+GK_ATOM(rem, "rem")
+GK_ATOM(removeelement, "removeelement")
+GK_ATOM(renderingobserverlist, "renderingobserverlist")
+GK_ATOM(repeat, "repeat")
+GK_ATOM(replace, "replace")
+GK_ATOM(required, "required")
+GK_ATOM(reserved, "reserved")
+GK_ATOM(reset, "reset")
+GK_ATOM(resizeafter, "resizeafter")
+GK_ATOM(resizebefore, "resizebefore")
+GK_ATOM(resizer, "resizer")
+GK_ATOM(resolution, "resolution")
+GK_ATOM(resource, "resource")
+GK_ATOM(resources, "resources")
+GK_ATOM(result, "result")
+GK_ATOM(resultPrefix, "result-prefix")
+GK_ATOM(retargetdocumentfocus, "retargetdocumentfocus")
+GK_ATOM(rev, "rev")
+GK_ATOM(reverse, "reverse")
+GK_ATOM(reversed, "reversed")
+GK_ATOM(richlistbox, "richlistbox")
+GK_ATOM(richlistitem, "richlistitem")
+GK_ATOM(right, "right")
+GK_ATOM(rightmargin, "rightmargin")
+GK_ATOM(rightpadding, "rightpadding")
+GK_ATOM(role, "role")
+GK_ATOM(rolluponmousewheel, "rolluponmousewheel")
+GK_ATOM(round, "round")
+GK_ATOM(row, "row")
+GK_ATOM(rows, "rows")
+GK_ATOM(rowspan, "rowspan")
+GK_ATOM(rb, "rb")
+GK_ATOM(rp, "rp")
+GK_ATOM(rt, "rt")
+GK_ATOM(rtc, "rtc")
+GK_ATOM(rtl, "rtl")
+GK_ATOM(ruby, "ruby")
+GK_ATOM(rubyBase, "ruby-base")
+GK_ATOM(rubyBaseContainer, "ruby-base-container")
+GK_ATOM(rubyText, "ruby-text")
+GK_ATOM(rubyTextContainer, "ruby-text-container")
+GK_ATOM(rule, "rule")
+GK_ATOM(rules, "rules")
+GK_ATOM(s, "s")
+GK_ATOM(samp, "samp")
+GK_ATOM(sandbox, "sandbox")
+GK_ATOM(sbattr, "sbattr")
+GK_ATOM(scale, "scale")
+GK_ATOM(scan, "scan")
+GK_ATOM(scheme, "scheme")
+GK_ATOM(scope, "scope")
+GK_ATOM(scoped, "scoped")
+GK_ATOM(screen, "screen")
+GK_ATOM(screenX, "screenX")
+GK_ATOM(screenY, "screenY")
+GK_ATOM(script, "script")
+GK_ATOM(scriptEnabledBeforePrintOrPreview, "scriptEnabledBeforePrintOrPreview")
+GK_ATOM(scrollbar, "scrollbar")
+GK_ATOM(scrollbarbutton, "scrollbarbutton")
+GK_ATOM(scrollbarDownBottom, "scrollbar-down-bottom")
+GK_ATOM(scrollbarDownTop, "scrollbar-down-top")
+GK_ATOM(scrollbarUpBottom, "scrollbar-up-bottom")
+GK_ATOM(scrollbarUpTop, "scrollbar-up-top")
+GK_ATOM(scrollbox, "scrollbox")
+GK_ATOM(scrollcorner, "scrollcorner")
+GK_ATOM(scrolling, "scrolling")
+GK_ATOM(section, "section")
+GK_ATOM(select, "select")
+GK_ATOM(selectable, "selectable")
+GK_ATOM(selected, "selected")
+GK_ATOM(selectedIndex, "selectedIndex")
+GK_ATOM(selectedindex, "selectedindex")
+GK_ATOM(self, "self")
+GK_ATOM(seltype, "seltype")
+GK_ATOM(setcookie, "set-cookie")
+GK_ATOM(setter, "setter")
+GK_ATOM(shape, "shape")
+GK_ATOM(show, "show")
+GK_ATOM(showcaret, "showcaret")
+GK_ATOM(showresizer, "showresizer")
+GK_ATOM(simple, "simple")
+GK_ATOM(single, "single")
+GK_ATOM(size, "size")
+GK_ATOM(sizes, "sizes")
+GK_ATOM(sizemode, "sizemode")
+GK_ATOM(sizetopopup, "sizetopopup")
+GK_ATOM(slider, "slider")
+GK_ATOM(small, "small")
+GK_ATOM(smooth, "smooth")
+GK_ATOM(snap, "snap")
+GK_ATOM(sort, "sort")
+GK_ATOM(sortActive, "sortActive")
+GK_ATOM(sortDirection, "sortDirection")
+GK_ATOM(sorted, "sorted")
+GK_ATOM(sorthints, "sorthints")
+GK_ATOM(sortLocked, "sortLocked")
+GK_ATOM(sortResource, "sortResource")
+GK_ATOM(sortResource2, "sortResource2")
+GK_ATOM(sortSeparators, "sortSeparators")
+GK_ATOM(sortStaticsLast, "sortStaticsLast")
+GK_ATOM(source, "source")
+GK_ATOM(space, "space")
+GK_ATOM(spacer, "spacer")
+GK_ATOM(span, "span")
+GK_ATOM(spellcheck, "spellcheck")
+GK_ATOM(spinner, "spinner")
+GK_ATOM(split, "split")
+GK_ATOM(splitmenu, "splitmenu")
+GK_ATOM(splitter, "splitter")
+GK_ATOM(spring, "spring")
+GK_ATOM(src, "src")
+GK_ATOM(srcdoc, "srcdoc")
+GK_ATOM(srclang, "srclang")
+GK_ATOM(srcset, "srcset")
+GK_ATOM(stack, "stack")
+GK_ATOM(standalone, "standalone")
+GK_ATOM(standby, "standby")
+GK_ATOM(start, "start")
+GK_ATOM(start_after, "start_after")
+GK_ATOM(start_before, "start_before")
+GK_ATOM(startsWith, "starts-with")
+GK_ATOM(state, "state")
+GK_ATOM(statedatasource, "statedatasource")
+GK_ATOM(staticHint, "staticHint")
+GK_ATOM(statusbar, "statusbar")
+GK_ATOM(statustext, "statustext")
+GK_ATOM(step, "step")
+GK_ATOM(stop, "stop")
+GK_ATOM(stretch, "stretch")
+GK_ATOM(strike, "strike")
+GK_ATOM(string, "string")
+GK_ATOM(stringLength, "string-length")
+GK_ATOM(stripSpace, "strip-space")
+GK_ATOM(strong, "strong")
+GK_ATOM(style, "style")
+GK_ATOM(stylesheet, "stylesheet")
+GK_ATOM(stylesheetPrefix, "stylesheet-prefix")
+GK_ATOM(subject, "subject")
+GK_ATOM(submit, "submit")
+GK_ATOM(substate, "substate")
+GK_ATOM(substring, "substring")
+GK_ATOM(substringAfter, "substring-after")
+GK_ATOM(substringBefore, "substring-before")
+GK_ATOM(sub, "sub")
+GK_ATOM(sum, "sum")
+GK_ATOM(sup, "sup")
+GK_ATOM(summary, "summary")
+GK_ATOM(systemProperty, "system-property")
+GK_ATOM(tab, "tab")
+GK_ATOM(tabbox, "tabbox")
+GK_ATOM(tabindex, "tabindex")
+GK_ATOM(table, "table")
+GK_ATOM(tabpanel, "tabpanel")
+GK_ATOM(tabpanels, "tabpanels")
+GK_ATOM(tag, "tag")
+GK_ATOM(target, "target")
+GK_ATOM(targets, "targets")
+GK_ATOM(tbody, "tbody")
+GK_ATOM(td, "td")
+GK_ATOM(_template, "template")
+GK_ATOM(text_decoration, "text-decoration")
+GK_ATOM(terminate, "terminate")
+GK_ATOM(test, "test")
+GK_ATOM(text, "text")
+GK_ATOM(textAlign, "text-align")
+GK_ATOM(textarea, "textarea")
+GK_ATOM(textbox, "textbox")
+GK_ATOM(textnode, "textnode")
+GK_ATOM(textNodeDirectionalityMap, "textNodeDirectionalityMap")
+GK_ATOM(tfoot, "tfoot")
+GK_ATOM(th, "th")
+GK_ATOM(thead, "thead")
+GK_ATOM(thumb, "thumb")
+GK_ATOM(time, "time")
+GK_ATOM(title, "title")
+GK_ATOM(titlebar, "titlebar")
+GK_ATOM(titletip, "titletip")
+GK_ATOM(toggled, "toggled")
+GK_ATOM(token, "token")
+GK_ATOM(tokenize, "tokenize")
+GK_ATOM(toolbar, "toolbar")
+GK_ATOM(toolbarbutton, "toolbarbutton")
+GK_ATOM(toolbaritem, "toolbaritem")
+GK_ATOM(toolbox, "toolbox")
+GK_ATOM(tooltip, "tooltip")
+GK_ATOM(tooltiptext, "tooltiptext")
+GK_ATOM(top, "top")
+GK_ATOM(topleft, "topleft")
+GK_ATOM(topmargin, "topmargin")
+GK_ATOM(toppadding, "toppadding")
+GK_ATOM(topright, "topright")
+GK_ATOM(tr, "tr")
+GK_ATOM(track, "track")
+GK_ATOM(trailing, "trailing")
+GK_ATOM(transform, "transform")
+GK_ATOM(transform_3d, "transform-3d")
+GK_ATOM(transformiix, "transformiix")
+GK_ATOM(translate, "translate")
+GK_ATOM(transparent, "transparent")
+GK_ATOM(tree, "tree")
+GK_ATOM(treecell, "treecell")
+GK_ATOM(treechildren, "treechildren")
+GK_ATOM(treecol, "treecol")
+GK_ATOM(treecolpicker, "treecolpicker")
+GK_ATOM(treecols, "treecols")
+GK_ATOM(treeitem, "treeitem")
+GK_ATOM(treerow, "treerow")
+GK_ATOM(treeseparator, "treeseparator")
+GK_ATOM(triple, "triple")
+GK_ATOM(_true, "true")
+GK_ATOM(tt, "tt")
+GK_ATOM(tty, "tty")
+GK_ATOM(tv, "tv")
+GK_ATOM(type, "type")
+GK_ATOM(typemustmatch, "typemustmatch")
+GK_ATOM(u, "u")
+GK_ATOM(ul, "ul")
+GK_ATOM(underflow, "underflow")
+GK_ATOM(undetermined, "undetermined")
+GK_ATOM(unload, "unload")
+GK_ATOM(unparsedEntityUri, "unparsed-entity-uri")
+GK_ATOM(upperFirst, "upper-first")
+GK_ATOM(uri, "uri")
+GK_ATOM(use, "use")
+GK_ATOM(useAttributeSets, "use-attribute-sets")
+GK_ATOM(usemap, "usemap")
+GK_ATOM(user_scalable, "user-scalable")
+GK_ATOM(userInput, "userInput")
+GK_ATOM(validate, "validate")
+GK_ATOM(valign, "valign")
+GK_ATOM(value, "value")
+GK_ATOM(values, "values")
+GK_ATOM(valueOf, "value-of")
+GK_ATOM(valuetype, "valuetype")
+GK_ATOM(var, "var")
+GK_ATOM(variable, "variable")
+GK_ATOM(vbox, "vbox")
+GK_ATOM(vcard_name, "vcard_name")
+GK_ATOM(vendor, "vendor")
+GK_ATOM(vendorUrl, "vendor-url")
+GK_ATOM(version, "version")
+GK_ATOM(vert, "vert")
+GK_ATOM(vertical, "vertical")
+GK_ATOM(audio, "audio")
+GK_ATOM(video, "video")
+GK_ATOM(videocontrols, "videocontrols")
+GK_ATOM(viewport, "viewport")
+GK_ATOM(viewport_height, "viewport-height")
+GK_ATOM(viewport_initial_scale, "viewport-initial-scale")
+GK_ATOM(viewport_maximum_scale, "viewport-maximum-scale")
+GK_ATOM(viewport_minimum_scale, "viewport-minimum-scale")
+GK_ATOM(viewport_user_scalable, "viewport-user-scalable")
+GK_ATOM(viewport_width, "viewport-width")
+GK_ATOM(visibility, "visibility")
+GK_ATOM(visuallyselected, "visuallyselected")
+GK_ATOM(vlink, "vlink")
+GK_ATOM(vspace, "vspace")
+GK_ATOM(wbr, "wbr")
+GK_ATOM(webkitdirectory, "webkitdirectory")
+GK_ATOM(when, "when")
+GK_ATOM(where, "where")
+GK_ATOM(widget, "widget")
+GK_ATOM(width, "width")
+GK_ATOM(window, "window")
+GK_ATOM(headerWindowTarget, "window-target")
+GK_ATOM(windowtype, "windowtype")
+GK_ATOM(withParam, "with-param")
+GK_ATOM(wizard, "wizard")
+GK_ATOM(wrap, "wrap")
+GK_ATOM(headerDNSPrefetchControl,"x-dns-prefetch-control")
+GK_ATOM(headerCSP, "content-security-policy")
+GK_ATOM(headerCSPReportOnly, "content-security-policy-report-only")
+GK_ATOM(headerXFO, "x-frame-options")
+GK_ATOM(x_western, "x-western")
+GK_ATOM(xml, "xml")
+GK_ATOM(xml_stylesheet, "xml-stylesheet")
+GK_ATOM(xmlns, "xmlns")
+GK_ATOM(xmp, "xmp")
+GK_ATOM(xulcontentsgenerated, "xulcontentsgenerated")
+GK_ATOM(yes, "yes")
+GK_ATOM(z_index, "z-index")
+GK_ATOM(zeroDigit, "zero-digit")
+
+
+GK_ATOM(percentage, "%")
+GK_ATOM(A, "A")
+GK_ATOM(alignment_baseline, "alignment-baseline")
+GK_ATOM(amplitude, "amplitude")
+GK_ATOM(animate, "animate")
+GK_ATOM(animateColor, "animateColor")
+GK_ATOM(animateMotion, "animateMotion")
+GK_ATOM(animateTransform, "animateTransform")
+GK_ATOM(arithmetic, "arithmetic")
+GK_ATOM(atop, "atop")
+GK_ATOM(azimuth, "azimuth")
+GK_ATOM(B, "B")
+GK_ATOM(backgroundColor, "background-color")
+GK_ATOM(background_image, "background-image")
+GK_ATOM(baseFrequency, "baseFrequency")
+GK_ATOM(baseline_shift, "baseline-shift")
+GK_ATOM(bias, "bias")
+GK_ATOM(caption_side, "caption-side")
+GK_ATOM(clip_path, "clip-path")
+GK_ATOM(clip_rule, "clip-rule")
+GK_ATOM(clipPath, "clipPath")
+GK_ATOM(clipPathUnits, "clipPathUnits")
+GK_ATOM(cm, "cm")
+GK_ATOM(colorBurn, "color-burn")
+GK_ATOM(colorDodge, "color-dodge")
+GK_ATOM(colorInterpolation, "color-interpolation")
+GK_ATOM(colorInterpolationFilters, "color-interpolation-filters")
+GK_ATOM(colorProfile, "color-profile")
+GK_ATOM(cursor, "cursor")
+GK_ATOM(cx, "cx")
+GK_ATOM(cy, "cy")
+GK_ATOM(d, "d")
+GK_ATOM(darken, "darken")
+GK_ATOM(defs, "defs")
+GK_ATOM(deg, "deg")
+GK_ATOM(desc, "desc")
+GK_ATOM(diffuseConstant, "diffuseConstant")
+GK_ATOM(dilate, "dilate")
+GK_ATOM(direction, "direction")
+GK_ATOM(disable, "disable")
+GK_ATOM(discrete, "discrete")
+GK_ATOM(divisor, "divisor")
+GK_ATOM(dominant_baseline, "dominant-baseline")
+GK_ATOM(duplicate, "duplicate")
+GK_ATOM(dx, "dx")
+GK_ATOM(dy, "dy")
+GK_ATOM(edgeMode, "edgeMode")
+GK_ATOM(ellipse, "ellipse")
+GK_ATOM(elevation, "elevation")
+GK_ATOM(erode, "erode")
+GK_ATOM(ex, "ex")
+GK_ATOM(exact, "exact")
+GK_ATOM(exclusion, "exclusion")
+GK_ATOM(exponent, "exponent")
+GK_ATOM(feBlend, "feBlend")
+GK_ATOM(feColorMatrix, "feColorMatrix")
+GK_ATOM(feComponentTransfer, "feComponentTransfer")
+GK_ATOM(feComposite, "feComposite")
+GK_ATOM(feConvolveMatrix, "feConvolveMatrix")
+GK_ATOM(feDiffuseLighting, "feDiffuseLighting")
+GK_ATOM(feDisplacementMap, "feDisplacementMap")
+GK_ATOM(feDistantLight,"feDistantLight")
+GK_ATOM(feDropShadow, "feDropShadow")
+GK_ATOM(feFlood, "feFlood")
+GK_ATOM(feFuncA, "feFuncA")
+GK_ATOM(feFuncB, "feFuncB")
+GK_ATOM(feFuncG, "feFuncG")
+GK_ATOM(feFuncR, "feFuncR")
+GK_ATOM(feGaussianBlur, "feGaussianBlur")
+GK_ATOM(feImage, "feImage")
+GK_ATOM(feMerge, "feMerge")
+GK_ATOM(feMergeNode, "feMergeNode")
+GK_ATOM(feMorphology, "feMorphology")
+GK_ATOM(feOffset, "feOffset")
+GK_ATOM(fePointLight, "fePointLight")
+GK_ATOM(feSpecularLighting, "feSpecularLighting")
+GK_ATOM(feSpotLight, "feSpotLight")
+GK_ATOM(feTile, "feTile")
+GK_ATOM(feTurbulence, "feTurbulence")
+GK_ATOM(fill, "fill")
+GK_ATOM(fill_opacity, "fill-opacity")
+GK_ATOM(fill_rule, "fill-rule")
+GK_ATOM(filter, "filter")
+GK_ATOM(filterUnits, "filterUnits")
+GK_ATOM(_float, "float")
+GK_ATOM(flood_color, "flood-color")
+GK_ATOM(flood_opacity, "flood-opacity")
+GK_ATOM(font_face, "font-face")
+GK_ATOM(font_face_format, "font-face-format")
+GK_ATOM(font_face_name, "font-face-name")
+GK_ATOM(font_face_src, "font-face-src")
+GK_ATOM(font_face_uri, "font-face-uri")
+GK_ATOM(font_family, "font-family")
+GK_ATOM(font_size, "font-size")
+GK_ATOM(font_size_adjust, "font-size-adjust")
+GK_ATOM(font_stretch, "font-stretch")
+GK_ATOM(font_style, "font-style")
+GK_ATOM(font_variant, "font-variant")
+GK_ATOM(foreignObject, "foreignObject")
+GK_ATOM(fractalNoise, "fractalNoise")
+GK_ATOM(fx, "fx")
+GK_ATOM(fy, "fy")
+GK_ATOM(G, "G")
+GK_ATOM(g, "g")
+GK_ATOM(gamma, "gamma")
+// 'generic' conflicts with msvc11 winrt compiler extensions
+GK_ATOM(generic_, "generic")
+GK_ATOM(glyphRef, "glyphRef")
+GK_ATOM(grad, "grad")
+GK_ATOM(gradientTransform, "gradientTransform")
+GK_ATOM(gradientUnits, "gradientUnits")
+GK_ATOM(hardLight, "hard-light")
+GK_ATOM(hue, "hue")
+GK_ATOM(hueRotate, "hueRotate")
+GK_ATOM(identity, "identity")
+GK_ATOM(image_rendering, "image-rendering")
+GK_ATOM(in, "in")
+GK_ATOM(in2, "in2")
+GK_ATOM(intercept, "intercept")
+GK_ATOM(k1, "k1")
+GK_ATOM(k2, "k2")
+GK_ATOM(k3, "k3")
+GK_ATOM(k4, "k4")
+GK_ATOM(kernelMatrix, "kernelMatrix")
+GK_ATOM(kernelUnitLength, "kernelUnitLength")
+GK_ATOM(lengthAdjust, "lengthAdjust")
+GK_ATOM(letter_spacing, "letter-spacing")
+GK_ATOM(lighten, "lighten")
+GK_ATOM(lighting_color, "lighting-color")
+GK_ATOM(limitingConeAngle, "limitingConeAngle")
+GK_ATOM(linear, "linear")
+GK_ATOM(linearGradient, "linearGradient")
+GK_ATOM(linearRGB, "linearRGB")
+GK_ATOM(list_style_type, "list-style-type")
+GK_ATOM(luminanceToAlpha, "luminanceToAlpha")
+GK_ATOM(luminosity, "luminosity")
+GK_ATOM(magnify, "magnify")
+GK_ATOM(marker, "marker")
+GK_ATOM(marker_end, "marker-end")
+GK_ATOM(marker_mid, "marker-mid")
+GK_ATOM(marker_start, "marker-start")
+GK_ATOM(markerHeight, "markerHeight")
+GK_ATOM(markerUnits, "markerUnits")
+GK_ATOM(markerWidth, "markerWidth")
+GK_ATOM(mask, "mask")
+GK_ATOM(maskContentUnits, "maskContentUnits")
+GK_ATOM(mask_type, "mask-type")
+GK_ATOM(maskUnits, "maskUnits")
+GK_ATOM(matrix, "matrix")
+GK_ATOM(metadata, "metadata")
+GK_ATOM(missingGlyph, "missing-glyph")
+GK_ATOM(mm, "mm")
+GK_ATOM(mpath, "mpath")
+GK_ATOM(noStitch, "noStitch")
+GK_ATOM(numOctaves, "numOctaves")
+GK_ATOM(multiply, "multiply")
+GK_ATOM(objectBoundingBox, "objectBoundingBox")
+GK_ATOM(offset, "offset")
+GK_ATOM(onSVGLoad, "onSVGLoad")
+GK_ATOM(onSVGResize, "onSVGResize")
+GK_ATOM(onSVGScroll, "onSVGScroll")
+GK_ATOM(onSVGUnload, "onSVGUnload")
+GK_ATOM(onSVGZoom, "onSVGZoom")
+GK_ATOM(onzoom, "onzoom")
+GK_ATOM(opacity, "opacity")
+GK_ATOM(_operator, "operator")
+GK_ATOM(out, "out")
+GK_ATOM(over, "over")
+GK_ATOM(overridePreserveAspectRatio, "overridePreserveAspectRatio")
+GK_ATOM(pad, "pad")
+GK_ATOM(path, "path")
+GK_ATOM(pathLength, "pathLength")
+GK_ATOM(patternContentUnits, "patternContentUnits")
+GK_ATOM(patternTransform, "patternTransform")
+GK_ATOM(patternUnits, "patternUnits")
+GK_ATOM(pc, "pc")
+GK_ATOM(pointer_events, "pointer-events")
+GK_ATOM(points, "points")
+GK_ATOM(pointsAtX, "pointsAtX")
+GK_ATOM(pointsAtY, "pointsAtY")
+GK_ATOM(pointsAtZ, "pointsAtZ")
+GK_ATOM(polyline, "polyline")
+GK_ATOM(preserveAlpha, "preserveAlpha")
+GK_ATOM(preserveAspectRatio, "preserveAspectRatio")
+GK_ATOM(primitiveUnits, "primitiveUnits")
+GK_ATOM(pt, "pt")
+GK_ATOM(px, "px")
+GK_ATOM(R, "R")
+GK_ATOM(r, "r")
+GK_ATOM(rad, "rad")
+GK_ATOM(radialGradient, "radialGradient")
+GK_ATOM(radius, "radius")
+GK_ATOM(reflect, "reflect")
+GK_ATOM(refX, "refX")
+GK_ATOM(refY, "refY")
+GK_ATOM(requiredExtensions, "requiredExtensions")
+GK_ATOM(requiredFeatures, "requiredFeatures")
+GK_ATOM(rotate, "rotate")
+GK_ATOM(rx, "rx")
+GK_ATOM(ry, "ry")
+GK_ATOM(saturate, "saturate")
+GK_ATOM(saturation, "saturation")
+GK_ATOM(set, "set")
+GK_ATOM(seed, "seed")
+GK_ATOM(shadow, "shadow")
+GK_ATOM(shape_rendering, "shape-rendering")
+GK_ATOM(skewX, "skewX")
+GK_ATOM(skewY, "skewY")
+GK_ATOM(slope, "slope")
+GK_ATOM(softLight, "soft-light")
+GK_ATOM(spacing, "spacing")
+GK_ATOM(spacingAndGlyphs, "spacingAndGlyphs")
+GK_ATOM(specularConstant, "specularConstant")
+GK_ATOM(specularExponent, "specularExponent")
+GK_ATOM(spreadMethod, "spreadMethod")
+GK_ATOM(sRGB, "sRGB")
+GK_ATOM(startOffset, "startOffset")
+GK_ATOM(stdDeviation, "stdDeviation")
+GK_ATOM(stitch, "stitch")
+GK_ATOM(stitchTiles, "stitchTiles")
+GK_ATOM(stop_color, "stop-color")
+GK_ATOM(stop_opacity, "stop-opacity")
+GK_ATOM(stroke, "stroke")
+GK_ATOM(stroke_dasharray, "stroke-dasharray")
+GK_ATOM(stroke_dashoffset, "stroke-dashoffset")
+GK_ATOM(stroke_linecap, "stroke-linecap")
+GK_ATOM(stroke_linejoin, "stroke-linejoin")
+GK_ATOM(stroke_miterlimit, "stroke-miterlimit")
+GK_ATOM(stroke_opacity, "stroke-opacity")
+GK_ATOM(stroke_width, "stroke-width")
+GK_ATOM(strokeWidth, "strokeWidth")
+GK_ATOM(surfaceScale, "surfaceScale")
+GK_ATOM(svg, "svg")
+GK_ATOM(svgContextPaint, "svgContextPaint")
+GK_ATOM(svgSwitch, "switch")
+GK_ATOM(symbol, "symbol")
+GK_ATOM(systemLanguage, "systemLanguage")
+GK_ATOM(tableValues, "tableValues")
+GK_ATOM(targetX, "targetX")
+GK_ATOM(targetY, "targetY")
+GK_ATOM(text_anchor, "text-anchor")
+GK_ATOM(text_rendering, "text-rendering")
+GK_ATOM(textLength, "textLength")
+GK_ATOM(textPath, "textPath")
+GK_ATOM(tref, "tref")
+GK_ATOM(tspan, "tspan")
+GK_ATOM(turbulence, "turbulence")
+GK_ATOM(unicode_bidi, "unicode-bidi")
+GK_ATOM(userSpaceOnUse, "userSpaceOnUse")
+GK_ATOM(view, "view")
+GK_ATOM(viewBox, "viewBox")
+GK_ATOM(viewTarget, "viewTarget")
+GK_ATOM(white_space, "white-space")
+GK_ATOM(word_spacing, "word-spacing")
+GK_ATOM(writing_mode, "writing-mode")
+GK_ATOM(x, "x")
+GK_ATOM(x1, "x1")
+GK_ATOM(x2, "x2")
+GK_ATOM(xChannelSelector, "xChannelSelector")
+GK_ATOM(xor_, "xor")
+GK_ATOM(y, "y")
+GK_ATOM(y1, "y1")
+GK_ATOM(y2, "y2")
+GK_ATOM(yChannelSelector, "yChannelSelector")
+GK_ATOM(z, "z")
+GK_ATOM(zoomAndPan, "zoomAndPan")
+GK_ATOM(vector_effect, "vector-effect")
+GK_ATOM(vertical_align, "vertical-align")
+
+GK_ATOM(accumulate, "accumulate")
+GK_ATOM(additive, "additive")
+GK_ATOM(attributeName, "attributeName")
+GK_ATOM(attributeType, "attributeType")
+GK_ATOM(auto_reverse, "auto-reverse")
+GK_ATOM(begin, "begin")
+GK_ATOM(beginEvent, "beginEvent")
+GK_ATOM(by, "by")
+GK_ATOM(calcMode, "calcMode")
+GK_ATOM(css, "CSS")
+GK_ATOM(dur, "dur")
+GK_ATOM(keyPoints, "keyPoints")
+GK_ATOM(keySplines, "keySplines")
+GK_ATOM(keyTimes, "keyTimes")
+GK_ATOM(mozAnimateMotionDummyAttr, "_mozAnimateMotionDummyAttr")
+GK_ATOM(onbegin, "onbegin")
+GK_ATOM(onbeginEvent, "onbeginEvent")
+GK_ATOM(onend, "onend")
+GK_ATOM(onendEvent, "onendEvent")
+GK_ATOM(onrepeat, "onrepeat")
+GK_ATOM(onrepeatEvent, "onrepeatEvent")
+GK_ATOM(repeatCount, "repeatCount")
+GK_ATOM(repeatDur, "repeatDur")
+GK_ATOM(repeatEvent, "repeatEvent")
+GK_ATOM(restart, "restart")
+GK_ATOM(to, "to")
+GK_ATOM(XML, "XML")
+
+GK_ATOM(abs_, "abs")
+GK_ATOM(accent_, "accent")
+GK_ATOM(accentunder_, "accentunder")
+GK_ATOM(actiontype_, "actiontype")
+GK_ATOM(alignmentscope_, "alignmentscope")
+GK_ATOM(altimg_, "altimg")
+GK_ATOM(altimg_height_, "altimg-height")
+GK_ATOM(altimg_valign_, "altimg-valign")
+GK_ATOM(altimg_width_, "altimg-width")
+GK_ATOM(annotation_, "annotation")
+GK_ATOM(annotation_xml_, "annotation-xml")
+GK_ATOM(apply_, "apply")
+GK_ATOM(approx_, "approx")
+GK_ATOM(arccos_, "arccos")
+GK_ATOM(arccosh_, "arccosh")
+GK_ATOM(arccot_, "arccot")
+GK_ATOM(arccoth_, "arccoth")
+GK_ATOM(arccsc_, "arccsc")
+GK_ATOM(arccsch_, "arccsch")
+GK_ATOM(arcsec_, "arcsec")
+GK_ATOM(arcsech_, "arcsech")
+GK_ATOM(arcsin_, "arcsin")
+GK_ATOM(arcsinh_, "arcsinh")
+GK_ATOM(arctan_, "arctan")
+GK_ATOM(arctanh_, "arctanh")
+GK_ATOM(arg_, "arg")
+GK_ATOM(bevelled_, "bevelled")
+GK_ATOM(bind_, "bind")
+GK_ATOM(bvar_, "bvar")
+GK_ATOM(card_, "card")
+GK_ATOM(cartesianproduct_, "cartesianproduct")
+GK_ATOM(cbytes_, "cbytes")
+GK_ATOM(cd_, "cd")
+GK_ATOM(cdgroup_, "cdgroup")
+GK_ATOM(cerror_, "cerror")
+GK_ATOM(charalign_, "charalign")
+GK_ATOM(ci_, "ci")
+GK_ATOM(closure_, "closure")
+GK_ATOM(cn_, "cn")
+GK_ATOM(codomain_, "codomain")
+GK_ATOM(columnalign_, "columnalign")
+GK_ATOM(columnalignment_, "columnalignment")
+GK_ATOM(columnlines_, "columnlines")
+GK_ATOM(columnspacing_, "columnspacing")
+GK_ATOM(columnspan_, "columnspan")
+GK_ATOM(columnwidth_, "columnwidth")
+GK_ATOM(complexes_, "complexes")
+GK_ATOM(compose_, "compose")
+GK_ATOM(condition_, "condition")
+GK_ATOM(conjugate_, "conjugate")
+GK_ATOM(cos_, "cos")
+GK_ATOM(cosh_, "cosh")
+GK_ATOM(cot_, "cot")
+GK_ATOM(coth_, "coth")
+GK_ATOM(crossout_, "crossout")
+GK_ATOM(csc_, "csc")
+GK_ATOM(csch_, "csch")
+GK_ATOM(cs_, "cs")
+GK_ATOM(csymbol_, "csymbol")
+GK_ATOM(curl_, "curl")
+GK_ATOM(decimalpoint_, "decimalpoint")
+GK_ATOM(definitionURL_, "definitionURL")
+GK_ATOM(degree_, "degree")
+GK_ATOM(denomalign_, "denomalign")
+GK_ATOM(depth_, "depth")
+GK_ATOM(determinant_, "determinant")
+GK_ATOM(diff_, "diff")
+GK_ATOM(displaystyle_, "displaystyle")
+GK_ATOM(divergence_, "divergence")
+GK_ATOM(divide_, "divide")
+GK_ATOM(domain_, "domain")
+GK_ATOM(domainofapplication_, "domainofapplication")
+GK_ATOM(edge_, "edge")
+GK_ATOM(el_, "el")
+GK_ATOM(emptyset_, "emptyset")
+GK_ATOM(eq_, "eq")
+GK_ATOM(equalcolumns_, "equalcolumns")
+GK_ATOM(equalrows_, "equalrows")
+GK_ATOM(equivalent_, "equivalent")
+GK_ATOM(eulergamma_, "eulergamma")
+GK_ATOM(exists_, "exists")
+GK_ATOM(exp_, "exp")
+GK_ATOM(exponentiale_, "exponentiale")
+GK_ATOM(factorial_, "factorial")
+GK_ATOM(factorof_, "factorof")
+GK_ATOM(fence_, "fence")
+GK_ATOM(fn_, "fn")
+GK_ATOM(fontfamily_, "fontfamily")
+GK_ATOM(fontsize_, "fontsize")
+GK_ATOM(fontstyle_, "fontstyle")
+GK_ATOM(fontweight_, "fontweight")
+GK_ATOM(forall_, "forall")
+GK_ATOM(framespacing_, "framespacing")
+GK_ATOM(gcd_, "gcd")
+GK_ATOM(geq_, "geq")
+GK_ATOM(groupalign_, "groupalign")
+GK_ATOM(gt_, "gt")
+GK_ATOM(ident_, "ident")
+GK_ATOM(imaginaryi_, "imaginaryi")
+GK_ATOM(imaginary_, "imaginary")
+GK_ATOM(implies_, "implies")
+GK_ATOM(indentalignfirst_, "indentalignfirst")
+GK_ATOM(indentalign_, "indentalign")
+GK_ATOM(indentalignlast_, "indentalignlast")
+GK_ATOM(indentshiftfirst_, "indentshiftfirst")
+GK_ATOM(indentshift_, "indentshift")
+GK_ATOM(indenttarget_, "indenttarget")
+GK_ATOM(integers_, "integers")
+GK_ATOM(intersect_, "intersect")
+GK_ATOM(interval_, "interval")
+GK_ATOM(int_, "int")
+GK_ATOM(inverse_, "inverse")
+GK_ATOM(lambda_, "lambda")
+GK_ATOM(laplacian_, "laplacian")
+GK_ATOM(largeop_, "largeop")
+GK_ATOM(lcm_, "lcm")
+GK_ATOM(leq_, "leq")
+GK_ATOM(limit_, "limit")
+GK_ATOM(linebreak_, "linebreak")
+GK_ATOM(linebreakmultchar_, "linebreakmultchar")
+GK_ATOM(linebreakstyle_, "linebreakstyle")
+GK_ATOM(linethickness_, "linethickness")
+GK_ATOM(list_, "list")
+GK_ATOM(ln_, "ln")
+GK_ATOM(location_, "location")
+GK_ATOM(logbase_, "logbase")
+GK_ATOM(log_, "log")
+GK_ATOM(longdivstyle_, "longdivstyle")
+GK_ATOM(lowlimit_, "lowlimit")
+GK_ATOM(lquote_, "lquote")
+GK_ATOM(lspace_, "lspace")
+GK_ATOM(lt_, "lt")
+GK_ATOM(maction_, "maction")
+GK_ATOM(maligngroup_, "maligngroup")
+GK_ATOM(malignmark_, "malignmark")
+GK_ATOM(mathbackground_, "mathbackground")
+GK_ATOM(mathcolor_, "mathcolor")
+GK_ATOM(mathsize_, "mathsize")
+GK_ATOM(mathvariant_, "mathvariant")
+GK_ATOM(matrixrow_, "matrixrow")
+GK_ATOM(maxsize_, "maxsize")
+GK_ATOM(mean_, "mean")
+GK_ATOM(median_, "median")
+GK_ATOM(menclose_, "menclose")
+GK_ATOM(merror_, "merror")
+GK_ATOM(mfenced_, "mfenced")
+GK_ATOM(mfrac_, "mfrac")
+GK_ATOM(mglyph_, "mglyph")
+GK_ATOM(mi_, "mi")
+GK_ATOM(minlabelspacing_, "minlabelspacing")
+GK_ATOM(minsize_, "minsize")
+GK_ATOM(minus_, "minus")
+GK_ATOM(mlabeledtr_, "mlabeledtr")
+GK_ATOM(mlongdiv_, "mlongdiv")
+GK_ATOM(mmultiscripts_, "mmultiscripts")
+GK_ATOM(mn_, "mn")
+GK_ATOM(momentabout_, "momentabout")
+GK_ATOM(moment_, "moment")
+GK_ATOM(mo_, "mo")
+GK_ATOM(movablelimits_, "movablelimits")
+GK_ATOM(mover_, "mover")
+GK_ATOM(mpadded_, "mpadded")
+GK_ATOM(mphantom_, "mphantom")
+GK_ATOM(mprescripts_, "mprescripts")
+GK_ATOM(mroot_, "mroot")
+GK_ATOM(mrow_, "mrow")
+GK_ATOM(mscarries_, "mscarries")
+GK_ATOM(mscarry_, "mscarry")
+GK_ATOM(msgroup_, "msgroup")
+GK_ATOM(msline_, "msline")
+GK_ATOM(ms_, "ms")
+GK_ATOM(mspace_, "mspace")
+GK_ATOM(msqrt_, "msqrt")
+GK_ATOM(msrow_, "msrow")
+GK_ATOM(mstack_, "mstack")
+GK_ATOM(mstyle_, "mstyle")
+GK_ATOM(msub_, "msub")
+GK_ATOM(msubsup_, "msubsup")
+GK_ATOM(msup_, "msup")
+GK_ATOM(mtable_, "mtable")
+GK_ATOM(mtd_, "mtd")
+GK_ATOM(mtext_, "mtext")
+GK_ATOM(mtr_, "mtr")
+GK_ATOM(munder_, "munder")
+GK_ATOM(munderover_, "munderover")
+GK_ATOM(naturalnumbers_, "naturalnumbers")
+GK_ATOM(neq_, "neq")
+GK_ATOM(notanumber_, "notanumber")
+GK_ATOM(notation_, "notation")
+GK_ATOM(note_, "note")
+GK_ATOM(notin_, "notin")
+GK_ATOM(notprsubset_, "notprsubset")
+GK_ATOM(notsubset_, "notsubset")
+GK_ATOM(numalign_, "numalign")
+GK_ATOM(other_, "other")
+GK_ATOM(outerproduct_, "outerproduct")
+GK_ATOM(partialdiff_, "partialdiff")
+GK_ATOM(piece_, "piece")
+GK_ATOM(piecewise_, "piecewise")
+GK_ATOM(pi_, "pi")
+GK_ATOM(plus_, "plus")
+GK_ATOM(power_, "power")
+GK_ATOM(primes_, "primes")
+GK_ATOM(product_, "product")
+GK_ATOM(prsubset_, "prsubset")
+GK_ATOM(quotient_, "quotient")
+GK_ATOM(rationals_, "rationals")
+GK_ATOM(real_, "real")
+GK_ATOM(reals_, "reals")
+GK_ATOM(reln_, "reln")
+GK_ATOM(root_, "root")
+GK_ATOM(rowalign_, "rowalign")
+GK_ATOM(rowlines_, "rowlines")
+GK_ATOM(rowspacing_, "rowspacing")
+GK_ATOM(rquote_, "rquote")
+GK_ATOM(rspace_, "rspace")
+GK_ATOM(scalarproduct_, "scalarproduct")
+GK_ATOM(schemaLocation_, "schemaLocation")
+GK_ATOM(scriptlevel_, "scriptlevel")
+GK_ATOM(scriptminsize_, "scriptminsize")
+GK_ATOM(scriptsizemultiplier_, "scriptsizemultiplier")
+GK_ATOM(scriptsize_, "scriptsize")
+GK_ATOM(sdev_, "sdev")
+GK_ATOM(sech_, "sech")
+GK_ATOM(sec_, "sec")
+GK_ATOM(selection_, "selection")
+GK_ATOM(selector_, "selector")
+GK_ATOM(semantics_, "semantics")
+GK_ATOM(separator_, "separator")
+GK_ATOM(separators_, "separators")
+GK_ATOM(sep_, "sep")
+GK_ATOM(setdiff_, "setdiff")
+GK_ATOM(set_, "set")
+GK_ATOM(share_, "share")
+GK_ATOM(shift_, "shift")
+GK_ATOM(side_, "side")
+GK_ATOM(sinh_, "sinh")
+GK_ATOM(sin_, "sin")
+GK_ATOM(stackalign_, "stackalign")
+GK_ATOM(stretchy_, "stretchy")
+GK_ATOM(subscriptshift_, "subscriptshift")
+GK_ATOM(subset_, "subset")
+GK_ATOM(superscriptshift_, "superscriptshift")
+GK_ATOM(symmetric_, "symmetric")
+GK_ATOM(tanh_, "tanh")
+GK_ATOM(tan_, "tan")
+GK_ATOM(tendsto_, "tendsto")
+GK_ATOM(times_, "times")
+GK_ATOM(transpose_, "transpose")
+GK_ATOM(union_, "union")
+GK_ATOM(uplimit_, "uplimit")
+GK_ATOM(variance_, "variance")
+GK_ATOM(vectorproduct_, "vectorproduct")
+GK_ATOM(vector_, "vector")
+GK_ATOM(voffset_, "voffset")
+GK_ATOM(xref_, "xref")
+GK_ATOM(math, "math") // the only one without an underscore
+GK_ATOM(avg, "avg")
+GK_ATOM(booleanFromString, "boolean-from-string")
+GK_ATOM(countNonEmpty, "count-non-empty")
+GK_ATOM(daysFromDate, "days-from-date")
+GK_ATOM(init, "init")
+GK_ATOM(instance, "instance")
+GK_ATOM(months, "months")
+GK_ATOM(now, "now")
+GK_ATOM(seconds, "seconds")
+GK_ATOM(secondsFromDateTime, "seconds-from-dateTime")
+
+// Simple gestures support
+GK_ATOM(onMozSwipeGestureMayStart, "onMozSwipeGestureMayStart")
+GK_ATOM(onMozSwipeGestureStart, "onMozSwipeGestureStart")
+GK_ATOM(onMozSwipeGestureUpdate, "onMozSwipeGestureUpdate")
+GK_ATOM(onMozSwipeGestureEnd, "onMozSwipeGestureEnd")
+GK_ATOM(onMozSwipeGesture, "onMozSwipeGesture")
+GK_ATOM(onMozMagnifyGestureStart, "onMozMagnifyGestureStart")
+GK_ATOM(onMozMagnifyGestureUpdate, "onMozMagnifyGestureUpdate")
+GK_ATOM(onMozMagnifyGesture, "onMozMagnifyGesture")
+GK_ATOM(onMozRotateGestureStart, "onMozRotateGestureStart")
+GK_ATOM(onMozRotateGestureUpdate, "onMozRotateGestureUpdate")
+GK_ATOM(onMozRotateGesture, "onMozRotateGesture")
+GK_ATOM(onMozTapGesture, "onMozTapGesture")
+GK_ATOM(onMozPressTapGesture, "onMozPressTapGesture")
+GK_ATOM(onMozEdgeUIStarted, "onMozEdgeUIStarted")
+GK_ATOM(onMozEdgeUICanceled, "onMozEdgeUICanceled")
+GK_ATOM(onMozEdgeUICompleted, "onMozEdgeUICompleted")
+
+// Pointer events
+GK_ATOM(onpointerdown, "onpointerdown")
+GK_ATOM(onpointermove, "onpointermove")
+GK_ATOM(onpointerup, "onpointerup")
+GK_ATOM(onpointercancel, "onpointercancel")
+GK_ATOM(onpointerover, "onpointerover")
+GK_ATOM(onpointerout, "onpointerout")
+GK_ATOM(onpointerenter, "onpointerenter")
+GK_ATOM(onpointerleave, "onpointerleave")
+GK_ATOM(ongotpointercapture, "ongotpointercapture")
+GK_ATOM(onlostpointercapture, "onlostpointercapture")
+
+// orientation support
+GK_ATOM(ondevicemotion, "ondevicemotion")
+GK_ATOM(ondeviceorientation, "ondeviceorientation")
+GK_ATOM(onabsolutedeviceorientation, "onabsolutedeviceorientation")
+GK_ATOM(ondeviceproximity, "ondeviceproximity")
+GK_ATOM(onmozorientationchange, "onmozorientationchange")
+GK_ATOM(onuserproximity, "onuserproximity")
+
+// light sensor support
+GK_ATOM(ondevicelight, "ondevicelight")
+
+// Audio channel events
+GK_ATOM(onmozinterruptbegin, "onmozinterruptbegin")
+GK_ATOM(onmozinterruptend, "onmozinterruptend")
+
+// MediaDevices device change event
+GK_ATOM(ondevicechange, "ondevicechange")
+
+//---------------------------------------------------------------------------
+// Special atoms
+//---------------------------------------------------------------------------
+
+// Node types
+GK_ATOM(cdataTagName, "#cdata-section")
+GK_ATOM(commentTagName, "#comment")
+GK_ATOM(documentNodeName, "#document")
+GK_ATOM(documentFragmentNodeName, "#document-fragment")
+GK_ATOM(documentTypeNodeName, "#document-type")
+GK_ATOM(processingInstructionTagName, "#processing-instruction")
+GK_ATOM(textTagName, "#text")
+
+// Frame types
+GK_ATOM(bcTableCellFrame, "BCTableCellFrame") // table cell in border collapsing model
+GK_ATOM(blockFrame, "BlockFrame")
+GK_ATOM(boxFrame, "BoxFrame")
+GK_ATOM(brFrame, "BRFrame")
+GK_ATOM(bulletFrame, "BulletFrame")
+GK_ATOM(colorControlFrame, "colorControlFrame")
+GK_ATOM(columnSetFrame, "ColumnSetFrame")
+GK_ATOM(comboboxControlFrame, "ComboboxControlFrame")
+GK_ATOM(comboboxDisplayFrame, "ComboboxDisplayFrame")
+GK_ATOM(dateTimeControlFrame, "DateTimeControlFrame")
+GK_ATOM(deckFrame, "DeckFrame")
+GK_ATOM(detailsFrame, "DetailsFrame")
+GK_ATOM(fieldSetFrame, "FieldSetFrame")
+GK_ATOM(flexContainerFrame, "FlexContainerFrame")
+GK_ATOM(formControlFrame, "FormControlFrame") // radio or checkbox
+GK_ATOM(frameSetFrame, "FrameSetFrame")
+GK_ATOM(gfxButtonControlFrame, "gfxButtonControlFrame")
+GK_ATOM(gridContainerFrame, "GridContainerFrame")
+GK_ATOM(HTMLButtonControlFrame, "HTMLButtonControlFrame")
+GK_ATOM(HTMLCanvasFrame, "HTMLCanvasFrame")
+GK_ATOM(subDocumentFrame, "subDocumentFrame")
+GK_ATOM(imageBoxFrame, "ImageBoxFrame")
+GK_ATOM(imageFrame, "ImageFrame")
+GK_ATOM(imageControlFrame, "ImageControlFrame")
+GK_ATOM(inlineFrame, "InlineFrame")
+GK_ATOM(leafBoxFrame, "LeafBoxFrame")
+GK_ATOM(legendFrame, "LegendFrame")
+GK_ATOM(letterFrame, "LetterFrame")
+GK_ATOM(lineFrame, "LineFrame")
+GK_ATOM(listControlFrame,"ListControlFrame")
+GK_ATOM(menuFrame,"MenuFrame")
+GK_ATOM(meterFrame, "MeterFrame")
+GK_ATOM(menuPopupFrame,"MenuPopupFrame")
+GK_ATOM(numberControlFrame, "NumberControlFrame")
+GK_ATOM(objectFrame, "ObjectFrame")
+GK_ATOM(pageFrame, "PageFrame")
+GK_ATOM(pageBreakFrame, "PageBreakFrame")
+GK_ATOM(pageContentFrame, "PageContentFrame")
+GK_ATOM(placeholderFrame, "PlaceholderFrame")
+GK_ATOM(popupSetFrame, "PopupSetFrame")
+GK_ATOM(progressFrame, "ProgressFrame")
+GK_ATOM(canvasFrame, "CanvasFrame")
+GK_ATOM(rangeFrame, "RangeFrame")
+GK_ATOM(rootFrame, "RootFrame")
+GK_ATOM(rubyBaseContainerFrame, "RubyBaseContainerFrame")
+GK_ATOM(rubyBaseFrame, "RubyBaseFrame")
+GK_ATOM(rubyFrame, "RubyFrame")
+GK_ATOM(rubyTextContainerFrame, "RubyTextContainerFrame")
+GK_ATOM(rubyTextFrame, "RubyTextFrame")
+GK_ATOM(scrollFrame, "ScrollFrame")
+GK_ATOM(scrollbarFrame, "ScrollbarFrame")
+GK_ATOM(sequenceFrame, "SequenceFrame")
+GK_ATOM(sliderFrame, "sliderFrame")
+GK_ATOM(tableCellFrame, "TableCellFrame")
+GK_ATOM(tableColFrame, "TableColFrame")
+GK_ATOM(tableColGroupFrame, "TableColGroupFrame")
+GK_ATOM(tableFrame, "TableFrame")
+GK_ATOM(tableWrapperFrame, "TableWrapperFrame")
+GK_ATOM(tableRowGroupFrame, "TableRowGroupFrame")
+GK_ATOM(tableRowFrame, "TableRowFrame")
+GK_ATOM(textInputFrame,"TextInputFrame")
+GK_ATOM(textFrame, "TextFrame")
+GK_ATOM(viewportFrame, "ViewportFrame")
+#ifdef MOZ_XUL
+GK_ATOM(XULLabelFrame, "XULLabelFrame")
+#endif
+GK_ATOM(svgAFrame, "SVGAFrame")
+GK_ATOM(svgClipPathFrame, "SVGClipPathFrame")
+GK_ATOM(svgDefsFrame, "SVGDefsFrame")
+GK_ATOM(svgFEContainerFrame, "SVGFEContainerFrame")
+GK_ATOM(svgFEImageFrame, "SVGFEImageFrame")
+GK_ATOM(svgFELeafFrame, "SVGFELeafFrame")
+GK_ATOM(svgFEUnstyledLeafFrame, "SVGFEUnstyledLeafFrame")
+GK_ATOM(svgFilterFrame, "SVGFilterFrame")
+GK_ATOM(svgForeignObjectFrame, "SVGForeignObjectFrame")
+GK_ATOM(svgGenericContainerFrame, "SVGGenericContainerFrame")
+GK_ATOM(svgGFrame, "SVGGFrame")
+GK_ATOM(svgGradientFrame, "SVGGradientFrame")
+GK_ATOM(svgImageFrame, "SVGImageFrame")
+GK_ATOM(svgInnerSVGFrame, "SVGInnerSVGFrame")
+GK_ATOM(svgLinearGradientFrame, "SVGLinearGradientFrame")
+GK_ATOM(svgMarkerFrame, "SVGMarkerFrame")
+GK_ATOM(svgMarkerAnonChildFrame, "SVGMarkerAnonChildFrame")
+GK_ATOM(svgMaskFrame, "SVGMaskFrame")
+GK_ATOM(svgOuterSVGFrame, "SVGOuterSVGFrame")
+GK_ATOM(svgOuterSVGAnonChildFrame, "SVGOuterSVGAnonChildFrame")
+GK_ATOM(svgPathGeometryFrame, "SVGPathGeometryFrame")
+GK_ATOM(svgPatternFrame, "SVGPatternFrame")
+GK_ATOM(svgRadialGradientFrame, "SVGRadialGradientFrame")
+GK_ATOM(svgStopFrame, "SVGStopFrame")
+GK_ATOM(svgSwitchFrame, "SVGSwitchFrame")
+GK_ATOM(svgTextFrame, "SVGTextFrame")
+GK_ATOM(svgUseFrame, "SVGUseFrame")
+GK_ATOM(svgViewFrame, "SVGViewFrame")
+GK_ATOM(HTMLVideoFrame, "VideoFrame")
+GK_ATOM(onloadend, "onloadend")
+GK_ATOM(onloadstart, "onloadstart")
+GK_ATOM(onprogress, "onprogress")
+GK_ATOM(onsuspend, "onsuspend")
+GK_ATOM(onemptied, "onemptied")
+GK_ATOM(onstalled, "onstalled")
+GK_ATOM(onplay, "onplay")
+GK_ATOM(onpause, "onpause")
+GK_ATOM(onloadedmetadata, "onloadedmetadata")
+GK_ATOM(onloadeddata, "onloadeddata")
+GK_ATOM(onwaiting, "onwaiting")
+GK_ATOM(onplaying, "onplaying")
+GK_ATOM(oncanplay, "oncanplay")
+GK_ATOM(oncanplaythrough, "oncanplaythrough")
+GK_ATOM(onseeking, "onseeking")
+GK_ATOM(onseeked, "onseeked")
+GK_ATOM(ontimeout, "ontimeout")
+GK_ATOM(ontimeupdate, "ontimeupdate")
+GK_ATOM(onended, "onended")
+GK_ATOM(onratechange, "onratechange")
+GK_ATOM(ondurationchange, "ondurationchange")
+GK_ATOM(onvolumechange, "onvolumechange")
+GK_ATOM(onaddtrack, "onaddtrack")
+GK_ATOM(oncontrollerchange, "oncontrollerchange")
+GK_ATOM(oncuechange, "oncuechange")
+GK_ATOM(onenter, "onenter")
+GK_ATOM(onexit, "onexit")
+GK_ATOM(onencrypted, "onencrypted")
+GK_ATOM(encrypted, "encrypted")
+GK_ATOM(onwaitingforkey, "onwaitingforkey")
+GK_ATOM(onkeystatuseschange, "onkeystatuseschange")
+GK_ATOM(onremovetrack, "onremovetrack")
+GK_ATOM(loadstart, "loadstart")
+GK_ATOM(suspend, "suspend")
+GK_ATOM(emptied, "emptied")
+GK_ATOM(stalled, "stalled")
+GK_ATOM(play, "play")
+GK_ATOM(pause, "pause")
+GK_ATOM(loadedmetadata, "loadedmetadata")
+GK_ATOM(loadeddata, "loadeddata")
+GK_ATOM(waiting, "waiting")
+GK_ATOM(playing, "playing")
+GK_ATOM(seeking, "seeking")
+GK_ATOM(seeked, "seeked")
+GK_ATOM(timeupdate, "timeupdate")
+GK_ATOM(ended, "ended")
+GK_ATOM(canplay, "canplay")
+GK_ATOM(canplaythrough, "canplaythrough")
+GK_ATOM(ratechange, "ratechange")
+GK_ATOM(durationchange, "durationchange")
+GK_ATOM(volumechange, "volumechange")
+GK_ATOM(ondataavailable, "ondataavailable")
+GK_ATOM(onwarning, "onwarning")
+GK_ATOM(onstart, "onstart")
+GK_ATOM(onstop, "onstop")
+GK_ATOM(onphoto, "onphoto")
+GK_ATOM(onactivestatechanged, "onactivestatechanged")
+#ifdef MOZ_GAMEPAD
+GK_ATOM(ongamepadbuttondown, "ongamepadbuttondown")
+GK_ATOM(ongamepadbuttonup, "ongamepadbuttonup")
+GK_ATOM(ongamepadaxismove, "ongamepadaxismove")
+GK_ATOM(ongamepadconnected, "ongamepadconnected")
+GK_ATOM(ongamepaddisconnected, "ongamepaddisconnected")
+#endif
+
+// Content property names
+GK_ATOM(animationsProperty, "AnimationsProperty") // FrameAnimations*
+GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations*
+GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations*
+GK_ATOM(animationEffectsProperty, "AnimationEffectsProperty") // EffectSet*
+GK_ATOM(animationEffectsForBeforeProperty, "AnimationsEffectsForBeforeProperty") // EffectSet*
+GK_ATOM(animationEffectsForAfterProperty, "AnimationsEffectsForAfterProperty") // EffectSet*
+GK_ATOM(cssPseudoElementBeforeProperty, "CSSPseudoElementBeforeProperty") // CSSPseudoElement*
+GK_ATOM(cssPseudoElementAfterProperty, "CSSPseudoElementAfterProperty") // CSSPseudoElement*
+GK_ATOM(transitionsProperty, "TransitionsProperty") // FrameTransitions*
+GK_ATOM(transitionsOfBeforeProperty, "TransitionsOfBeforeProperty") // FrameTransitions*
+GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*
+GK_ATOM(genConInitializerProperty, "QuoteNodeProperty")
+GK_ATOM(labelMouseDownPtProperty, "LabelMouseDownPtProperty")
+GK_ATOM(baseURIProperty, "baseURIProperty")
+GK_ATOM(lockedStyleStates, "lockedStyleStates")
+GK_ATOM(apzCallbackTransform, "apzCallbackTransform")
+GK_ATOM(restylableAnonymousNode, "restylableAnonymousNode")
+GK_ATOM(paintRequestTime, "PaintRequestTime")
+
+// Languages for lang-specific transforms
+GK_ATOM(Japanese, "ja")
+GK_ATOM(Chinese, "zh-CN")
+GK_ATOM(Taiwanese, "zh-TW")
+GK_ATOM(HongKongChinese, "zh-HK")
+GK_ATOM(Unicode, "x-unicode")
+
+// language codes specifically referenced in the gfx code
+GK_ATOM(ko, "ko")
+GK_ATOM(zh_cn, "zh-cn")
+GK_ATOM(zh_hk, "zh-hk")
+GK_ATOM(zh_tw, "zh-tw")
+
+// additional codes used in nsUnicodeRange.cpp
+GK_ATOM(x_cyrillic, "x-cyrillic")
+GK_ATOM(he, "he")
+GK_ATOM(ar, "ar")
+GK_ATOM(x_devanagari, "x-devanagari")
+GK_ATOM(x_tamil, "x-tamil")
+GK_ATOM(x_armn, "x-armn")
+GK_ATOM(x_beng, "x-beng")
+GK_ATOM(x_cans, "x-cans")
+GK_ATOM(x_ethi, "x-ethi")
+GK_ATOM(x_geor, "x-geor")
+GK_ATOM(x_gujr, "x-gujr")
+GK_ATOM(x_guru, "x-guru")
+GK_ATOM(x_khmr, "x-khmr")
+GK_ATOM(x_knda, "x-knda")
+GK_ATOM(x_mlym, "x-mlym")
+GK_ATOM(x_orya, "x-orya")
+GK_ATOM(x_sinh, "x-sinh")
+GK_ATOM(x_telu, "x-telu")
+GK_ATOM(x_tibt, "x-tibt")
+
+// additional languages that have special case transformations
+GK_ATOM(az, "az")
+GK_ATOM(ba, "ba")
+GK_ATOM(crh, "crh")
+GK_ATOM(el, "el")
+GK_ATOM(ga, "ga")
+GK_ATOM(nl, "nl")
+
+// mathematical language, used for MathML
+GK_ATOM(x_math, "x-math")
+
+// Names for editor transactions
+GK_ATOM(TypingTxnName, "Typing")
+GK_ATOM(IMETxnName, "IME")
+GK_ATOM(DeleteTxnName, "Deleting")
+
+// Font families
+GK_ATOM(serif, "serif")
+GK_ATOM(sans_serif, "sans-serif")
+GK_ATOM(cursive, "cursive")
+GK_ATOM(fantasy, "fantasy")
+GK_ATOM(monospace, "monospace")
+
+// IPC stuff
+GK_ATOM(Remote, "remote")
+GK_ATOM(RemoteId, "_remote_id")
+GK_ATOM(DisplayPort, "_displayport")
+GK_ATOM(DisplayPortMargins, "_displayportmargins")
+GK_ATOM(DisplayPortBase, "_displayportbase")
+GK_ATOM(AsyncScrollLayerCreationFailed, "_asyncscrolllayercreationfailed")
+GK_ATOM(forcemessagemanager, "forcemessagemanager")
+
+// Names for system metrics
+GK_ATOM(color_picker_available, "color-picker-available")
+GK_ATOM(scrollbar_start_backward, "scrollbar-start-backward")
+GK_ATOM(scrollbar_start_forward, "scrollbar-start-forward")
+GK_ATOM(scrollbar_end_backward, "scrollbar-end-backward")
+GK_ATOM(scrollbar_end_forward, "scrollbar-end-forward")
+GK_ATOM(scrollbar_thumb_proportional, "scrollbar-thumb-proportional")
+GK_ATOM(overlay_scrollbars, "overlay-scrollbars")
+GK_ATOM(windows_default_theme, "windows-default-theme")
+GK_ATOM(mac_graphite_theme, "mac-graphite-theme")
+GK_ATOM(mac_yosemite_theme, "mac-yosemite-theme")
+GK_ATOM(windows_compositor, "windows-compositor")
+GK_ATOM(windows_glass, "windows-glass")
+GK_ATOM(touch_enabled, "touch-enabled")
+GK_ATOM(menubar_drag, "menubar-drag")
+GK_ATOM(swipe_animation_enabled, "swipe-animation-enabled")
+GK_ATOM(physical_home_button, "physical-home-button")
+
+// windows theme selector metrics
+GK_ATOM(windows_classic, "windows-classic")
+GK_ATOM(windows_theme_aero, "windows-theme-aero")
+GK_ATOM(windows_theme_aero_lite, "windows-theme-aero-lite")
+GK_ATOM(windows_theme_luna_blue, "windows-theme-luna-blue")
+GK_ATOM(windows_theme_luna_olive, "windows-theme-luna-olive")
+GK_ATOM(windows_theme_luna_silver, "windows-theme-luna-silver")
+GK_ATOM(windows_theme_royale, "windows-theme-royale")
+GK_ATOM(windows_theme_zune, "windows-theme-zune")
+GK_ATOM(windows_theme_generic, "windows-theme-generic")
+
+// And the same again, as media query keywords.
+GK_ATOM(_moz_color_picker_available, "-moz-color-picker-available")
+GK_ATOM(_moz_scrollbar_start_backward, "-moz-scrollbar-start-backward")
+GK_ATOM(_moz_scrollbar_start_forward, "-moz-scrollbar-start-forward")
+GK_ATOM(_moz_scrollbar_end_backward, "-moz-scrollbar-end-backward")
+GK_ATOM(_moz_scrollbar_end_forward, "-moz-scrollbar-end-forward")
+GK_ATOM(_moz_scrollbar_thumb_proportional, "-moz-scrollbar-thumb-proportional")
+GK_ATOM(_moz_overlay_scrollbars, "-moz-overlay-scrollbars")
+GK_ATOM(_moz_windows_default_theme, "-moz-windows-default-theme")
+GK_ATOM(_moz_mac_graphite_theme, "-moz-mac-graphite-theme")
+GK_ATOM(_moz_mac_yosemite_theme, "-moz-mac-yosemite-theme")
+GK_ATOM(_moz_windows_compositor, "-moz-windows-compositor")
+GK_ATOM(_moz_windows_classic, "-moz-windows-classic")
+GK_ATOM(_moz_windows_glass, "-moz-windows-glass")
+GK_ATOM(_moz_windows_theme, "-moz-windows-theme")
+GK_ATOM(_moz_os_version, "-moz-os-version")
+GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
+GK_ATOM(_moz_menubar_drag, "-moz-menubar-drag")
+GK_ATOM(_moz_device_pixel_ratio, "-moz-device-pixel-ratio")
+GK_ATOM(_moz_device_orientation, "-moz-device-orientation")
+GK_ATOM(_moz_is_resource_document, "-moz-is-resource-document")
+GK_ATOM(_moz_swipe_animation_enabled, "-moz-swipe-animation-enabled")
+GK_ATOM(_moz_physical_home_button, "-moz-physical-home-button")
+
+// application commands
+GK_ATOM(Back, "Back")
+GK_ATOM(Forward, "Forward")
+GK_ATOM(Reload, "Reload")
+GK_ATOM(Stop, "Stop")
+GK_ATOM(Search, "Search")
+GK_ATOM(Bookmarks, "Bookmarks")
+GK_ATOM(Home, "Home")
+GK_ATOM(Clear, "Clear")
+GK_ATOM(VolumeUp, "VolumeUp")
+GK_ATOM(VolumeDown, "VolumeDown")
+GK_ATOM(NextTrack, "NextTrack")
+GK_ATOM(PreviousTrack, "PreviousTrack")
+GK_ATOM(MediaStop, "MediaStop")
+GK_ATOM(PlayPause, "PlayPause")
+GK_ATOM(Menu, "Menu")
+GK_ATOM(New, "New")
+GK_ATOM(Open, "Open")
+GK_ATOM(Close, "Close")
+GK_ATOM(Save, "Save")
+GK_ATOM(Find, "Find")
+GK_ATOM(Help, "Help")
+GK_ATOM(Print, "Print")
+GK_ATOM(SendMail, "SendMail")
+GK_ATOM(ForwardMail, "ForwardMail")
+GK_ATOM(ReplyToMail, "ReplyToMail")
+
+// Scroll origins (these are used in various scrolling functions in
+// nsIScrollableFrame and ScrollFrameHelper). These are divided into two lists
+// - origins in the first one have smooth-scrolling prefs associated with them,
+// under the "general.smoothScroll.<origin>.*" pref branch. Origins in the
+// second one do not.
+GK_ATOM(mouseWheel, "mouseWheel") // For discrete wheel events (e.g. not OSX magic mouse)
+GK_ATOM(pixels, "pixels")
+GK_ATOM(lines, "lines")
+GK_ATOM(pages, "pages")
+GK_ATOM(scrollbars, "scrollbars")
+GK_ATOM(other, "other")
+// Scroll origins without smooth-scrolling prefs
+GK_ATOM(apz, "apz")
+GK_ATOM(restore, "restore")
+
+#ifdef ACCESSIBILITY
+GK_ATOM(alert, "alert")
+GK_ATOM(alertdialog, "alertdialog")
+GK_ATOM(application, "application")
+GK_ATOM(aria_activedescendant, "aria-activedescendant")
+GK_ATOM(aria_atomic, "aria-atomic")
+GK_ATOM(aria_autocomplete, "aria-autocomplete")
+GK_ATOM(aria_busy, "aria-busy")
+GK_ATOM(aria_checked, "aria-checked")
+GK_ATOM(aria_colcount, "aria-colcount")
+GK_ATOM(aria_colindex, "aria-colindex")
+GK_ATOM(aria_controls, "aria-controls")
+GK_ATOM(aria_describedby, "aria-describedby")
+GK_ATOM(aria_details, "aria-details")
+GK_ATOM(aria_disabled, "aria-disabled")
+GK_ATOM(aria_dropeffect, "aria-dropeffect")
+GK_ATOM(aria_errormessage, "aria-errormessage")
+GK_ATOM(aria_expanded, "aria-expanded")
+GK_ATOM(aria_flowto, "aria-flowto")
+GK_ATOM(aria_grabbed, "aria-grabbed")
+GK_ATOM(aria_haspopup, "aria-haspopup")
+GK_ATOM(aria_hidden, "aria-hidden")
+GK_ATOM(aria_invalid, "aria-invalid")
+GK_ATOM(aria_label, "aria-label")
+GK_ATOM(aria_labelledby, "aria-labelledby")
+GK_ATOM(aria_level, "aria-level")
+GK_ATOM(aria_live, "aria-live")
+GK_ATOM(aria_modal, "aria-modal")
+GK_ATOM(aria_multiline, "aria-multiline")
+GK_ATOM(aria_multiselectable, "aria-multiselectable")
+GK_ATOM(aria_orientation, "aria-orientation")
+GK_ATOM(aria_owns, "aria-owns")
+GK_ATOM(aria_posinset, "aria-posinset")
+GK_ATOM(aria_pressed, "aria-pressed")
+GK_ATOM(aria_readonly, "aria-readonly")
+GK_ATOM(aria_relevant, "aria-relevant")
+GK_ATOM(aria_required, "aria-required")
+GK_ATOM(aria_rowcount, "aria-rowcount")
+GK_ATOM(aria_rowindex, "aria-rowindex")
+GK_ATOM(aria_selected, "aria-selected")
+GK_ATOM(aria_setsize, "aria-setsize")
+GK_ATOM(aria_sort, "aria-sort")
+GK_ATOM(aria_valuenow, "aria-valuenow")
+GK_ATOM(aria_valuemin, "aria-valuemin")
+GK_ATOM(aria_valuemax, "aria-valuemax")
+GK_ATOM(aria_valuetext, "aria-valuetext")
+GK_ATOM(AreaFrame, "AreaFrame")
+GK_ATOM(auto_generated, "auto-generated")
+GK_ATOM(banner, "banner")
+GK_ATOM(checkable, "checkable")
+GK_ATOM(choices, "choices")
+GK_ATOM(columnheader, "columnheader")
+GK_ATOM(complementary, "complementary")
+GK_ATOM(containerAtomic, "container-atomic")
+GK_ATOM(containerBusy, "container-busy")
+GK_ATOM(containerLive, "container-live")
+GK_ATOM(containerLiveRole, "container-live-role")
+GK_ATOM(containerRelevant, "container-relevant")
+GK_ATOM(contentinfo, "contentinfo")
+GK_ATOM(cycles, "cycles")
+GK_ATOM(datatable, "datatable")
+GK_ATOM(eventFromInput, "event-from-input")
+GK_ATOM(feed, "feed")
+GK_ATOM(grammar, "grammar")
+GK_ATOM(gridcell, "gridcell")
+GK_ATOM(heading, "heading")
+GK_ATOM(hitregion, "hitregion")
+GK_ATOM(InlineBlockFrame, "InlineBlockFrame")
+GK_ATOM(inlinevalue, "inline")
+GK_ATOM(invalid, "invalid")
+GK_ATOM(item, "item")
+GK_ATOM(itemset, "itemset")
+GK_ATOM(lineNumber, "line-number")
+GK_ATOM(linkedPanel, "linkedpanel")
+GK_ATOM(live, "live")
+GK_ATOM(menuitemcheckbox, "menuitemcheckbox")
+GK_ATOM(menuitemradio, "menuitemradio")
+GK_ATOM(mixed, "mixed")
+GK_ATOM(multiline, "multiline")
+GK_ATOM(navigation, "navigation")
+GK_ATOM(polite, "polite")
+GK_ATOM(posinset, "posinset")
+GK_ATOM(presentation, "presentation")
+GK_ATOM(progressbar, "progressbar")
+GK_ATOM(region, "region")
+GK_ATOM(rowgroup, "rowgroup")
+GK_ATOM(rowheader, "rowheader")
+GK_ATOM(search, "search")
+GK_ATOM(searchbox, "searchbox")
+GK_ATOM(select1, "select1")
+GK_ATOM(setsize, "setsize")
+GK_ATOM(spelling, "spelling")
+GK_ATOM(spinbutton, "spinbutton")
+GK_ATOM(status, "status")
+GK_ATOM(_switch, "switch")
+GK_ATOM(tableCellIndex, "table-cell-index")
+GK_ATOM(tablist, "tablist")
+GK_ATOM(textIndent, "text-indent")
+GK_ATOM(textInputType, "text-input-type")
+GK_ATOM(textLineThroughColor, "text-line-through-color")
+GK_ATOM(textLineThroughStyle, "text-line-through-style")
+GK_ATOM(textPosition, "text-position")
+GK_ATOM(textUnderlineColor, "text-underline-color")
+GK_ATOM(textUnderlineStyle, "text-underline-style")
+GK_ATOM(timer, "timer")
+GK_ATOM(toolbarname, "toolbarname")
+GK_ATOM(toolbarseparator, "toolbarseparator")
+GK_ATOM(toolbarspacer, "toolbarspacer")
+GK_ATOM(toolbarspring, "toolbarspring")
+GK_ATOM(treegrid, "treegrid")
+GK_ATOM(_undefined, "undefined")
+GK_ATOM(xmlroles, "xml-roles")
+
+// MathML xml roles
+GK_ATOM(close_fence, "close-fence")
+GK_ATOM(denominator, "denominator")
+GK_ATOM(numerator, "numerator")
+GK_ATOM(open_fence, "open-fence")
+GK_ATOM(overscript, "overscript")
+GK_ATOM(presubscript, "presubscript")
+GK_ATOM(presuperscript, "presuperscript")
+GK_ATOM(root_index, "root-index")
+GK_ATOM(subscript, "subscript")
+GK_ATOM(superscript, "superscript")
+GK_ATOM(underscript, "underscript")
+#endif
+
+#ifdef MOZ_WEBSPEECH
+GK_ATOM(onaudiostart, "onaudiostart")
+GK_ATOM(onaudioend, "onaudioend")
+GK_ATOM(onsoundstart, "onsoundstart")
+GK_ATOM(onsoundend, "onsoundend")
+GK_ATOM(onspeechstart, "onspeechstart")
+GK_ATOM(onspeechend, "onspeechend")
+GK_ATOM(onresult, "onresult")
+GK_ATOM(onnomatch, "onnomatch")
+GK_ATOM(onresume, "onresume")
+GK_ATOM(onmark, "onmark")
+GK_ATOM(onboundary, "onboundary")
+#endif
+
+// Contextual Identity / Containers
+GK_ATOM(usercontextid, "usercontextid")
+
+// Namespaces
+GK_ATOM(nsuri_xmlns, "http://www.w3.org/2000/xmlns/")
+GK_ATOM(nsuri_xml, "http://www.w3.org/XML/1998/namespace")
+GK_ATOM(nsuri_xhtml, "http://www.w3.org/1999/xhtml")
+GK_ATOM(nsuri_xlink, "http://www.w3.org/1999/xlink")
+GK_ATOM(nsuri_xslt, "http://www.w3.org/1999/XSL/Transform")
+GK_ATOM(nsuri_xbl, "http://www.mozilla.org/xbl")
+GK_ATOM(nsuri_mathml, "http://www.w3.org/1998/Math/MathML")
+GK_ATOM(nsuri_rdf, "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+GK_ATOM(nsuri_xul, "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
+GK_ATOM(nsuri_svg, "http://www.w3.org/2000/svg")
+
+// MSE
+GK_ATOM(onsourceopen, "onsourceopen")
+GK_ATOM(onsourceended, "onsourceended")
+GK_ATOM(onsourceclosed, "onsourceclosed")
+GK_ATOM(onupdatestart, "onupdatestart")
+GK_ATOM(onupdate, "onupdate")
+GK_ATOM(onupdateend, "onupdateend")
+GK_ATOM(onaddsourcebuffer, "onaddsourcebuffer")
+GK_ATOM(onremovesourcebuffer, "onremovesourcebuffer")
diff --git a/dom/base/nsGkAtoms.cpp b/dom/base/nsGkAtoms.cpp
new file mode 100644
index 000000000..24d4abddf
--- /dev/null
+++ b/dom/base/nsGkAtoms.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/. */
+
+/*
+ * This class wraps up the creation (and destruction) of the standard
+ * set of atoms used by gklayout; the atoms are created when gklayout
+ * is loaded and they are destroyed when gklayout is unloaded.
+ */
+
+#include "nsGkAtoms.h"
+#include "nsStaticAtom.h"
+
+using namespace mozilla;
+
+// define storage for all atoms
+#define GK_ATOM(name_, value_) nsIAtom* nsGkAtoms::name_;
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+
+#define GK_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+
+static const nsStaticAtom GkAtoms_info[] = {
+#define GK_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &nsGkAtoms::name_),
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+};
+
+void nsGkAtoms::AddRefAtoms()
+{
+ NS_RegisterStaticAtoms(GkAtoms_info);
+}
+
diff --git a/dom/base/nsGkAtoms.h b/dom/base/nsGkAtoms.h
new file mode 100644
index 000000000..12f51b0cf
--- /dev/null
+++ b/dom/base/nsGkAtoms.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/. */
+
+/*
+ * This class wraps up the creation (and destruction) of the standard
+ * set of atoms used by gklayout; the atoms are created when gklayout
+ * is loaded and they are destroyed when gklayout is unloaded.
+ */
+
+#ifndef nsGkAtoms_h___
+#define nsGkAtoms_h___
+
+class nsIAtom;
+
+class nsGkAtoms {
+public:
+
+ static void AddRefAtoms();
+
+ /* Declare all atoms
+
+ The atom names and values are stored in nsGkAtomList.h and
+ are brought to you by the magic of C preprocessing
+
+ Add new atoms to nsGkAtomList and all support logic will be auto-generated
+ */
+#define GK_ATOM(_name, _value) static nsIAtom* _name;
+#include "nsGkAtomList.h"
+#undef GK_ATOM
+};
+
+#endif /* nsGkAtoms_h___ */
diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
new file mode 100644
index 000000000..8ff4b84ce
--- /dev/null
+++ b/dom/base/nsGlobalWindow.cpp
@@ -0,0 +1,15189 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsGlobalWindow.h"
+
+#include <algorithm>
+
+#include "mozilla/MemoryReporting.h"
+
+// Local Includes
+#include "Navigator.h"
+#include "nsContentSecurityManager.h"
+#include "nsScreen.h"
+#include "nsHistory.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsIDOMStorageManager.h"
+#include "mozilla/dom/DOMStorage.h"
+#include "mozilla/dom/IdleRequest.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/dom/Timeout.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+#include "mozilla/dom/WindowOrientationObserver.h"
+#endif
+#include "nsDOMOfflineResourceList.h"
+#include "nsError.h"
+#include "nsIIdleService.h"
+#include "nsISizeOfEventTarget.h"
+#include "nsDOMJSUtils.h"
+#include "nsArrayUtils.h"
+#include "nsIDOMWindowCollection.h"
+#include "nsDOMWindowList.h"
+#include "mozilla/dom/WakeLock.h"
+#include "mozilla/dom/power/PowerManagerService.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptTimeoutHandler.h"
+#include "nsITimeoutHandler.h"
+#include "nsIController.h"
+#include "nsScriptNameSpaceManager.h"
+#include "nsISlowScriptDebug.h"
+#include "nsWindowMemoryReporter.h"
+#include "WindowNamedPropertiesHandler.h"
+#include "nsFrameSelection.h"
+#include "nsNetUtil.h"
+#include "nsVariant.h"
+#include "nsPrintfCString.h"
+
+// Helper Classes
+#include "nsJSUtils.h"
+#include "jsapi.h" // for JSAutoRequest
+#include "jswrapper.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsReadableUtils.h"
+#include "nsDOMClassInfo.h"
+#include "nsJSEnvironment.h"
+#include "ScriptSettings.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Likely.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/workers/Workers.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsJSPrincipals.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Debug.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/ProcessHangMonitor.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/dom/TabGroup.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 "nsIDocCharset.h"
+#include "nsIDocument.h"
+#include "Crypto.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMOfflineResourceList.h"
+#include "nsDOMString.h"
+#include "nsIEmbeddingSiteWindow.h"
+#include "nsThreadUtils.h"
+#include "nsILoadContext.h"
+#include "nsIPresShell.h"
+#include "nsIScrollableFrame.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsISelectionController.h"
+#include "nsISelection.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 "nsIWindowMediator.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 "nsIControllers.h"
+#include "nsIControllerContext.h"
+#include "nsGlobalWindowCommands.h"
+#include "nsQueryObject.h"
+#include "nsContentUtils.h"
+#include "nsCSSProps.h"
+#include "nsIDOMFileList.h"
+#include "nsIURIFixup.h"
+#ifndef DEBUG
+#include "nsIAppStartup.h"
+#include "nsToolkitCompsCID.h"
+#endif
+#include "nsCDefaultURIFixup.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "nsIObserverService.h"
+#include "nsFocusManager.h"
+#include "nsIXULWindow.h"
+#include "nsITimedChannel.h"
+#include "nsServiceManagerUtils.h"
+#ifdef MOZ_XUL
+#include "nsIDOMXULControlElement.h"
+#include "nsMenuPopupFrame.h"
+#endif
+#include "mozilla/dom/CustomEvent.h"
+#include "nsIJARChannel.h"
+#include "nsIScreenManager.h"
+#include "nsIEffectiveTLDService.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"
+
+// XXX An unfortunate dependency exists here (two XUL files).
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMXULCommandDispatcher.h"
+
+#include "nsBindingManager.h"
+#include "nsXBLService.h"
+
+// used for popup blocking, needs to be converted to something
+// belonging to the back-end like nsIContentPolicy
+#include "nsIPopupWindowManager.h"
+
+#include "nsIDragService.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Selection.h"
+#include "nsFrameLoader.h"
+#include "nsISupportsPrimitives.h"
+#include "nsXPCOMCID.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "prprf.h"
+
+#include "mozilla/dom/IDBFactory.h"
+#include "mozilla/dom/MessageChannel.h"
+#include "mozilla/dom/Promise.h"
+
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadManager.h"
+#endif
+
+#include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/VREventObserver.h"
+
+#include "nsRefreshDriver.h"
+#include "Layers.h"
+
+#include "mozilla/AddonPathService.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 "TimeChangeObserver.h"
+#include "mozilla/dom/AudioContext.h"
+#include "mozilla/dom/BrowserElementDictionariesBinding.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/MozSelfSupportBinding.h"
+#include "mozilla/dom/PopStateEvent.h"
+#include "mozilla/dom/PopupBlockedEvent.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "nsITabChild.h"
+#include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/NavigatorBinding.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/ServiceWorkerRegistration.h"
+#include "mozilla/dom/U2F.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
+#include "mozilla/dom/Worklet.h"
+#ifdef HAVE_SIDEBAR
+#include "mozilla/dom/ExternalBinding.h"
+#endif
+
+#ifdef MOZ_WEBSPEECH
+#include "mozilla/dom/SpeechSynthesis.h"
+#endif
+
+#ifdef MOZ_B2G
+#include "nsPISocketTransportService.h"
+#endif
+
+// Apple system headers seem to have a check() macro. <sigh>
+#ifdef check
+class nsIScriptTimeoutHandler;
+#undef check
+#endif // check
+#include "AccessCheck.h"
+
+#ifdef ANDROID
+#include <android/log.h>
+#endif
+
+#ifdef XP_WIN
+#include <process.h>
+#define getpid _getpid
+#else
+#include <unistd.h> // for getpid()
+#endif
+
+static const char kStorageEnabled[] = "dom.storage.enabled";
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+using mozilla::BasePrincipal;
+using mozilla::PrincipalOriginAttributes;
+using mozilla::TimeStamp;
+using mozilla::TimeDuration;
+using mozilla::dom::cache::CacheStorage;
+
+static LazyLogModule gDOMLeakPRLog("DOMLeak");
+
+nsGlobalWindow::WindowByIdTable *nsGlobalWindow::sWindowsById = nullptr;
+bool nsGlobalWindow::sWarnedAboutWindowInternal = false;
+bool nsGlobalWindow::sIdleObserversAPIFuzzTimeDisabled = false;
+
+static int32_t gRefCnt = 0;
+static int32_t gOpenPopupSpamCount = 0;
+static PopupControlState gPopupControlState = openAbused;
+static int32_t gRunningTimeoutDepth = 0;
+static bool gMouseDown = false;
+static bool gDragServiceDisabled = false;
+static FILE *gDumpFile = nullptr;
+static uint32_t gSerialCounter = 0;
+static TimeStamp gLastRecordedRecentTimeouts;
+#define STATISTICS_INTERVAL (30 * PR_MSEC_PER_SEC)
+
+#ifdef DEBUG_jst
+int32_t gTimeoutCnt = 0;
+#endif
+
+#if defined(DEBUG_bryner) || defined(DEBUG_chb)
+#define DEBUG_PAGE_CACHE
+#endif
+
+#define DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added"
+
+// The default shortest interval/timeout we permit
+#define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms
+#define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms
+static int32_t gMinTimeoutValue;
+static int32_t gMinBackgroundTimeoutValue;
+inline int32_t
+nsGlobalWindow::DOMMinTimeoutValue() const {
+ // First apply any back pressure delay that might be in effect.
+ int32_t value = std::max(mBackPressureDelayMS, 0);
+ // Don't use the background timeout value when there are audio contexts
+ // present, so that baackground audio can keep running smoothly. (bug 1181073)
+ bool isBackground = mAudioContexts.IsEmpty() &&
+ (!mOuterWindow || mOuterWindow->IsBackground());
+ return
+ std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, value);
+}
+
+// The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
+// uses 5.
+#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
+
+// 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
+
+// The interval at which we execute idle callbacks
+static uint32_t gThrottledIdlePeriodLength;
+
+#define DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH 10000
+
+#define FORWARD_TO_OUTER(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ if (IsInnerWindow()) { \
+ nsGlobalWindow *outer = GetOuterWindowInternal(); \
+ if (!AsInner()->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
+
+#define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval) \
+ PR_BEGIN_MACRO \
+ MOZ_RELEASE_ASSERT(IsInnerWindow()); \
+ nsGlobalWindow *outer = GetOuterWindowInternal(); \
+ if (MOZ_LIKELY(AsInner()->HasActiveDocument())) { \
+ return outer->method args; \
+ } \
+ if (!outer) { \
+ NS_WARNING("No outer window available!"); \
+ errorresult.Throw(NS_ERROR_NOT_INITIALIZED); \
+ } else { \
+ errorresult.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO); \
+ } \
+ return err_rval; \
+ PR_END_MACRO
+
+#define FORWARD_TO_OUTER_VOID(method, args) \
+ PR_BEGIN_MACRO \
+ if (IsInnerWindow()) { \
+ nsGlobalWindow *outer = GetOuterWindowInternal(); \
+ if (!AsInner()->HasActiveDocument()) { \
+ NS_WARNING(outer ? \
+ "Inner window does not have active document." : \
+ "No outer window available!"); \
+ return; \
+ } \
+ outer->method args; \
+ return; \
+ } \
+ PR_END_MACRO
+
+#define FORWARD_TO_OUTER_CHROME(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ if (IsInnerWindow()) { \
+ nsGlobalWindow *outer = GetOuterWindowInternal(); \
+ if (!AsInner()->HasActiveDocument()) { \
+ NS_WARNING(outer ? \
+ "Inner window does not have active document." : \
+ "No outer window available!"); \
+ return err_rval; \
+ } \
+ return ((nsGlobalChromeWindow *)outer)->method args; \
+ } \
+ PR_END_MACRO
+
+#define FORWARD_TO_INNER_CHROME(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ if (IsOuterWindow()) { \
+ if (!mInnerWindow) { \
+ NS_WARNING("No inner window available!"); \
+ return err_rval; \
+ } \
+ return ((nsGlobalChromeWindow *)nsGlobalWindow::Cast(mInnerWindow))->method args; \
+ } \
+ PR_END_MACRO
+
+#define FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ if (IsInnerWindow()) { \
+ nsGlobalWindow *outer = GetOuterWindowInternal(); \
+ if (!AsInner()->HasActiveDocument()) { \
+ NS_WARNING(outer ? \
+ "Inner window does not have active document." : \
+ "No outer window available!"); \
+ return err_rval; \
+ } \
+ return ((nsGlobalModalWindow *)outer)->method args; \
+ } \
+ PR_END_MACRO
+
+#define FORWARD_TO_INNER(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ if (IsOuterWindow()) { \
+ if (!mInnerWindow) { \
+ NS_WARNING("No inner window available!"); \
+ return err_rval; \
+ } \
+ return GetCurrentInnerWindowInternal()->method args; \
+ } \
+ PR_END_MACRO
+
+#define FORWARD_TO_INNER_MODAL_CONTENT_WINDOW(method, args, err_rval) \
+ PR_BEGIN_MACRO \
+ if (IsOuterWindow()) { \
+ if (!mInnerWindow) { \
+ NS_WARNING("No inner window available!"); \
+ return err_rval; \
+ } \
+ return ((nsGlobalModalWindow*)GetCurrentInnerWindowInternal())->method args; \
+ } \
+ PR_END_MACRO
+
+#define FORWARD_TO_INNER_VOID(method, args) \
+ PR_BEGIN_MACRO \
+ if (IsOuterWindow()) { \
+ 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 (IsOuterWindow()) { \
+ if (!mInnerWindow) { \
+ if (mIsClosed) { \
+ return err_rval; \
+ } \
+ nsCOMPtr<nsIDocument> kungFuDeathGrip = GetDoc(); \
+ ::mozilla::Unused << kungFuDeathGrip; \
+ if (!mInnerWindow) { \
+ return err_rval; \
+ } \
+ } \
+ return GetCurrentInnerWindowInternal()->method args; \
+ } \
+ PR_END_MACRO
+
+// CIDs
+static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
+
+#define NETWORK_UPLOAD_EVENT_NAME NS_LITERAL_STRING("moznetworkupload")
+#define NETWORK_DOWNLOAD_EVENT_NAME NS_LITERAL_STRING("moznetworkdownload")
+
+/**
+ * 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:
+ explicit nsGlobalWindowObserver(nsGlobalWindow* 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;
+ }
+
+private:
+ ~nsGlobalWindowObserver() {}
+
+ // This reference is non-owning and safe because it's cleared by
+ // nsGlobalWindow::CleanUp().
+ nsGlobalWindow* MOZ_NON_OWNING_REF mWindow;
+};
+
+NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor)
+
+static already_AddRefed<nsIVariant>
+CreateVoidVariant()
+{
+ RefPtr<nsVariantCC> writable = new nsVariantCC();
+ writable->SetAsVoid();
+ return writable.forget();
+}
+
+nsresult
+DialogValueHolder::Get(nsIPrincipal* aSubject, nsIVariant** aResult)
+{
+ nsCOMPtr<nsIVariant> result;
+ if (aSubject->SubsumesConsideringDomain(mOrigin)) {
+ result = mValue;
+ } else {
+ result = CreateVoidVariant();
+ }
+ result.forget(aResult);
+ return NS_OK;
+}
+
+void
+DialogValueHolder::Get(JSContext* aCx, JS::Handle<JSObject*> aScope,
+ nsIPrincipal* aSubject,
+ JS::MutableHandle<JS::Value> aResult,
+ mozilla::ErrorResult& aError)
+{
+ if (aSubject->Subsumes(mOrigin)) {
+ aError = nsContentUtils::XPConnect()->VariantToJS(aCx, aScope,
+ mValue, aResult);
+ } else {
+ aResult.setUndefined();
+ }
+}
+
+void
+nsGlobalWindow::PostThrottledIdleCallback()
+{
+ AssertIsOnMainThread();
+
+ if (mThrottledIdleRequestCallbacks.isEmpty())
+ return;
+
+ RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
+ // ownership transferred from mThrottledIdleRequestCallbacks to
+ // mIdleRequestCallbacks
+ mIdleRequestCallbacks.insertBack(request);
+ NS_IdleDispatchToCurrentThread(request.forget());
+}
+
+/* static */ void
+nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest,
+ IdleRequests& aList)
+{
+ aList.insertBack(aRequest);
+ aRequest->AddRef();
+}
+
+uint32_t
+nsGlobalWindow::RequestIdleCallback(JSContext* aCx,
+ IdleRequestCallback& aCallback,
+ const IdleRequestOptions& aOptions,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+ AssertIsOnMainThread();
+
+ uint32_t handle = ++mIdleRequestCallbackCounter;
+
+ RefPtr<IdleRequest> request =
+ new IdleRequest(aCx, AsInner(), aCallback, handle);
+
+ if (aOptions.mTimeout.WasPassed()) {
+ aError = request->SetTimeout(aOptions.mTimeout.Value());
+ if (NS_WARN_IF(aError.Failed())) {
+ return 0;
+ }
+ }
+
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (outer && outer->AsOuter()->IsBackground()) {
+ // mThrottledIdleRequestCallbacks now owns request
+ InsertIdleCallbackIntoList(request, mThrottledIdleRequestCallbacks);
+
+ NS_DelayedDispatchToCurrentThread(
+ NewRunnableMethod(this, &nsGlobalWindow::PostThrottledIdleCallback),
+ 10000);
+ } else {
+ MOZ_ASSERT(mThrottledIdleRequestCallbacks.isEmpty());
+
+ // mIdleRequestCallbacks now owns request
+ InsertIdleCallbackIntoList(request, mIdleRequestCallbacks);
+
+ NS_IdleDispatchToCurrentThread(request.forget());
+ }
+
+ return handle;
+}
+
+void
+nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ for (IdleRequest* r : mIdleRequestCallbacks) {
+ if (r->Handle() == aHandle) {
+ r->Cancel();
+ break;
+ }
+ }
+}
+
+void
+nsGlobalWindow::DisableIdleCallbackRequests()
+{
+ while (!mIdleRequestCallbacks.isEmpty()) {
+ RefPtr<IdleRequest> request = mIdleRequestCallbacks.popFirst();
+ request->Cancel();
+ }
+
+ while (!mThrottledIdleRequestCallbacks.isEmpty()) {
+ RefPtr<IdleRequest> request = mThrottledIdleRequestCallbacks.popFirst();
+ request->Cancel();
+ }
+}
+
+void nsGlobalWindow::UnthrottleIdleCallbackRequests()
+{
+ AssertIsOnMainThread();
+
+ while (!mThrottledIdleRequestCallbacks.isEmpty()) {
+ RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
+ mIdleRequestCallbacks.insertBack(request);
+ NS_IdleDispatchToCurrentThread(request.forget());
+ }
+}
+
+
+namespace mozilla {
+namespace dom {
+extern uint64_t
+NextWindowID();
+} // namespace dom
+} // namespace mozilla
+
+template<class T>
+nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow)
+: mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0),
+ mRunningTimeout(nullptr), mMutationBits(0), mIsDocumentLoaded(false),
+ mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr),
+ mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
+ mMayHaveMouseEnterLeaveEventListener(false),
+ mMayHavePointerEnterLeaveEventListener(false),
+ mInnerObjectsFreed(false),
+ mIsModalContentWindow(false),
+ mIsActive(false), mIsBackground(false),
+ mMediaSuspend(Preferences::GetBool("media.block-autoplay-until-in-foreground", true) ?
+ nsISuspendedTypes::SUSPENDED_BLOCK : nsISuspendedTypes::NONE_SUSPENDED),
+ mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false),
+ mDesktopModeViewport(false), mIsRootOuterWindow(false), mInnerWindow(nullptr),
+ mOuterWindow(aOuterWindow),
+ // Make sure no actual window ends up with mWindowID == 0
+ mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
+ mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false)
+ {}
+
+template<class T>
+nsPIDOMWindow<T>::~nsPIDOMWindow() {}
+
+/* static */
+nsPIDOMWindowOuter*
+nsPIDOMWindowOuter::GetFromCurrentInner(nsPIDOMWindowInner* aInner)
+{
+ if (!aInner) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowOuter* outer = aInner->GetOuterWindow();
+ if (!outer || outer->GetCurrentInnerWindow() != aInner) {
+ return nullptr;
+ }
+
+ return outer;
+}
+
+// DialogValueHolder CC goop.
+NS_IMPL_CYCLE_COLLECTION(DialogValueHolder, mValue)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DialogValueHolder)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DialogValueHolder)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DialogValueHolder)
+
+//*****************************************************************************
+// nsOuterWindowProxy: Outer Window Proxy
+//*****************************************************************************
+
+class nsOuterWindowProxy : public js::Wrapper
+{
+public:
+ constexpr nsOuterWindowProxy() : js::Wrapper(0) { }
+
+ virtual bool finalizeInBackground(const JS::Value& priv) const override {
+ return false;
+ }
+
+ // Standard internal methods
+ virtual bool getOwnPropertyDescriptor(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc)
+ const override;
+ virtual bool defineProperty(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult &result) const override;
+ virtual bool ownPropertyKeys(JSContext *cx,
+ JS::Handle<JSObject*> proxy,
+ JS::AutoIdVector &props) const override;
+ virtual bool delete_(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::ObjectOpResult &result) const override;
+
+ virtual bool getPrototypeIfOrdinary(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ bool* isOrdinary,
+ JS::MutableHandle<JSObject*> protop) const override;
+
+ virtual bool enumerate(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> vp) const override;
+ virtual bool preventExtensions(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::ObjectOpResult& result) const override;
+ virtual bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible)
+ const override;
+ virtual bool has(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, bool *bp) const override;
+ virtual bool get(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp) const override;
+ virtual 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
+ virtual bool getPropertyDescriptor(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc)
+ const override;
+ virtual bool hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, bool *bp) const override;
+ virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::AutoIdVector &props) const override;
+ virtual const char *className(JSContext *cx,
+ JS::Handle<JSObject*> wrapper) const override;
+
+ virtual void finalize(JSFreeOp *fop, JSObject *proxy) const override;
+
+ virtual bool isCallable(JSObject *obj) const override {
+ return false;
+ }
+ virtual bool isConstructor(JSObject *obj) const override {
+ return false;
+ }
+
+ virtual bool watch(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::Handle<JSObject*> callable) const override;
+ virtual bool unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id) const override;
+
+ static void ObjectMoved(JSObject *obj, const JSObject *old);
+
+ static const nsOuterWindowProxy singleton;
+
+protected:
+ static nsGlobalWindow* GetOuterWindow(JSObject *proxy)
+ {
+ nsGlobalWindow* outerWindow = nsGlobalWindow::FromSupports(
+ static_cast<nsISupports*>(js::GetProxyExtra(proxy, 0).toPrivate()));
+ MOZ_ASSERT_IF(outerWindow, outerWindow->IsOuterWindow());
+ return outerWindow;
+ }
+
+ // 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.
+ already_AddRefed<nsPIDOMWindowOuter>
+ GetSubframeWindow(JSContext *cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id) const;
+
+ bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
+ JS::AutoIdVector &props) const;
+};
+
+static const js::ClassExtension OuterWindowProxyClassExtension = PROXY_MAKE_EXT(
+ nsOuterWindowProxy::ObjectMoved
+);
+
+const js::Class OuterWindowProxyClass = PROXY_CLASS_WITH_EXT(
+ "Proxy",
+ 0, /* additional class flags */
+ &OuterWindowProxyClassExtension);
+
+const char *
+nsOuterWindowProxy::className(JSContext *cx, JS::Handle<JSObject*> proxy) const
+{
+ MOZ_ASSERT(js::IsProxy(proxy));
+
+ return "Window";
+}
+
+void
+nsOuterWindowProxy::finalize(JSFreeOp *fop, JSObject *proxy) const
+{
+ nsGlobalWindow* outerWindow = GetOuterWindow(proxy);
+ if (outerWindow) {
+ outerWindow->ClearWrapper();
+
+ // 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::getPropertyDescriptor(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc) const
+{
+ // The only thing we can do differently from js::Wrapper is shadow stuff with
+ // our indexed properties, so we can just try getOwnPropertyDescriptor and if
+ // that gives us nothing call on through to js::Wrapper.
+ desc.object().set(nullptr);
+ if (!getOwnPropertyDescriptor(cx, proxy, id, desc)) {
+ return false;
+ }
+
+ if (desc.object()) {
+ return true;
+ }
+
+ return js::Wrapper::getPropertyDescriptor(cx, proxy, id, desc);
+}
+
+bool
+nsOuterWindowProxy::getOwnPropertyDescriptor(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc)
+ const
+{
+ bool found;
+ if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) {
+ return false;
+ }
+ if (found) {
+ FillPropertyDescriptor(desc, proxy, true);
+ return true;
+ }
+ // else fall through to js::Wrapper
+
+ // When we change this to always claim the property is configurable (bug
+ // 1178639), update the comments in nsOuterWindowProxy::defineProperty
+ // accordingly.
+ return js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
+}
+
+bool
+nsOuterWindowProxy::defineProperty(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult &result) const
+{
+ if (IsArrayIndex(GetArrayIndexFromId(cx, 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();
+ }
+
+#ifndef RELEASE_OR_BETA // To be turned on in bug 1178638.
+ // For now, allow chrome code to define non-configurable properties
+ // on windows, until we sort out what exactly the addon SDK is
+ // doing. In the meantime, this still allows us to test web compat
+ // behavior.
+ if (desc.hasConfigurable() && !desc.configurable() &&
+ !nsContentUtils::IsCallerChrome()) {
+ return ThrowErrorMessage(cx, MSG_DEFINE_NON_CONFIGURABLE_PROP_ON_WINDOW);
+ }
+
+ // Note that if hasConfigurable() is false we do NOT want to
+ // setConfigurable(true). That would make this code:
+ //
+ // var x;
+ // window.x = 5;
+ //
+ // fail, because the JS engine ends up converting the assignment into a define
+ // with !hasConfigurable(), but the var actually declared a non-configurable
+ // property on our underlying Window object, so the set would fail if we
+ // forced setConfigurable(true) here. What we want to do instead is change
+ // getOwnPropertyDescriptor to always claim configurable. See bug 1178639.
+#endif
+
+ return js::Wrapper::defineProperty(cx, proxy, id, desc, result);
+}
+
+bool
+nsOuterWindowProxy::ownPropertyKeys(JSContext *cx,
+ JS::Handle<JSObject*> proxy,
+ JS::AutoIdVector &props) const
+{
+ // Just our indexed stuff followed by our "normal" own property names.
+ if (!AppendIndexedPropertyNames(cx, proxy, props)) {
+ return false;
+ }
+
+ JS::AutoIdVector innerProps(cx);
+ if (!js::Wrapper::ownPropertyKeys(cx, proxy, innerProps)) {
+ return false;
+ }
+ return js::AppendUnique(cx, props, innerProps);
+}
+
+bool
+nsOuterWindowProxy::delete_(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::ObjectOpResult &result) const
+{
+ if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
+ // Fail (which means throw if strict, else return false).
+ return result.failCantDeleteWindowElement();
+ }
+
+ if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
+ // Indexed, but not supported. Spec says return true.
+ return result.succeed();
+ }
+
+ return js::Wrapper::delete_(cx, proxy, id, result);
+}
+
+bool
+nsOuterWindowProxy::getPrototypeIfOrdinary(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ bool* isOrdinary,
+ JS::MutableHandle<JSObject*> protop) const
+{
+ // Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
+ //
+ // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof
+ //
+ // We nonetheless can implement it with a static [[Prototype]], because
+ // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
+ // all non-ordinary behavior.
+ //
+ // But from a spec point of view, it's the exact same object in both cases --
+ // only the observer's changed. So this getPrototypeIfOrdinary trap on the
+ // non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
+ // usually means ordinary.
+ *isOrdinary = false;
+ return true;
+}
+
+bool
+nsOuterWindowProxy::preventExtensions(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::ObjectOpResult& result) const
+{
+ // If [[Extensible]] could be false, then navigating a window could navigate
+ // to a window that's [[Extensible]] after being at one that wasn't: an
+ // invariant violation. So never change a window's extensibility.
+ return result.failCantPreventExtensions();
+}
+
+bool
+nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy,
+ bool *extensible) const
+{
+ // See above.
+ *extensible = true;
+ return true;
+}
+
+bool
+nsOuterWindowProxy::has(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, bool *bp) const
+{
+ if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
+ *bp = true;
+ return true;
+ }
+
+ return js::Wrapper::has(cx, proxy, id, bp);
+}
+
+bool
+nsOuterWindowProxy::hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, bool *bp) const
+{
+ if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
+ *bp = true;
+ return true;
+ }
+
+ 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 == nsDOMClassInfo::sWrappedJSObject_id &&
+ xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
+ vp.set(JS::ObjectValue(*proxy));
+ return true;
+ }
+
+ bool found;
+ if (!GetSubframeWindow(cx, proxy, id, vp, found)) {
+ return false;
+ }
+ if (found) {
+ return true;
+ }
+ // Else fall through to js::Wrapper
+
+ return js::Wrapper::get(cx, proxy, receiver, id, 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 (IsArrayIndex(GetArrayIndexFromId(cx, 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();
+ }
+
+ return js::Wrapper::set(cx, proxy, id, v, receiver, result);
+}
+
+bool
+nsOuterWindowProxy::getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::AutoIdVector &props) const
+{
+ // BaseProxyHandler::keys seems to do what we want here: call
+ // ownPropertyKeys and then filter out the non-enumerable properties.
+ return js::BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
+}
+
+bool
+nsOuterWindowProxy::enumerate(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> objp) const
+{
+ // BaseProxyHandler::enumerate seems to do what we want here: fall
+ // back on the property names returned from js::GetPropertyKeys()
+ return js::BaseProxyHandler::enumerate(cx, proxy, objp);
+}
+
+bool
+nsOuterWindowProxy::GetSubframeWindow(JSContext *cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp,
+ bool& found) const
+{
+ nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id);
+ if (!frame) {
+ found = false;
+ return true;
+ }
+
+ found = true;
+ // Just return the window's global
+ nsGlobalWindow* global = nsGlobalWindow::Cast(frame);
+ frame->EnsureInnerWindow();
+ JSObject* obj = global->FastGetGlobalJSObject();
+ // This null check fixes a hard-to-reproduce crash that occurs when we
+ // get here when we're mid-call to nsDocShell::Destroy. See bug 640904
+ // comment 105.
+ if (MOZ_UNLIKELY(!obj)) {
+ return xpc::Throw(cx, NS_ERROR_FAILURE);
+ }
+ JS::ExposeObjectToActiveJS(obj);
+ vp.setObject(*obj);
+ return JS_WrapValue(cx, vp);
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsOuterWindowProxy::GetSubframeWindow(JSContext *cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id) const
+{
+ uint32_t index = GetArrayIndexFromId(cx, id);
+ if (!IsArrayIndex(index)) {
+ return nullptr;
+ }
+
+ nsGlobalWindow* win = GetOuterWindow(proxy);
+ MOZ_ASSERT(win->IsOuterWindow());
+ return win->IndexedGetterOuter(index);
+}
+
+bool
+nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy,
+ JS::AutoIdVector &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(INT_TO_JSID(i))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+nsOuterWindowProxy::watch(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::Handle<JSObject*> callable) const
+{
+ return js::WatchGuts(cx, proxy, id, callable);
+}
+
+bool
+nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id) const
+{
+ return js::UnwatchGuts(cx, proxy, id);
+}
+
+void
+nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old)
+{
+ nsGlobalWindow* outerWindow = GetOuterWindow(obj);
+ if (outerWindow) {
+ outerWindow->UpdateWrapper(obj, old);
+ }
+}
+
+const nsOuterWindowProxy
+nsOuterWindowProxy::singleton;
+
+class nsChromeOuterWindowProxy : public nsOuterWindowProxy
+{
+public:
+ constexpr nsChromeOuterWindowProxy() : nsOuterWindowProxy() { }
+
+ virtual 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)
+{
+ JSAutoCompartment ac(cx, global);
+ MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(global) == global);
+
+ js::WrapperOptions options;
+ options.setClass(&OuterWindowProxyClass);
+ options.setSingleton(true);
+ JSObject *obj = js::Wrapper::New(cx, global,
+ isChrome ? &nsChromeOuterWindowProxy::singleton
+ : &nsOuterWindowProxy::singleton,
+ options);
+ MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj));
+ return obj;
+}
+
+namespace {
+
+// The maximum number of timer callbacks we will try to run in a single event
+// loop runnable.
+#define DEFAULT_TARGET_MAX_CONSECUTIVE_CALLBACKS 5
+uint32_t gTargetMaxConsecutiveCallbacks;
+
+// The number of queued runnables within the TabGroup ThrottledEventQueue
+// at which to begin applying back pressure to the window.
+#define DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE 5000
+static uint32_t gThrottledEventQueueBackPressure;
+
+// The amount of delay to apply to timers when back pressure is triggered.
+// As the length of the ThrottledEventQueue grows delay is increased. The
+// delay is scaled such that every kThrottledEventQueueBackPressure runnables
+// in the queue equates to an additional kBackPressureDelayMS.
+#define DEFAULT_BACK_PRESSURE_DELAY_MS 250
+static uint32_t gBackPressureDelayMS;
+
+// This defines a limit for how much the delay must drop before we actually
+// reduce back pressure throttle amount. This makes the throttle delay
+// a bit "sticky" once we enter back pressure.
+#define DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS 1000
+static uint32_t gBackPressureDelayReductionThresholdMS;
+
+// The minimum delay we can reduce back pressure to before we just floor
+// the value back to zero. This allows us to ensure that we can exit
+// back pressure event if there are always a small number of runnables
+// queued up.
+#define DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS 100
+static uint32_t gBackPressureDelayMinimumMS;
+
+} // anonymous namespace
+
+//*****************************************************************************
+//*** nsGlobalWindow: Object Management
+//*****************************************************************************
+
+nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
+ : nsPIDOMWindow<nsISupports>(aOuterWindow ? aOuterWindow->AsOuter() : nullptr),
+ mIdleFuzzFactor(0),
+ mIdleCallbackIndex(-1),
+ mCurrentlyIdle(false),
+ mAddActiveEventFuzzTime(true),
+ mFullScreen(false),
+ mFullscreenMode(false),
+ mIsClosed(false),
+ mInClose(false),
+ mHavePendingClose(false),
+ mHadOriginalOpener(false),
+ mOriginalOpenerWasSecureContext(false),
+ mIsPopupSpam(false),
+ mBlockScriptedClosingFlag(false),
+ mWasOffline(false),
+ mHasHadSlowScript(false),
+ mNotifyIdleObserversIdleOnThaw(false),
+ mNotifyIdleObserversActiveOnThaw(false),
+ mCreatingInnerWindow(false),
+ mIsChrome(false),
+ mCleanMessageManager(false),
+ mNeedsFocus(true),
+ mHasFocus(false),
+ mShowFocusRingForContent(false),
+ mFocusByKeyOccurred(false),
+ mHasGamepad(false),
+ mHasVREvents(false),
+#ifdef MOZ_GAMEPAD
+ mHasSeenGamepadInput(false),
+#endif
+ mNotifiedIDDestroyed(false),
+ mAllowScriptsToClose(false),
+ mTimeoutInsertionPoint(nullptr),
+ mTimeoutIdCounter(1),
+ mTimeoutFiringDepth(0),
+ mSuspendDepth(0),
+ mFreezeDepth(0),
+ mBackPressureDelayMS(0),
+ mFocusMethod(0),
+ mSerial(0),
+ mIdleCallbackTimeoutCounter(1),
+ mIdleRequestCallbackCounter(1),
+#ifdef DEBUG
+ mSetOpenerWindowCalled(false),
+#endif
+#ifdef MOZ_B2G
+ mNetworkUploadObserverEnabled(false),
+ mNetworkDownloadObserverEnabled(false),
+#endif
+ mCleanedUp(false),
+ mDialogAbuseCount(0),
+ mAreDialogsEnabled(true),
+#ifdef DEBUG
+ mIsValidatingTabGroup(false),
+#endif
+ mCanSkipCCGeneration(0)
+{
+ AssertIsOnMainThread();
+
+ nsLayoutStatics::AddRef();
+
+ // Initialize the PRCList (this).
+ PR_INIT_CLIST(this);
+
+ if (aOuterWindow) {
+ // |this| is an inner window, add this inner window to the outer
+ // window list of inners.
+ PR_INSERT_AFTER(this, aOuterWindow);
+
+ mObserver = new nsGlobalWindowObserver(this);
+ if (mObserver) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ // Watch for online/offline status changes so we can fire events. Use
+ // a strong reference.
+ os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ false);
+
+ // Watch for dom-storage2-changed so we can fire storage
+ // events. Use a strong reference.
+ os->AddObserver(mObserver, "dom-storage2-changed", false);
+ }
+
+ Preferences::AddStrongObserver(mObserver, "intl.accept_languages");
+ }
+ } else {
+ // |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.
+
+ gRefCnt++;
+
+ static bool sFirstTime = true;
+ if (sFirstTime) {
+ Preferences::AddIntVarCache(&gMinTimeoutValue,
+ "dom.min_timeout_value",
+ DEFAULT_MIN_TIMEOUT_VALUE);
+ Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue,
+ "dom.min_background_timeout_value",
+ DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE);
+ Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled,
+ "dom.idle-observers-api.fuzz_time.disabled",
+ false);
+
+ Preferences::AddUintVarCache(&gThrottledIdlePeriodLength,
+ "dom.idle_period.throttled_length",
+ DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH);
+
+ Preferences::AddUintVarCache(&gThrottledEventQueueBackPressure,
+ "dom.timeout.throttled_event_queue_back_pressure",
+ DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE);
+ Preferences::AddUintVarCache(&gBackPressureDelayMS,
+ "dom.timeout.back_pressure_delay_ms",
+ DEFAULT_BACK_PRESSURE_DELAY_MS);
+ Preferences::AddUintVarCache(&gBackPressureDelayReductionThresholdMS,
+ "dom.timeout.back_pressure_delay_reduction_threshold_ms",
+ DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS);
+ Preferences::AddUintVarCache(&gBackPressureDelayMinimumMS,
+ "dom.timeout.back_pressure_delay_minimum_ms",
+ DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS);
+
+ Preferences::AddUintVarCache(&gTargetMaxConsecutiveCallbacks,
+ "dom.timeout.max_consecutive_callbacks",
+ DEFAULT_TARGET_MAX_CONSECUTIVE_CALLBACKS);
+
+ sFirstTime = false;
+ }
+
+ if (gDumpFile == nullptr) {
+ const nsAdoptingCString& fname =
+ Preferences::GetCString("browser.dom.window.dump.file");
+ if (!fname.IsEmpty()) {
+ // if this fails to open, Dump() knows to just go to stdout
+ // on null.
+ gDumpFile = fopen(fname, "wb+");
+ } else {
+ gDumpFile = stdout;
+ }
+ }
+
+ mSerial = ++gSerialCounter;
+
+#ifdef DEBUG
+ if (!PR_GetEnv("MOZ_QUIET")) {
+ printf_stderr("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n",
+ gRefCnt,
+ static_cast<void*>(ToCanonicalSupports(this)),
+ getpid(),
+ gSerialCounter,
+ static_cast<void*>(ToCanonicalSupports(aOuterWindow)));
+ }
+#endif
+
+ if (gDOMLeakPRLog)
+ MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug,
+ ("DOMWINDOW %p created outer=%p", this, aOuterWindow));
+
+ NS_ASSERTION(sWindowsById, "Windows hash table must be created!");
+ NS_ASSERTION(!sWindowsById->Get(mWindowID),
+ "This window shouldn't be in the hash table yet!");
+ // We seem to see crashes in release builds because of null |sWindowsById|.
+ if (sWindowsById) {
+ sWindowsById->Put(mWindowID, this);
+ }
+}
+
+#ifdef DEBUG
+
+/* static */
+void
+nsGlobalWindow::AssertIsOnMainThread()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+#endif // DEBUG
+
+/* static */
+void
+nsGlobalWindow::Init()
+{
+ AssertIsOnMainThread();
+
+ NS_ASSERTION(gDOMLeakPRLog, "gDOMLeakPRLog should have been initialized!");
+
+ sWindowsById = new WindowByIdTable();
+}
+
+nsGlobalWindow::~nsGlobalWindow()
+{
+ AssertIsOnMainThread();
+
+ DisconnectEventTargetObjects();
+
+ // We have to check if sWindowsById isn't null because ::Shutdown might have
+ // been called.
+ if (sWindowsById) {
+ NS_ASSERTION(sWindowsById->Get(mWindowID),
+ "This window should be in the hash table");
+ sWindowsById->Remove(mWindowID);
+ }
+
+ --gRefCnt;
+
+#ifdef DEBUG
+ if (!PR_GetEnv("MOZ_QUIET")) {
+ 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);
+ }
+ }
+
+ nsGlobalWindow* outer = nsGlobalWindow::Cast(mOuterWindow);
+ printf_stderr("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = %s]\n",
+ gRefCnt,
+ static_cast<void*>(ToCanonicalSupports(this)),
+ getpid(),
+ mSerial,
+ static_cast<void*>(ToCanonicalSupports(outer)),
+ url.get());
+ }
+#endif
+
+ if (gDOMLeakPRLog)
+ MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug,
+ ("DOMWINDOW %p destroyed", this));
+
+ if (IsOuterWindow()) {
+ JSObject *proxy = GetWrapperPreserveColor();
+ if (proxy) {
+ js::SetProxyExtra(proxy, 0, 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.
+
+ nsGlobalWindow *w;
+ while ((w = (nsGlobalWindow *)PR_LIST_HEAD(this)) != this) {
+ PR_REMOVE_AND_INIT_LINK(w);
+ }
+
+ DropOuterWindowDocs();
+ } else {
+ Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
+ mMutationBits ? 1 : 0);
+
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ mListenerManager = nullptr;
+ }
+
+ // 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.
+ nsGlobalWindow *outer = GetOuterWindowInternal();
+ if (outer) {
+ outer->MaybeClearInnerWindow(this);
+ }
+ }
+
+ // We don't have to leave the tab group if we are an inner window.
+ if (mTabGroup && IsOuterWindow()) {
+ mTabGroup->Leave(AsOuter());
+ }
+
+ // Outer windows are always supposed to call CleanUp before letting themselves
+ // be destroyed. And while CleanUp generally seems to be intended to clean up
+ // outers, we've historically called it for both. Changing this would probably
+ // involve auditing all of the references that inners and outers can have, and
+ // separating the handling into CleanUp() and FreeInnerObjects.
+ if (IsInnerWindow()) {
+ CleanUp();
+ } else {
+ MOZ_ASSERT(mCleanedUp);
+ }
+
+ nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
+ if (ac)
+ ac->RemoveWindowAsListener(this);
+
+ nsLayoutStatics::Release();
+}
+
+void
+nsGlobalWindow::AddEventTargetObject(DOMEventTargetHelper* aObject)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ mEventTargetObjects.PutEntry(aObject);
+}
+
+void
+nsGlobalWindow::RemoveEventTargetObject(DOMEventTargetHelper* aObject)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ mEventTargetObjects.RemoveEntry(aObject);
+}
+
+void
+nsGlobalWindow::DisconnectEventTargetObjects()
+{
+ for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done();
+ iter.Next()) {
+ RefPtr<DOMEventTargetHelper> target = iter.Get()->GetKey();
+ target->DisconnectFromOwner();
+ }
+ mEventTargetObjects.Clear();
+}
+
+// static
+void
+nsGlobalWindow::ShutDown()
+{
+ AssertIsOnMainThread();
+
+ if (gDumpFile && gDumpFile != stdout) {
+ fclose(gDumpFile);
+ }
+ gDumpFile = nullptr;
+
+ delete sWindowsById;
+ sWindowsById = nullptr;
+}
+
+// static
+void
+nsGlobalWindow::CleanupCachedXBLHandlers(nsGlobalWindow* aWindow)
+{
+ if (aWindow->mCachedXBLPrototypeHandlers &&
+ aWindow->mCachedXBLPrototypeHandlers->Count() > 0) {
+ aWindow->mCachedXBLPrototypeHandlers->Clear();
+ }
+}
+
+void
+nsGlobalWindow::MaybeForgiveSpamCount()
+{
+ if (IsOuterWindow() &&
+ IsPopupSpamWindow()) {
+ SetIsPopupSpamWindow(false);
+ }
+}
+
+void
+nsGlobalWindow::SetIsPopupSpamWindow(bool aIsPopupSpam)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ mIsPopupSpam = aIsPopupSpam;
+ if (aIsPopupSpam) {
+ ++gOpenPopupSpamCount;
+ } else {
+ --gOpenPopupSpamCount;
+ NS_ASSERTION(gOpenPopupSpamCount >= 0,
+ "Unbalanced decrement of gOpenPopupSpamCount");
+ }
+}
+
+void
+nsGlobalWindow::DropOuterWindowDocs()
+{
+ MOZ_ASSERT(IsOuterWindow());
+ MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed());
+ mDoc = nullptr;
+ mSuspendedDoc = nullptr;
+}
+
+void
+nsGlobalWindow::CleanUp()
+{
+ // Guarantee idempotence.
+ if (mCleanedUp)
+ return;
+ mCleanedUp = true;
+
+ StartDying();
+
+ DisconnectEventTargetObjects();
+
+ if (mObserver) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ os->RemoveObserver(mObserver, "dom-storage2-changed");
+ }
+
+#ifdef MOZ_B2G
+ DisableNetworkEvent(eNetworkUpload);
+ DisableNetworkEvent(eNetworkDownload);
+#endif // MOZ_B2G
+
+ if (mIdleService) {
+ mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
+ }
+
+ 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();
+ }
+
+ if (mNavigator) {
+ mNavigator->Invalidate();
+ mNavigator = nullptr;
+ }
+
+ mScreen = nullptr;
+ mMenubar = nullptr;
+ mToolbar = nullptr;
+ mLocationbar = nullptr;
+ mPersonalbar = nullptr;
+ mStatusbar = nullptr;
+ mScrollbars = nullptr;
+ mLocation = nullptr;
+ mHistory = nullptr;
+ mCustomElements = nullptr;
+ mFrames = nullptr;
+ mWindowUtils = nullptr;
+ mApplicationCache = nullptr;
+ mIndexedDB = nullptr;
+
+ mConsole = nullptr;
+
+ mExternal = nullptr;
+
+ mMozSelfSupport = nullptr;
+
+ mPerformance = nullptr;
+
+#ifdef MOZ_WEBSPEECH
+ mSpeechSynthesis = nullptr;
+#endif
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+ mOrientationChangeObserver = nullptr;
+#endif
+
+ ClearControllers();
+
+ mOpener = nullptr; // Forces Release
+ if (mContext) {
+ mContext = nullptr; // Forces Release
+ }
+ mChromeEventHandler = nullptr; // Forces Release
+ mParentTarget = nullptr;
+
+ if (IsOuterWindow()) {
+ nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
+ if (inner) {
+ inner->CleanUp();
+ }
+ }
+
+ if (IsInnerWindow()) {
+ DisableGamepadUpdates();
+ mHasGamepad = false;
+ DisableVRUpdates();
+ mHasVREvents = false;
+#ifdef MOZ_B2G
+ DisableTimeChangeNotifications();
+#endif
+ DisableIdleCallbackRequests();
+ } else {
+ MOZ_ASSERT(!mHasGamepad);
+ MOZ_ASSERT(!mHasVREvents);
+ }
+
+ if (mCleanMessageManager) {
+ MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
+ nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this);
+ if (asChrome->mMessageManager) {
+ static_cast<nsFrameMessageManager*>(
+ asChrome->mMessageManager.get())->Disconnect();
+ }
+ }
+
+ mArguments = nullptr;
+ mDialogArguments = nullptr;
+
+ CleanupCachedXBLHandlers(this);
+
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ mAudioContexts[i]->Shutdown();
+ }
+ mAudioContexts.Clear();
+
+ if (mIdleTimer) {
+ mIdleTimer->Cancel();
+ mIdleTimer = nullptr;
+ }
+
+ mServiceWorkerRegistrationTable.Clear();
+}
+
+void
+nsGlobalWindow::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;
+ }
+}
+
+void
+nsGlobalWindow::FreeInnerObjects()
+{
+ NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window");
+
+ // 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);
+ }
+
+ mInnerObjectsFreed = true;
+
+ // Kill all of the workers for this window.
+ mozilla::dom::workers::CancelWorkersForWindow(AsInner());
+
+ ClearAllTimeouts();
+
+ if (mIdleTimer) {
+ mIdleTimer->Cancel();
+ mIdleTimer = nullptr;
+ }
+
+ mIdleObservers.Clear();
+
+ DisableIdleCallbackRequests();
+
+ mChromeEventHandler = nullptr;
+
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ mListenerManager = nullptr;
+ }
+
+ mLocation = nullptr;
+ mHistory = nullptr;
+ mCustomElements = nullptr;
+
+ if (mNavigator) {
+ mNavigator->OnNavigation();
+ mNavigator->Invalidate();
+ mNavigator = nullptr;
+ }
+
+ if (mScreen) {
+ mScreen = nullptr;
+ }
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+ mOrientationChangeObserver = nullptr;
+#endif
+
+ if (mDoc) {
+ // Remember the document's principal and URI.
+ mDocumentPrincipal = mDoc->NodePrincipal();
+ mDocumentURI = mDoc->GetDocumentURI();
+ mDocBaseURI = mDoc->GetDocBaseURI();
+
+ while (mDoc->EventHandlingSuppressed()) {
+ mDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, false);
+ }
+
+ // Note: we don't have to worry about eAnimationsOnly suppressions because
+ // they won't leak.
+ }
+
+ // Remove our reference to the document and the document principal.
+ mFocusedNode = nullptr;
+
+ if (mApplicationCache) {
+ static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->Disconnect();
+ mApplicationCache = nullptr;
+ }
+
+ mIndexedDB = nullptr;
+
+ UnlinkHostObjectURIs();
+
+ NotifyWindowIDDestroyed("inner-window-destroyed");
+
+ CleanupCachedXBLHandlers(this);
+
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ mAudioContexts[i]->Shutdown();
+ }
+ mAudioContexts.Clear();
+
+#ifdef MOZ_GAMEPAD
+ DisableGamepadUpdates();
+ mHasGamepad = false;
+ mGamepads.Clear();
+#endif
+ DisableVRUpdates();
+ mHasVREvents = false;
+ mVRDisplays.Clear();
+}
+
+//*****************************************************************************
+// nsGlobalWindow::nsISupports
+//*****************************************************************************
+
+// QueryInterface implementation for nsGlobalWindow
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindow)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ // Make sure this matches the cast in nsGlobalWindow::FromWrapper()
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMWindow)
+ if (aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) {
+ foundInterface = static_cast<nsIDOMWindowInternal*>(this);
+ if (!sWarnedAboutWindowInternal) {
+ sWarnedAboutWindowInternal = true;
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Extensions"), mDoc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "nsIDOMWindowInternalWarning");
+ }
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
+ if (aIID.Equals(NS_GET_IID(nsPIDOMWindowInner))) {
+ foundInterface = AsInner();
+ } else
+ if (aIID.Equals(NS_GET_IID(mozIDOMWindow)) && IsInnerWindow()) {
+ foundInterface = AsInner();
+ } else
+ if (aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter))) {
+ foundInterface = AsOuter();
+ } else
+ if (aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) && IsOuterWindow()) {
+ foundInterface = AsOuter();
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+NS_INTERFACE_MAP_END
+
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindow)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindow)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindow)
+ if (tmp->IsBlackForCC(false)) {
+ if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) {
+ return true;
+ }
+ tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration;
+ if (tmp->mCachedXBLPrototypeHandlers) {
+ for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter();
+ !iter.Done();
+ iter.Next()) {
+ iter.Data().exposeToActiveJS();
+ }
+ }
+ if (EventListenerManager* elm = tmp->GetExistingListenerManager()) {
+ elm->MarkForCC();
+ }
+ tmp->UnmarkGrayTimers();
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindow)
+ return tmp->IsBlackForCC(true);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindow)
+ return tmp->IsBlackForCC(false);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ IdleObserverHolder& aField,
+ const char* aName,
+ unsigned aFlags)
+{
+ CycleCollectionNoteChild(aCallback, aField.mIdleObserver.get(), aName, aFlags);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindow)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[512];
+ nsAutoCString uri;
+ if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) {
+ uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault();
+ }
+ SprintfLiteral(name, "nsGlobalWindow # %" PRIu64 " %s %s", tmp->mWindowID,
+ tmp->IsInnerWindow() ? "inner" : "outer", uri.get());
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ } else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindow, 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(mDialogArguments)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValue)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerRegistrationTable)
+
+#ifdef MOZ_WEBSPEECH
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis)
+#endif
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow)
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
+
+ for (Timeout* timeout = tmp->mTimeouts.getFirst();
+ timeout;
+ timeout = timeout->getNext()) {
+ 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(mLocalStorage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents)
+
+ for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
+ cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
+ }
+
+ for (IdleRequest* request : tmp->mThrottledIdleRequestCallbacks) {
+ cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers)
+
+#ifdef MOZ_GAMEPAD
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)
+#endif
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays)
+
+ // Traverse stuff from nsPIDOMWindow
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedNode)
+
+ 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(mU2F)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMozSelfSupport)
+
+ tmp->TraverseHostObjectURIs(cb);
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
+ nsGlobalWindow::CleanupCachedXBLHandlers(tmp);
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDialogArguments)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValue)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mServiceWorkerRegistrationTable)
+
+#ifdef MOZ_WEBSPEECH
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis)
+#endif
+
+ if (tmp->mOuterWindow) {
+ nsGlobalWindow::Cast(tmp->mOuterWindow)->MaybeClearInnerWindow(tmp);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow)
+ }
+
+ if (tmp->mListenerManager) {
+ tmp->mListenerManager->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage)
+ if (tmp->mApplicationCache) {
+ static_cast<nsDOMOfflineResourceList*>(tmp->mApplicationCache.get())->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache)
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWakeLock)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers)
+
+#ifdef MOZ_GAMEPAD
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)
+#endif
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays)
+
+ // Unlink stuff from nsPIDOMWindow
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedNode)
+
+ 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(mU2F)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport)
+
+ tmp->UnlinkHostObjectURIs();
+
+ tmp->DisableIdleCallbackRequests();
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+#ifdef DEBUG
+void
+nsGlobalWindow::RiskyUnlink()
+{
+ NS_CYCLE_COLLECTION_INNERNAME.Unlink(this);
+}
+#endif
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindow)
+ if (tmp->mCachedXBLPrototypeHandlers) {
+ for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter();
+ !iter.Done();
+ iter.Next()) {
+ aCallbacks.Trace(&iter.Data(), "Cached XBL prototype handler", aClosure);
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+bool
+nsGlobalWindow::IsBlackForCC(bool aTracingNeeded)
+{
+ if (!nsCCUncollectableMarker::sGeneration) {
+ return false;
+ }
+
+ return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) ||
+ IsBlack()) &&
+ (!aTracingNeeded ||
+ HasNothingToTrace(static_cast<nsIDOMEventTarget*>(this)));
+}
+
+void
+nsGlobalWindow::UnmarkGrayTimers()
+{
+ for (Timeout* timeout = mTimeouts.getFirst();
+ timeout;
+ timeout = timeout->getNext()) {
+ if (timeout->mScriptHandler) {
+ timeout->mScriptHandler->MarkForCC();
+ }
+ }
+}
+
+//*****************************************************************************
+// nsGlobalWindow::nsIScriptGlobalObject
+//*****************************************************************************
+
+nsresult
+nsGlobalWindow::EnsureScriptEnvironment()
+{
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (!outer) {
+ NS_WARNING("No outer window available!");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (outer->GetWrapperPreserveColor()) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!outer->GetCurrentInnerWindowInternal(),
+ "No cached wrapper, but we have an inner window?");
+
+ // If this window is a [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.
+ nsCOMPtr<nsIScriptContext> context = new nsJSContext(!IsFrame(), outer);
+
+ NS_ASSERTION(!outer->mContext, "Will overwrite mContext!");
+
+ // should probably assert the context is clean???
+ context->WillInitializeContext();
+
+ nsresult rv = context->InitContext();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outer->mContext = context;
+ return NS_OK;
+}
+
+nsIScriptContext *
+nsGlobalWindow::GetScriptContext()
+{
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (!outer) {
+ return nullptr;
+ }
+ return outer->mContext;
+}
+
+JSObject *
+nsGlobalWindow::GetGlobalJSObject()
+{
+ return FastGetGlobalJSObject();
+}
+
+void
+nsGlobalWindow::TraceGlobalJSObject(JSTracer* aTrc)
+{
+ TraceWrapper(aTrc, "active window global");
+}
+
+bool
+nsGlobalWindow::WouldReuseInnerWindow(nsIDocument* aNewDocument)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ // 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;
+ }
+
+ NS_ASSERTION(NS_IsAboutBlank(mDoc->GetDocumentURI()),
+ "How'd this happen?");
+
+ // Great, we're the original document, check for one of the other
+ // conditions.
+
+ if (mDoc == aNewDocument) {
+ return true;
+ }
+
+ bool equal;
+ if (NS_SUCCEEDED(mDoc->NodePrincipal()->Equals(aNewDocument->NodePrincipal(),
+ &equal)) &&
+ equal) {
+ // The origin is the same.
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsGlobalWindow::SetInitialPrincipalToSubject()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ // First, grab the subject principal.
+ nsCOMPtr<nsIPrincipal> newWindowPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
+
+ // 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 nsWebShellWindow::Initialize().
+ if (nsContentUtils::IsExpandedPrincipal(newWindowPrincipal) ||
+ (nsContentUtils::IsSystemPrincipal(newWindowPrincipal) &&
+ GetDocShell()->ItemType() != nsIDocShellTreeItem::typeChrome)) {
+ newWindowPrincipal = 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() == newWindowPrincipal)
+ return;
+
+#ifdef DEBUG
+ // If we have a document loaded at this point, it had better be about:blank.
+ // Otherwise, something is really weird.
+ nsCOMPtr<nsIURI> uri;
+ mDoc->NodePrincipal()->GetURI(getter_AddRefs(uri));
+ NS_ASSERTION(uri && NS_IsAboutBlank(uri) &&
+ NS_IsAboutBlank(mDoc->GetDocumentURI()),
+ "Unexpected original document");
+#endif
+ }
+
+ GetDocShell()->CreateAboutBlankContentViewer(newWindowPrincipal);
+ mDoc->SetIsInitialDocument(true);
+
+ nsCOMPtr<nsIPresShell> shell = GetDocShell()->GetPresShell();
+
+ if (shell && !shell->DidInitialize()) {
+ // Ensure that if someone plays with this document they will get
+ // layout happening.
+ nsRect r = shell->GetPresContext()->GetVisibleArea();
+ shell->Initialize(r.width, r.height);
+ }
+}
+
+PopupControlState
+PushPopupControlState(PopupControlState aState, bool aForce)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PopupControlState oldState = gPopupControlState;
+
+ if (aState < gPopupControlState || aForce) {
+ gPopupControlState = aState;
+ }
+
+ return oldState;
+}
+
+void
+PopPopupControlState(PopupControlState aState)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ gPopupControlState = aState;
+}
+
+PopupControlState
+nsGlobalWindow::PushPopupControlState(PopupControlState aState,
+ bool aForce) const
+{
+ return ::PushPopupControlState(aState, aForce);
+}
+
+void
+nsGlobalWindow::PopPopupControlState(PopupControlState aState) const
+{
+ ::PopPopupControlState(aState);
+}
+
+PopupControlState
+nsGlobalWindow::GetPopupControlState() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ return gPopupControlState;
+}
+
+#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(nsGlobalWindow *aWindow);
+
+ nsGlobalWindow* GetInnerWindow() { return mInnerWindow; }
+
+ void DidRestoreWindow()
+ {
+ mInnerWindow = nullptr;
+ mInnerWindowReflector = nullptr;
+ }
+
+protected:
+ ~WindowStateHolder();
+
+ nsGlobalWindow *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(nsGlobalWindow* aWindow)
+ : mInnerWindow(aWindow),
+ mInnerWindowReflector(RootingCx(), aWindow->GetWrapper())
+{
+ NS_PRECONDITION(aWindow, "null window");
+ NS_PRECONDITION(aWindow->IsInnerWindow(), "Saving an outer window");
+
+ aWindow->Suspend();
+
+ // When a global goes into the bfcache, we disable script.
+ xpc::Scriptability::Get(mInnerWindowReflector).SetDocShellAllowsScript(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)
+
+// We need certain special behavior for remote XUL whitelisted domains, but we
+// don't want that behavior to take effect in automation, because we whitelist
+// all the mochitest domains. So we need to check a pref here.
+static bool
+TreatAsRemoteXUL(nsIPrincipal* aPrincipal)
+{
+ MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(aPrincipal));
+ return nsContentUtils::AllowXULXBLForPrincipal(aPrincipal) &&
+ !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false);
+}
+
+static bool
+EnablePrivilege(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ Telemetry::Accumulate(Telemetry::ENABLE_PRIVILEGE_EVER_CALLED, true);
+ return xpc::EnableUniversalXPConnect(cx);
+}
+
+static const JSFunctionSpec EnablePrivilegeSpec[] = {
+ JS_FS("enablePrivilege", EnablePrivilege, 1, 0),
+ JS_FS_END
+};
+
+static bool
+InitializeLegacyNetscapeObject(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
+{
+ JSAutoCompartment ac(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);
+
+ // We hide enablePrivilege behind a pref because it has been altered in a
+ // way that makes it fundamentally insecure to use in production. Mozilla
+ // uses this pref during automated testing to support legacy test code that
+ // uses enablePrivilege. If you're not doing test automation, you _must_ not
+ // flip this pref, or you will be exposing all your users to security
+ // vulnerabilities.
+ if (!xpc::IsInAutomation()) {
+ return true;
+ }
+
+ /* Define PrivilegeManager object with the necessary "static" methods. */
+ obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr);
+ NS_ENSURE_TRUE(obj, false);
+
+ return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec);
+}
+
+bool
+nsGlobalWindow::ComputeIsSecureContext(nsIDocument* aDocument, SecureContextFlags aFlags)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
+ if (nsContentUtils::IsSystemPrincipal(principal)) {
+ return true;
+ }
+
+ // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object
+ // With some modifications to allow for aFlags.
+
+ bool hadNonSecureContextCreator = false;
+
+ nsPIDOMWindowOuter* parentOuterWin = GetScriptableParent();
+ MOZ_ASSERT(parentOuterWin, "How can we get here? No docShell somehow?");
+ if (nsGlobalWindow::Cast(parentOuterWin) != this) {
+ // There may be a small chance that parentOuterWin has navigated in
+ // the time that it took us to start loading this sub-document. If that
+ // were the case then parentOuterWin->GetCurrentInnerWindow() wouldn't
+ // return the window for the document that is embedding us. For this
+ // reason we only use the GetScriptableParent call above to check that we
+ // have a same-type parent, but actually get the inner window via the
+ // document that we know is embedding us.
+ nsIDocument* creatorDoc = aDocument->GetParentDocument();
+ if (!creatorDoc) {
+ return false; // we must be tearing down
+ }
+ nsGlobalWindow* parentWin =
+ nsGlobalWindow::Cast(creatorDoc->GetInnerWindow());
+ if (!parentWin) {
+ return false; // we must be tearing down
+ }
+ MOZ_ASSERT(parentWin ==
+ nsGlobalWindow::Cast(parentOuterWin->GetCurrentInnerWindow()),
+ "Creator window mismatch while setting Secure Context state");
+ if (aFlags != SecureContextFlags::eIgnoreOpener) {
+ hadNonSecureContextCreator = !parentWin->IsSecureContext();
+ } else {
+ hadNonSecureContextCreator = !parentWin->IsSecureContextIfOpenerIgnored();
+ }
+ } else if (mHadOriginalOpener) {
+ if (aFlags != SecureContextFlags::eIgnoreOpener) {
+ hadNonSecureContextCreator = !mOriginalOpenerWasSecureContext;
+ }
+ }
+
+ if (hadNonSecureContextCreator) {
+ return false;
+ }
+
+ if (nsContentUtils::HttpsStateIsModern(aDocument)) {
+ return true;
+ }
+
+ if (principal->GetIsNullPrincipal()) {
+ nsCOMPtr<nsIURI> 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 PrincipalOriginAttributes& attrs =
+ BasePrincipal::Cast(principal)->OriginAttributesRef();
+ // CreateCodebasePrincipal correctly gets a useful principal for blob: and
+ // other URI_INHERITS_SECURITY_CONTEXT URIs.
+ principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+ if (NS_WARN_IF(!principal)) {
+ return false;
+ }
+ }
+
+ nsCOMPtr<nsIContentSecurityManager> csm =
+ do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
+ NS_WARNING_ASSERTION(csm, "csm is null");
+ if (csm) {
+ bool isTrustworthyOrigin = false;
+ csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
+ if (isTrustworthyOrigin) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * 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,
+ nsGlobalWindow* aNewInner,
+ nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ JS::MutableHandle<JSObject*> aGlobal,
+ bool aIsSecureContext)
+{
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aNewInner);
+ MOZ_ASSERT(aNewInner->IsInnerWindow());
+ MOZ_ASSERT(aPrincipal);
+
+ // DOMWindow with nsEP is not supported, we have to make sure
+ // no one creates one accidentally.
+ nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(aPrincipal);
+ MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported");
+
+ nsGlobalWindow *top = nullptr;
+ if (aNewInner->GetOuterWindow()) {
+ top = aNewInner->GetTopInternal();
+ }
+
+ JS::CompartmentOptions options;
+
+ // Sometimes add-ons load their own XUL windows, either as separate top-level
+ // windows or inside a browser element. In such cases we want to tag the
+ // window's compartment with the add-on ID. See bug 1092156.
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ options.creationOptions().setAddonId(MapURIToAddonID(aURI));
+ }
+
+ if (top && top->GetGlobalJSObject()) {
+ options.creationOptions().setSameZoneAs(top->GetGlobalJSObject());
+ }
+
+ options.creationOptions().setSecureContext(aIsSecureContext);
+
+ xpc::InitGlobalObjectOptions(options, aPrincipal);
+
+ // Determine if we need the Components object.
+ bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) ||
+ TreatAsRemoteXUL(aPrincipal);
+ uint32_t flags = needComponents ? 0 : nsIXPConnect::OMIT_COMPONENTS_OBJECT;
+ flags |= nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK;
+
+ if (!WindowBinding::Wrap(aCx, aNewInner, aNewInner, options,
+ nsJSPrincipals::get(aPrincipal), 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, aURI);
+
+ if (!InitializeLegacyNetscapeObject(aCx, aGlobal)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsGlobalWindow::SetNewDocument(nsIDocument* aDocument,
+ nsISupports* aState,
+ bool aForceReuseInnerWindow)
+{
+ NS_PRECONDITION(mDocumentPrincipal == nullptr,
+ "mDocumentPrincipal prematurely set!");
+ MOZ_ASSERT(aDocument);
+
+ if (IsInnerWindow()) {
+ if (!mOuterWindow) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Refuse to set a new document if the call came from an inner
+ // window that's not the current inner window.
+ if (mOuterWindow->GetCurrentInnerWindow() != AsInner()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return GetOuterWindowInternal()->SetNewDocument(aDocument, aState,
+ aForceReuseInnerWindow);
+ }
+
+ NS_PRECONDITION(IsOuterWindow(), "Must only be called on outer windows");
+
+ // Bail out early if we're in process of closing down the window.
+ NS_ENSURE_STATE(!mCleanedUp);
+
+ NS_ASSERTION(!AsOuter()->GetCurrentInnerWindow() ||
+ AsOuter()->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;
+ }
+
+ nsCOMPtr<nsIDocument> oldDoc = mDoc;
+
+ 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.
+ bool overrecursed = false;
+ JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, overrecursed = true);
+ if (overrecursed) {
+ 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 = nsGlobalWindow::GetPrivateRoot();
+
+ if (privateRoot == AsOuter()) {
+ nsXBLService::AttachGlobalKeyHandler(mChromeEventHandler);
+ }
+ }
+
+ /* 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(this, &nsGlobalWindow::ClearStatus));
+
+ // Sometimes, WouldReuseInnerWindow() returns true even if there's no inner
+ // window (see bug 776497). Be safe.
+ bool reUseInnerWindow = (aForceReuseInnerWindow || wouldReuseInnerWindow) &&
+ GetCurrentInnerWindowInternal();
+
+ nsresult rv = NS_OK;
+
+ // 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;
+
+ // Take this opportunity to clear mSuspendedDoc. Our old inner window is now
+ // responsible for unsuspending it.
+ mSuspendedDoc = nullptr;
+
+#ifdef DEBUG
+ mLastOpenedURI = aDocument->GetDocumentURI();
+#endif
+
+ mContext->WillInitializeContext();
+
+ nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
+
+ if (currentInner && currentInner->mNavigator) {
+ currentInner->mNavigator->OnNavigation();
+ }
+
+ RefPtr<nsGlobalWindow> 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?");
+
+ 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->GetWrapperPreserveColor();
+
+ if (aDocument != oldDoc) {
+ JS::ExposeObjectToActiveJS(newInnerGlobal);
+ }
+
+ // 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 compartment to
+ // match the new document.
+ // NB: We don't just call currentInner->RefreshCompartmentPrincipals() here
+ // because we haven't yet set its mDoc to aDocument.
+ JSCompartment *compartment = js::GetObjectCompartment(newInnerGlobal);
+#ifdef DEBUG
+ bool sameOrigin = false;
+ nsIPrincipal *existing =
+ nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
+ aDocument->NodePrincipal()->Equals(existing, &sameOrigin);
+ MOZ_ASSERT(sameOrigin);
+#endif
+ MOZ_ASSERT_IF(aDocument == oldDoc,
+ xpc::GetCompartmentPrincipal(compartment) ==
+ aDocument->NodePrincipal());
+ if (aDocument != oldDoc) {
+ JS_SetCompartmentPrincipals(compartment,
+ nsJSPrincipals::get(aDocument->NodePrincipal()));
+ // Make sure we clear out the old content XBL scope, so the new one will
+ // get created with a principal that subsumes our new principal.
+ xpc::ClearContentXBLScope(newInnerGlobal);
+ }
+ } else {
+ if (aState) {
+ newInnerWindow = wsh->GetInnerWindow();
+ newInnerGlobal = newInnerWindow->GetWrapperPreserveColor();
+ } else {
+ if (thisChrome) {
+ newInnerWindow = nsGlobalChromeWindow::Create(this);
+ } else if (mIsModalContentWindow) {
+ newInnerWindow = nsGlobalModalWindow::Create(this);
+ } else {
+ newInnerWindow = nsGlobalWindow::Create(this);
+ }
+
+ // 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;
+ // Every script context we are initialized with must create a
+ // new global.
+ rv = CreateNativeGlobalForInner(cx, newInnerWindow,
+ aDocument->GetDocumentURI(),
+ aDocument->NodePrincipal(),
+ &newInnerGlobal,
+ ComputeIsSecureContext(aDocument));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerGlobal &&
+ newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal,
+ "Failed to get script global");
+ newInnerWindow->mIsSecureContextIfOpenerIgnored =
+ ComputeIsSecureContext(aDocument, SecureContextFlags::eIgnoreOpener);
+
+ mCreatingInnerWindow = false;
+ createdInnerWindow = true;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (currentInner && currentInner->GetWrapperPreserveColor()) {
+ if (oldDoc == aDocument) {
+ // Move the navigator from the old inner window to the new one since
+ // this is a document.write. This is safe from a same-origin point of
+ // view because document.write can only be used by the same origin.
+ newInnerWindow->mNavigator = currentInner->mNavigator;
+ currentInner->mNavigator = nullptr;
+ if (newInnerWindow->mNavigator) {
+ newInnerWindow->mNavigator->SetWindow(newInnerWindow->AsInner());
+ }
+
+ // Make a copy of the old window's performance object on document.open.
+ // Note that we have to force eager creation of it here, because we need
+ // to grab the current document channel and whatnot before that changes.
+ currentInner->AsInner()->CreatePerformanceObjectIfNeeded();
+ if (currentInner->mPerformance) {
+ newInnerWindow->mPerformance =
+ Performance::CreateForMainThread(newInnerWindow->AsInner(),
+ currentInner->mPerformance->GetDOMTiming(),
+ currentInner->mPerformance->GetChannel(),
+ currentInner->mPerformance->GetParentPerformance());
+ }
+ }
+
+ // Don't free objects on our current inner window if it's going to be
+ // held in the bfcache.
+ if (!currentInner->IsFrozen()) {
+ currentInner->FreeInnerObjects();
+ }
+ }
+
+ mInnerWindow = newInnerWindow->AsInner();
+
+ if (!GetWrapperPreserveColor()) {
+ JS::Rooted<JSObject*> outer(cx,
+ NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
+ NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE);
+
+ js::SetProxyExtra(outer, 0, js::PrivateValue(ToSupports(this)));
+
+ // Inform the nsJSContext, which is the canonical holder of the outer.
+ mContext->SetWindowProxy(outer);
+ mContext->DidInitializeContext();
+
+ SetWrapper(mContext->GetWindowProxy());
+ } else {
+ JS::ExposeObjectToActiveJS(newInnerGlobal);
+ 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, GetWrapperPreserveColor());
+
+ js::SetProxyExtra(obj, 0, js::PrivateValue(nullptr));
+ js::SetProxyExtra(outerObject, 0, js::PrivateValue(nullptr));
+
+ outerObject = xpc::TransplantObject(cx, obj, outerObject);
+ if (!outerObject) {
+ NS_ERROR("unable to transplant wrappers, probably OOM");
+ return NS_ERROR_FAILURE;
+ }
+
+ js::SetProxyExtra(outerObject, 0, js::PrivateValue(ToSupports(this)));
+
+ SetWrapper(outerObject);
+
+ MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(outerObject) == newInnerGlobal);
+
+ // Inform the nsJSContext, which is the canonical holder of the outer.
+ mContext->SetWindowProxy(outerObject);
+ }
+
+ // Enter the new global's compartment.
+ JSAutoCompartment ac(cx, GetWrapperPreserveColor());
+
+ {
+ JS::Rooted<JSObject*> outer(cx, GetWrapperPreserveColor());
+ js::SetWindowProxy(cx, newInnerGlobal, outer);
+ }
+
+ // Set scriptability based on the state of the docshell.
+ bool allow = GetDocShell()->GetCanExecuteScripts();
+ xpc::Scriptability::Get(GetWrapperPreserveColor()).SetDocShellAllowsScript(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
+ // nsGlobalWindow, so GetWrapperPreserveColor() on that outer
+ // nsGlobalWindow doesn't return null and nsGlobalWindow::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;
+ }
+ }
+ }
+
+ JSAutoCompartment ac(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);
+
+ aDocument->SetScriptGlobalObject(newInnerWindow);
+ MOZ_ASSERT(newInnerWindow->mTabGroup,
+ "We must have a TabGroup cached at this point");
+
+ if (!aState) {
+ if (reUseInnerWindow) {
+
+ if (newInnerWindow->mDoc != aDocument) {
+ newInnerWindow->mDoc = aDocument;
+
+ // 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->ClearDocumentDependentSlots(cx);
+ }
+ } else {
+ newInnerWindow->InnerSetNewDocument(cx, aDocument);
+
+ // Initialize DOM classes etc on the inner window.
+ JS::Rooted<JSObject*> obj(cx, newInnerGlobal);
+ rv = kungFuDeathGrip->InitClasses(obj);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If the document comes from a JAR, check if the channel was determined
+ // to be unsafe. If so, permanently disable script on the compartment by
+ // calling Block() and throwing away the key.
+ nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aDocument->GetChannel());
+ if (jarChannel && jarChannel->GetIsUnsafe()) {
+ xpc::Scriptability::Get(newInnerGlobal).Block();
+ }
+
+ 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;
+ }
+
+ nsJSContext::PokeGC(JS::gcreason::SET_NEW_DOCUMENT);
+ kungFuDeathGrip->DidInitializeContext();
+
+ // 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(newInnerWindow,
+ &nsGlobalWindow::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.
+ if (!mDocShell ||
+ mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome ||
+ nsContentUtils::IsSystemPrincipal(mDoc->NodePrincipal())) {
+ newInnerWindow->mHasNotifiedGlobalCreated = true;
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &nsGlobalWindow::DispatchDOMWindowCreated));
+ }
+ }
+
+ PreloadLocalStorage();
+
+ return NS_OK;
+}
+
+void
+nsGlobalWindow::PreloadLocalStorage()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (!Preferences::GetBool(kStorageEnabled)) {
+ return;
+ }
+
+ if (IsChromeWindow()) {
+ return;
+ }
+
+ nsIPrincipal* principal = GetPrincipal();
+ if (!principal) {
+ return;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDOMStorageManager> storageManager =
+ do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ storageManager->PrecacheStorage(principal);
+}
+
+void
+nsGlobalWindow::DispatchDOMWindowCreated()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (!mDoc) {
+ return;
+ }
+
+ // Fire DOMWindowCreated at chrome event listeners
+ nsContentUtils::DispatchChromeEvent(mDoc, mDoc, NS_LITERAL_STRING("DOMWindowCreated"),
+ true /* bubbles */,
+ false /* not cancellable */);
+
+ 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),
+ nsContentUtils::IsSystemPrincipal(principal) ?
+ "chrome-document-global-created" :
+ "content-document-global-created",
+ origin.get());
+ }
+}
+
+void
+nsGlobalWindow::ClearStatus()
+{
+ SetStatusOuter(EmptyString());
+}
+
+void
+nsGlobalWindow::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument)
+{
+ NS_PRECONDITION(IsInnerWindow(), "Must only be called on inner windows");
+ MOZ_ASSERT(aDocument);
+
+ if (gDOMLeakPRLog && MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) {
+ nsIURI *uri = aDocument->GetDocumentURI();
+ PR_LogPrint("DOMWINDOW %p SetNewDocument %s",
+ this, uri ? uri->GetSpecOrDefault().get() : "");
+ }
+
+ mDoc = aDocument;
+ ClearDocumentDependentSlots(aCx);
+ mFocusedNode = nullptr;
+ mLocalStorage = nullptr;
+ mSessionStorage = nullptr;
+
+#ifdef DEBUG
+ mLastOpenedURI = aDocument->GetDocumentURI();
+#endif
+
+ Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
+ mMutationBits ? 1 : 0);
+
+ // Clear our mutation bitfield.
+ mMutationBits = 0;
+}
+
+void
+nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell)
+{
+ NS_ASSERTION(IsOuterWindow(), "Uh, SetDocShell() called on inner window!");
+ MOZ_ASSERT(aDocShell);
+
+ if (aDocShell == mDocShell) {
+ return;
+ }
+
+ mDocShell = aDocShell; // Weak Reference
+
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull();
+ MOZ_RELEASE_ASSERT(!parentWindow || !mTabGroup || mTabGroup == Cast(parentWindow)->mTabGroup);
+
+ NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!");
+
+ if (mFrames) {
+ mFrames->SetDocShell(aDocShell);
+ }
+
+ // Get our enclosing chrome shell and retrieve its global window impl, so
+ // that we can do some forwarding to the chrome document.
+ nsCOMPtr<nsIDOMEventTarget> chromeEventHandler;
+ mDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
+ mChromeEventHandler = do_QueryInterface(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 = GetParent();
+ if (parentWindow.get() != AsOuter()) {
+ mChromeEventHandler = parentWindow->GetChromeEventHandler();
+ }
+ else {
+ mChromeEventHandler = NS_NewWindowRoot(AsOuter());
+ mIsRootOuterWindow = true;
+ }
+ }
+
+ bool docShellActive;
+ mDocShell->GetIsActive(&docShellActive);
+ mIsBackground = !docShellActive;
+}
+
+void
+nsGlobalWindow::DetachFromDocShell()
+{
+ NS_ASSERTION(IsOuterWindow(), "Uh, DetachFromDocShell() called on inner window!");
+
+ // 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).
+
+ NS_ASSERTION(mTimeouts.isEmpty(), "Uh, outer window holds timeouts!");
+
+ // Call FreeInnerObjects on all inner windows, not just the current
+ // one, since some could be held by WindowStateHolder objects that
+ // are GC-owned.
+ for (RefPtr<nsGlobalWindow> inner = (nsGlobalWindow *)PR_LIST_HEAD(this);
+ inner != this;
+ inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) {
+ MOZ_ASSERT(!inner->mOuterWindow || inner->mOuterWindow == AsOuter());
+ inner->FreeInnerObjects();
+ }
+
+ if (auto* reporter = nsWindowMemoryReporter::Get()) {
+ reporter->ObserveDOMWindowDetached(this);
+ }
+
+ NotifyWindowIDDestroyed("outer-window-destroyed");
+
+ nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
+
+ if (currentInner) {
+ NS_ASSERTION(mDoc, "Must have doc!");
+
+ // Remember the document's principal and URI.
+ mDocumentPrincipal = mDoc->NodePrincipal();
+ mDocumentURI = mDoc->GetDocumentURI();
+ mDocBaseURI = mDoc->GetDocBaseURI();
+
+ // Release our document reference
+ DropOuterWindowDocs();
+ mFocusedNode = nullptr;
+ }
+
+ ClearControllers();
+
+ mChromeEventHandler = nullptr; // force release now
+
+ if (mContext) {
+ nsJSContext::PokeGC(JS::gcreason::SET_DOC_SHELL);
+ mContext = nullptr;
+ }
+
+ mDocShell = nullptr; // Weak Reference
+
+ NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!");
+
+ if (mFrames) {
+ mFrames->SetDocShell(nullptr);
+ }
+
+ MaybeForgiveSpamCount();
+ CleanUp();
+}
+
+void
+nsGlobalWindow::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
+ bool aOriginalOpener)
+{
+ FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener));
+
+ nsWeakPtr opener = do_GetWeakReference(aOpener);
+ if (opener == mOpener) {
+ return;
+ }
+
+ NS_ASSERTION(!aOriginalOpener || !mSetOpenerWindowCalled,
+ "aOriginalOpener is true, but not first call to "
+ "SetOpenerWindow!");
+ NS_ASSERTION(aOpener || !aOriginalOpener,
+ "Shouldn't set mHadOriginalOpener if aOpener is null");
+
+ mOpener = opener.forget();
+ NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!");
+
+ if (aOriginalOpener) {
+ MOZ_ASSERT(!mHadOriginalOpener,
+ "Probably too late to call ComputeIsSecureContext again");
+ mHadOriginalOpener = true;
+ mOriginalOpenerWasSecureContext =
+ aOpener->GetCurrentInnerWindow()->IsSecureContext();
+ }
+
+#ifdef DEBUG
+ mSetOpenerWindowCalled = true;
+#endif
+}
+
+static
+already_AddRefed<EventTarget>
+TryGetTabChildGlobalAsEventTarget(nsISupports *aFrom)
+{
+ nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(aFrom);
+ if (!frameLoaderOwner) {
+ return nullptr;
+ }
+
+ RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
+ if (!frameLoader) {
+ return nullptr;
+ }
+
+ nsCOMPtr<EventTarget> target = frameLoader->GetTabChildGlobalAsEventTarget();
+ return target.forget();
+}
+
+void
+nsGlobalWindow::UpdateParentTarget()
+{
+ // 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 = GetOuterWindow()->GetFrameElementInternal();
+ nsCOMPtr<EventTarget> eventTarget =
+ TryGetTabChildGlobalAsEventTarget(frameElement);
+
+ if (!eventTarget) {
+ nsGlobalWindow* topWin = GetScriptableTopInternal();
+ if (topWin) {
+ frameElement = topWin->AsOuter()->GetFrameElementInternal();
+ eventTarget = TryGetTabChildGlobalAsEventTarget(frameElement);
+ }
+ }
+
+ if (!eventTarget) {
+ eventTarget = TryGetTabChildGlobalAsEventTarget(mChromeEventHandler);
+ }
+
+ if (!eventTarget) {
+ eventTarget = mChromeEventHandler;
+ }
+
+ mParentTarget = eventTarget;
+}
+
+EventTarget*
+nsGlobalWindow::GetTargetForDOMEvent()
+{
+ return GetOuterWindowInternal();
+}
+
+EventTarget*
+nsGlobalWindow::GetTargetForEventTargetChain()
+{
+ return IsInnerWindow() ? this : GetCurrentInnerWindowInternal();
+}
+
+nsresult
+nsGlobalWindow::WillHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ return NS_OK;
+}
+
+nsresult
+nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ NS_PRECONDITION(IsInnerWindow(), "PreHandleEvent is used on outer window!?");
+ EventMessage msg = aVisitor.mEvent->mMessage;
+
+ aVisitor.mCanHandle = true;
+ aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
+ if (msg == eResize && aVisitor.mEvent->IsTrusted()) {
+ // QIing to window so that we can keep the old behavior also in case
+ // a child window is handling resize.
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
+ if (window) {
+ mIsHandlingResizeEvent = true;
+ }
+ } else if (msg == eMouseDown && aVisitor.mEvent->IsTrusted()) {
+ gMouseDown = true;
+ } else if ((msg == eMouseUp || msg == eDragEnd) &&
+ aVisitor.mEvent->IsTrusted()) {
+ gMouseDown = false;
+ if (gDragServiceDisabled) {
+ nsCOMPtr<nsIDragService> ds =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (ds) {
+ gDragServiceDisabled = false;
+ ds->Unsuppress();
+ }
+ }
+ }
+
+ aVisitor.mParentTarget = GetParentTarget();
+
+ // Handle 'active' event.
+ if (!mIdleObservers.IsEmpty() &&
+ aVisitor.mEvent->IsTrusted() &&
+ (aVisitor.mEvent->HasMouseEventMessage() ||
+ aVisitor.mEvent->HasDragEventMessage())) {
+ mAddActiveEventFuzzTime = false;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsGlobalWindow::ShouldPromptToBlockDialogs()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ nsGlobalWindow *topWindow = GetScriptableTopInternal();
+ if (!topWindow) {
+ NS_ASSERTION(!mDocShell, "ShouldPromptToBlockDialogs() called without a top window?");
+ return true;
+ }
+
+ topWindow = topWindow->GetCurrentInnerWindowInternal();
+ if (!topWindow) {
+ return true;
+ }
+
+ return topWindow->DialogsAreBeingAbused();
+}
+
+bool
+nsGlobalWindow::AreDialogsEnabled()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ nsGlobalWindow *topWindow = GetScriptableTopInternal();
+ if (!topWindow) {
+ NS_ERROR("AreDialogsEnabled() called without a top window?");
+ return false;
+ }
+
+ // TODO: Warn if no top window?
+ topWindow = topWindow->GetCurrentInnerWindowInternal();
+ if (!topWindow) {
+ 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 topWindow->mAreDialogsEnabled;
+}
+
+bool
+nsGlobalWindow::DialogsAreBeingAbused()
+{
+ MOZ_ASSERT(IsInnerWindow());
+ NS_ASSERTION(GetScriptableTopInternal() &&
+ GetScriptableTopInternal()->GetCurrentInnerWindowInternal() == this,
+ "DialogsAreBeingAbused called with invalid window");
+
+ if (mLastDialogQuitTime.IsNull() ||
+ nsContentUtils::IsCallerChrome()) {
+ return false;
+ }
+
+ TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime);
+ if (dialogInterval.ToSeconds() <
+ Preferences::GetInt("dom.successive_dialog_time_limit",
+ DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) {
+ mDialogAbuseCount++;
+
+ return GetPopupControlState() > openAllowed ||
+ mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT;
+ }
+
+ // Reset the abuse counter
+ mDialogAbuseCount = 0;
+
+ return false;
+}
+
+bool
+nsGlobalWindow::ConfirmDialogIfNeeded()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ NS_ENSURE_TRUE(mDocShell, false);
+ nsCOMPtr<nsIPromptService> promptSvc =
+ do_GetService("@mozilla.org/embedcomp/prompt-service;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.
+ nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+
+ bool disableDialog = false;
+ nsXPIDLString label, title;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ "ScriptDialogLabel", label);
+ nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ "ScriptDialogPreventTitle", title);
+ promptSvc->Confirm(AsOuter(), title.get(), label.get(), &disableDialog);
+ if (disableDialog) {
+ DisableDialogs();
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsGlobalWindow::DisableDialogs()
+{
+ nsGlobalWindow *topWindow = GetScriptableTopInternal();
+ if (!topWindow) {
+ NS_ERROR("DisableDialogs() called without a top window?");
+ return;
+ }
+
+ topWindow = topWindow->GetCurrentInnerWindowInternal();
+ // TODO: Warn if no top window?
+ if (topWindow) {
+ topWindow->mAreDialogsEnabled = false;
+ }
+}
+
+void
+nsGlobalWindow::EnableDialogs()
+{
+ nsGlobalWindow *topWindow = GetScriptableTopInternal();
+ if (!topWindow) {
+ NS_ERROR("EnableDialogs() called without a top window?");
+ return;
+ }
+
+ // TODO: Warn if no top window?
+ topWindow = topWindow->GetCurrentInnerWindowInternal();
+ if (topWindow) {
+ topWindow->mAreDialogsEnabled = true;
+ }
+}
+
+nsresult
+nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ NS_PRECONDITION(IsInnerWindow(), "PostHandleEvent is used on outer window!?");
+
+ // 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. */
+ nsCOMPtr<nsIDOMEventTarget> 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()) {
+ // Execute bindingdetached handlers before we tear ourselves
+ // down.
+ if (mDoc) {
+ mDoc->BindingManager()->ExecuteDetachedHandlers();
+ }
+ mIsDocumentLoaded = false;
+ } else if (aVisitor.mEvent->mMessage == eLoad &&
+ aVisitor.mEvent->IsTrusted()) {
+ // This is page load event since load events don't propagate to |window|.
+ // @see nsDocument::PreHandleEvent.
+ mIsDocumentLoaded = true;
+
+ nsCOMPtr<Element> element = GetOuterWindow()->GetFrameElementInternal();
+ nsIDocShell* docShell = GetDocShell();
+ if (element && GetParentInternal() &&
+ docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ // If we're not in chrome, or at a chrome boundary, fire the
+ // onload event for the frame element.
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetEvent event(aVisitor.mEvent->IsTrusted(), eLoad);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+
+ // 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 NS_OK;
+}
+
+nsresult
+nsGlobalWindow::DispatchDOMEvent(WidgetEvent* aEvent,
+ nsIDOMEvent* aDOMEvent,
+ nsPresContext* aPresContext,
+ nsEventStatus* aEventStatus)
+{
+ return EventDispatcher::DispatchDOMEvent(static_cast<nsPIDOMWindow*>(this),
+ aEvent, aDOMEvent, aPresContext,
+ aEventStatus);
+}
+
+void
+nsGlobalWindow::PoisonOuterWindowProxy(JSObject *aObject)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ if (aObject == GetWrapperPreserveColor()) {
+ PoisonWrapper();
+ }
+}
+
+nsresult
+nsGlobalWindow::SetArguments(nsIArray *aArguments)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ nsresult rv;
+
+ // Historically, we've used the same machinery to handle openDialog arguments
+ // (exposed via window.arguments) and showModalDialog arguments (exposed via
+ // window.dialogArguments), even though the former is XUL-only and uses an XPCOM
+ // array while the latter is web-exposed and uses an arbitrary JS value.
+ // Moreover, per-spec |dialogArguments| is a property of the browsing context
+ // (outer), whereas |arguments| lives on the inner.
+ //
+ // 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.
+ nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
+ if (mIsModalContentWindow) {
+ // nsWindowWatcher blindly converts the original nsISupports into an array
+ // of length 1. We need to recover it, and then cast it back to the concrete
+ // object we know it to be.
+ nsCOMPtr<nsISupports> supports = do_QueryElementAt(aArguments, 0, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDialogArguments = static_cast<DialogValueHolder*>(supports.get());
+ } else {
+ mArguments = aArguments;
+ rv = currentInner->DefineArgumentsProperty(aArguments);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsGlobalWindow::DefineArgumentsProperty(nsIArray *aArguments)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ MOZ_ASSERT(!mIsModalContentWindow); // Handled separately.
+
+ 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);
+}
+
+namespace {
+
+// Convert a ThrottledEventQueue length to a timer delay in milliseconds.
+// This will return a value between 0 and INT32_MAX.
+int32_t
+CalculateNewBackPressureDelayMS(uint32_t aBacklogDepth)
+{
+ double multiplier = static_cast<double>(aBacklogDepth) /
+ static_cast<double>(gThrottledEventQueueBackPressure);
+ double value = static_cast<double>(gBackPressureDelayMS) * multiplier;
+ // Avoid overflow
+ if (value > INT32_MAX) {
+ value = INT32_MAX;
+ }
+
+ // Once we get close to an empty queue just floor the delay back to zero.
+ // We want to ensure we don't get stuck in a condition where there is a
+ // small amount of delay remaining due to an active, but reasonable, queue.
+ else if (value < static_cast<double>(gBackPressureDelayMinimumMS)) {
+ value = 0;
+ }
+ return static_cast<int32_t>(value);
+}
+
+} // anonymous namespace
+
+void
+nsGlobalWindow::MaybeApplyBackPressure()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If we are already in back pressure then we don't need to apply back
+ // pressure again. We also shouldn't need to apply back pressure while
+ // the window is suspended.
+ if (mBackPressureDelayMS > 0 || IsSuspended()) {
+ return;
+ }
+
+ RefPtr<ThrottledEventQueue> queue = TabGroup()->GetThrottledEventQueue();
+ if (!queue) {
+ return;
+ }
+
+ // Only begin back pressure if the window has greatly fallen behind the main
+ // thread. This is a somewhat arbitrary threshold chosen such that it should
+ // rarely fire under normaly circumstances. Its low enough, though,
+ // that we should have time to slow new runnables from being added before an
+ // OOM occurs.
+ if (queue->Length() < gThrottledEventQueueBackPressure) {
+ return;
+ }
+
+ // First attempt to dispatch a runnable to update our back pressure state. We
+ // do this first in order to verify we can dispatch successfully before
+ // entering the back pressure state.
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod(this, &nsGlobalWindow::CancelOrUpdateBackPressure);
+ nsresult rv = queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Since the callback was scheduled successfully we can now persist the
+ // backpressure value.
+ mBackPressureDelayMS = CalculateNewBackPressureDelayMS(queue->Length());
+}
+
+void
+nsGlobalWindow::CancelOrUpdateBackPressure()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mBackPressureDelayMS > 0);
+
+ // First, re-calculate the back pressure delay.
+ RefPtr<ThrottledEventQueue> queue = TabGroup()->GetThrottledEventQueue();
+ int32_t newBackPressureDelayMS =
+ CalculateNewBackPressureDelayMS(queue ? queue->Length() : 0);
+
+ // If the delay has increased, then simply apply it. Increasing the delay
+ // does not risk re-ordering timers.
+ if (newBackPressureDelayMS > mBackPressureDelayMS) {
+ mBackPressureDelayMS = newBackPressureDelayMS;
+ }
+
+ // If the delay has decreased, though, we only apply the new value if it has
+ // reduced significantly. This hysteresis avoids thrashing the back pressure
+ // value back and forth rapidly. This is important because reducing the
+ // backpressure delay requires calling ResetTimerForThrottleReduction() which
+ // can be quite expensive. We only want to call that method if the back log
+ // is really clearing.
+ else if (newBackPressureDelayMS == 0 ||
+ (static_cast<uint32_t>(mBackPressureDelayMS) >
+ (newBackPressureDelayMS + gBackPressureDelayReductionThresholdMS))) {
+ int32_t oldBackPressureDelayMS = mBackPressureDelayMS;
+ mBackPressureDelayMS = newBackPressureDelayMS;
+
+ // If the back pressure delay has gone down we must reset any existing
+ // timers to use the new value. Otherwise we run the risk of executing
+ // timer callbacks out-of-order.
+ ResetTimersForThrottleReduction(oldBackPressureDelayMS);
+ }
+
+ // If all of the back pressure delay has been removed then we no longer need
+ // to check back pressure updates. We can simply return without scheduling
+ // another update runnable.
+ if (!mBackPressureDelayMS) {
+ return;
+ }
+
+ // Otherwise, if there is a back pressure delay still in effect we need
+ // queue a runnable to check if it can be reduced in the future. Note
+ // that this runnable is dispatched to the ThrottledEventQueue. This
+ // means we will not check for a new value until the current back log
+ // has been processed. The next update will only keep back pressure if
+ // more runnables continue to be dispatched to the queue.
+ nsCOMPtr<nsIRunnable> r =
+ NewRunnableMethod(this, &nsGlobalWindow::CancelOrUpdateBackPressure);
+ MOZ_ALWAYS_SUCCEEDS(queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
+}
+
+//*****************************************************************************
+// nsGlobalWindow::nsIScriptObjectPrincipal
+//*****************************************************************************
+
+nsIPrincipal*
+nsGlobalWindow::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(GetParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetPrincipal();
+ }
+
+ return nullptr;
+}
+
+//*****************************************************************************
+// nsGlobalWindow::nsIDOMWindow
+//*****************************************************************************
+
+template <class T>
+nsIURI*
+nsPIDOMWindow<T>::GetDocumentURI() const
+{
+ return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
+}
+
+template <class T>
+nsIURI*
+nsPIDOMWindow<T>::GetDocBaseURI() const
+{
+ return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get();
+}
+
+template <class T>
+void
+nsPIDOMWindow<T>::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<nsIDocument> document = docShell->GetDocument();
+ Unused << document;
+ }
+}
+
+void
+nsPIDOMWindowOuter::SetInitialKeyboardIndicators(
+ UIStateChangeType aShowAccelerators, UIStateChangeType aShowFocusRings)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(!GetCurrentInnerWindow());
+
+ nsPIDOMWindowOuter* piWin = GetPrivateRoot();
+ if (!piWin) {
+ return;
+ }
+
+ MOZ_ASSERT(piWin == AsOuter());
+
+ // only change the flags that have been modified
+ nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(mChromeEventHandler);
+ if (!windowRoot) {
+ return;
+ }
+
+ if (aShowAccelerators != UIStateChangeType_NoChange) {
+ windowRoot->SetShowAccelerators(aShowAccelerators == UIStateChangeType_Set);
+ }
+ if (aShowFocusRings != UIStateChangeType_NoChange) {
+ windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set);
+ }
+
+ nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(GetOuterWindow(),
+ aShowAccelerators,
+ aShowFocusRings);
+}
+
+Element*
+nsPIDOMWindowOuter::GetFrameElementInternal() const
+{
+ MOZ_ASSERT(IsOuterWindow());
+ return mFrameElement;
+}
+
+void
+nsPIDOMWindowOuter::SetFrameElementInternal(Element* aFrameElement)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ mFrameElement = aFrameElement;
+}
+
+bool
+nsPIDOMWindowInner::AddAudioContext(AudioContext* aAudioContext)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ 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)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ mAudioContexts.RemoveElement(aAudioContext);
+}
+
+void
+nsPIDOMWindowInner::MuteAudioContexts()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ if (!mAudioContexts[i]->IsOffline()) {
+ mAudioContexts[i]->Mute();
+ }
+ }
+}
+
+void
+nsPIDOMWindowInner::UnmuteAudioContexts()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ if (!mAudioContexts[i]->IsOffline()) {
+ mAudioContexts[i]->Unmute();
+ }
+ }
+}
+
+nsGlobalWindow*
+nsGlobalWindow::Window()
+{
+ return this;
+}
+
+nsGlobalWindow*
+nsGlobalWindow::Self()
+{
+ return this;
+}
+
+Navigator*
+nsGlobalWindow::GetNavigator(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mNavigator) {
+ mNavigator = new Navigator(AsInner());
+ }
+
+ return mNavigator;
+}
+
+nsIDOMNavigator*
+nsGlobalWindow::GetNavigator()
+{
+ FORWARD_TO_INNER(GetNavigator, (), nullptr);
+
+ ErrorResult dummy;
+ nsIDOMNavigator* navigator = GetNavigator(dummy);
+ dummy.SuppressException();
+ return navigator;
+}
+
+nsScreen*
+nsGlobalWindow::GetScreen(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mScreen) {
+ mScreen = nsScreen::Create(AsInner());
+ if (!mScreen) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ }
+
+ return mScreen;
+}
+
+nsIDOMScreen*
+nsGlobalWindow::GetScreen()
+{
+ FORWARD_TO_INNER(GetScreen, (), nullptr);
+
+ ErrorResult dummy;
+ nsIDOMScreen* screen = GetScreen(dummy);
+ dummy.SuppressException();
+ return screen;
+}
+
+nsHistory*
+nsGlobalWindow::GetHistory(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mHistory) {
+ mHistory = new nsHistory(AsInner());
+ }
+
+ return mHistory;
+}
+
+CustomElementRegistry*
+nsGlobalWindow::CustomElements()
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+ if (!mCustomElements) {
+ mCustomElements = CustomElementRegistry::Create(AsInner());
+ }
+
+ return mCustomElements;
+}
+
+Performance*
+nsPIDOMWindowInner::GetPerformance()
+{
+ MOZ_ASSERT(IsInnerWindow());
+ CreatePerformanceObjectIfNeeded();
+ return mPerformance;
+}
+
+Performance*
+nsGlobalWindow::GetPerformance()
+{
+ return AsInner()->GetPerformance();
+}
+
+void
+nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ 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) {
+ // If we are dealing with an iframe, we will need the parent's performance
+ // object (so we can add the iframe as a resource of that page).
+ Performance* parentPerformance = nullptr;
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull();
+ if (parentWindow) {
+ nsPIDOMWindowInner* parentInnerWindow = nullptr;
+ if (parentWindow) {
+ parentInnerWindow = parentWindow->GetCurrentInnerWindow();
+ }
+ if (parentInnerWindow) {
+ parentPerformance = parentInnerWindow->GetPerformance();
+ }
+ }
+ mPerformance =
+ Performance::CreateForMainThread(this, timing, timedChannel,
+ parentPerformance);
+ }
+}
+
+bool
+nsPIDOMWindowInner::IsSecureContext() const
+{
+ return nsGlobalWindow::Cast(this)->IsSecureContext();
+}
+
+bool
+nsPIDOMWindowInner::IsSecureContextIfOpenerIgnored() const
+{
+ return nsGlobalWindow::Cast(this)->IsSecureContextIfOpenerIgnored();
+}
+
+void
+nsPIDOMWindowInner::Suspend()
+{
+ nsGlobalWindow::Cast(this)->Suspend();
+}
+
+void
+nsPIDOMWindowInner::Resume()
+{
+ nsGlobalWindow::Cast(this)->Resume();
+}
+
+void
+nsPIDOMWindowInner::Freeze()
+{
+ nsGlobalWindow::Cast(this)->Freeze();
+}
+
+void
+nsPIDOMWindowInner::Thaw()
+{
+ nsGlobalWindow::Cast(this)->Thaw();
+}
+
+void
+nsPIDOMWindowInner::SyncStateFromParentWindow()
+{
+ nsGlobalWindow::Cast(this)->SyncStateFromParentWindow();
+}
+
+SuspendTypes
+nsPIDOMWindowOuter::GetMediaSuspend() const
+{
+ if (IsInnerWindow()) {
+ return mOuterWindow->GetMediaSuspend();
+ }
+
+ return mMediaSuspend;
+}
+
+void
+nsPIDOMWindowOuter::SetMediaSuspend(SuspendTypes aSuspend)
+{
+ if (IsInnerWindow()) {
+ mOuterWindow->SetMediaSuspend(aSuspend);
+ return;
+ }
+
+ if (!IsDisposableSuspend(aSuspend)) {
+ mMediaSuspend = aSuspend;
+ }
+
+ RefreshMediaElementsSuspend(aSuspend);
+}
+
+bool
+nsPIDOMWindowOuter::GetAudioMuted() const
+{
+ if (IsInnerWindow()) {
+ return mOuterWindow->GetAudioMuted();
+ }
+
+ return mAudioMuted;
+}
+
+void
+nsPIDOMWindowOuter::SetAudioMuted(bool aMuted)
+{
+ if (IsInnerWindow()) {
+ mOuterWindow->SetAudioMuted(aMuted);
+ return;
+ }
+
+ if (mAudioMuted == aMuted) {
+ return;
+ }
+
+ mAudioMuted = aMuted;
+ RefreshMediaElementsVolume();
+}
+
+float
+nsPIDOMWindowOuter::GetAudioVolume() const
+{
+ if (IsInnerWindow()) {
+ return mOuterWindow->GetAudioVolume();
+ }
+
+ return mAudioVolume;
+}
+
+nsresult
+nsPIDOMWindowOuter::SetAudioVolume(float aVolume)
+{
+ if (IsInnerWindow()) {
+ return mOuterWindow->SetAudioVolume(aVolume);
+ }
+
+ if (aVolume < 0.0) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ if (mAudioVolume == aVolume) {
+ return NS_OK;
+ }
+
+ mAudioVolume = aVolume;
+ RefreshMediaElementsVolume();
+ return NS_OK;
+}
+
+void
+nsPIDOMWindowOuter::RefreshMediaElementsVolume()
+{
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ service->RefreshAgentsVolume(GetOuterWindow());
+ }
+}
+
+void
+nsPIDOMWindowOuter::RefreshMediaElementsSuspend(SuspendTypes aSuspend)
+{
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ service->RefreshAgentsSuspend(GetOuterWindow(), aSuspend);
+ }
+}
+
+bool
+nsPIDOMWindowOuter::IsDisposableSuspend(SuspendTypes aSuspend) const
+{
+ return (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
+ aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
+}
+
+void
+nsPIDOMWindowOuter::SetServiceWorkersTestingEnabled(bool aEnabled)
+{
+ // Devtools should only be setting this on the top level window. Its
+ // ok if devtools clears the flag on clean up of nested windows, though.
+ // It will have no affect.
+#ifdef DEBUG
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
+ MOZ_ASSERT_IF(aEnabled, this == topWindow);
+#endif
+ mServiceWorkersTestingEnabled = aEnabled;
+}
+
+bool
+nsPIDOMWindowOuter::GetServiceWorkersTestingEnabled()
+{
+ // Automatically get this setting from the top level window so that nested
+ // iframes get the correct devtools setting.
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop();
+ if (!topWindow) {
+ return false;
+ }
+ return topWindow->mServiceWorkersTestingEnabled;
+}
+
+bool
+nsPIDOMWindowInner::GetAudioCaptured() const
+{
+ MOZ_ASSERT(IsInnerWindow());
+ return mAudioCaptured;
+}
+
+nsresult
+nsPIDOMWindowInner::SetAudioCapture(bool aCapture)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ mAudioCaptured = aCapture;
+
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture);
+ }
+
+ return NS_OK;
+}
+
+// nsISpeechSynthesisGetter
+
+#ifdef MOZ_WEBSPEECH
+SpeechSynthesis*
+nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mSpeechSynthesis) {
+ mSpeechSynthesis = new SpeechSynthesis(AsInner());
+ }
+
+ return mSpeechSynthesis;
+}
+
+bool
+nsGlobalWindow::HasActiveSpeechSynthesis()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ if (mSpeechSynthesis) {
+ return !mSpeechSynthesis->HasEmptyQueue();
+ }
+
+ return false;
+}
+
+#endif
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetParentOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> parent;
+ if (mDocShell->GetIsMozBrowserOrApp()) {
+ parent = AsOuter();
+ } else {
+ parent = GetParent();
+ }
+
+ return parent.forget();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetParent(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr);
+}
+
+/**
+ * GetScriptableParent is called when script reads window.parent.
+ *
+ * In contrast to GetRealParent, GetScriptableParent respects <iframe
+ * mozbrowser> boundaries, so if |this| is contained by an <iframe
+ * mozbrowser>, we will return |this| as its own parent.
+ */
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetScriptableParent()
+{
+ FORWARD_TO_OUTER(GetScriptableParent, (), nullptr);
+
+ nsCOMPtr<nsPIDOMWindowOuter> parent = GetParentOuter();
+ return parent.get();
+}
+
+/**
+ * Behavies identically to GetScriptableParent extept that it returns null
+ * if GetScriptableParent would return this window.
+ */
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetScriptableParentOrNull()
+{
+ FORWARD_TO_OUTER(GetScriptableParentOrNull, (), nullptr);
+
+ nsPIDOMWindowOuter* parent = GetScriptableParent();
+ return (Cast(parent) == this) ? nullptr : parent;
+}
+
+/**
+ * nsPIDOMWindow::GetParent (when called from C++) is just a wrapper around
+ * GetRealParent.
+ */
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetParent()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> parent;
+ mDocShell->GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent));
+
+ if (parent) {
+ nsCOMPtr<nsPIDOMWindowOuter> win = parent->GetWindow();
+ return win.forget();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win(AsOuter());
+ return win.forget();
+}
+
+static nsresult
+GetTopImpl(nsGlobalWindow* aWin, nsPIDOMWindowOuter** aTop, bool aScriptable)
+{
+ *aTop = nullptr;
+
+ // Walk up the parent chain.
+
+ nsCOMPtr<nsPIDOMWindowOuter> prevParent = aWin->AsOuter();
+ nsCOMPtr<nsPIDOMWindowOuter> parent = aWin->AsOuter();
+ do {
+ if (!parent) {
+ break;
+ }
+
+ prevParent = parent;
+
+ nsCOMPtr<nsPIDOMWindowOuter> newParent;
+ if (aScriptable) {
+ newParent = parent->GetScriptableParent();
+ }
+ else {
+ newParent = parent->GetParent();
+ }
+
+ parent = newParent;
+
+ } while (parent != prevParent);
+
+ if (parent) {
+ parent.swap(*aTop);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * GetScriptableTop is called when script reads window.top.
+ *
+ * In contrast to GetRealTop, GetScriptableTop 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*
+nsGlobalWindow::GetScriptableTop()
+{
+ FORWARD_TO_OUTER(GetScriptableTop, (), nullptr);
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetTopImpl(this, getter_AddRefs(window), /* aScriptable = */ true);
+ return window.get();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetTop()
+{
+ MOZ_ASSERT(IsOuterWindow());
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetTopImpl(this, getter_AddRefs(window), /* aScriptable = */ false);
+ return window.forget();
+}
+
+void
+nsGlobalWindow::GetContentOuter(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsPIDOMWindowOuter> content =
+ GetContentInternal(aError, !nsContentUtils::IsCallerChrome());
+ if (aError.Failed()) {
+ return;
+ }
+
+ if (content) {
+ JS::Rooted<JS::Value> val(aCx);
+ aError = nsContentUtils::WrapNative(aCx, content, &val);
+ if (aError.Failed()) {
+ return;
+ }
+
+ aRetval.set(&val.toObject());
+ return;
+ }
+
+ aRetval.set(nullptr);
+ return;
+}
+
+void
+nsGlobalWindow::GetContent(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetContentOuter, (aCx, aRetval, aError), aError, );
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetContentInternal(ErrorResult& aError, bool aUnprivilegedCaller)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ // First check for a named frame named "content"
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+ GetChildWindow(NS_LITERAL_STRING("content"));
+ if (domWindow) {
+ return domWindow.forget();
+ }
+
+ // If we're contained in <iframe mozbrowser> or <iframe mozapp>, then
+ // GetContent is the same as window.top.
+ if (mDocShell && mDocShell->GetIsInMozBrowserOrApp()) {
+ return GetTopOuter();
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> primaryContent;
+ if (aUnprivilegedCaller) {
+ // If we're called by non-chrome code, make sure we don't return
+ // the primary content window if the calling tab is hidden. In
+ // such a case we return the same-type root in the hidden tab,
+ // which is "good enough", for now.
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(mDocShell));
+
+ if (baseWin) {
+ bool visible = false;
+ baseWin->GetVisibility(&visible);
+
+ if (!visible) {
+ mDocShell->GetSameTypeRootTreeItem(getter_AddRefs(primaryContent));
+ }
+ }
+ }
+
+ if (!primaryContent) {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+ if (!treeOwner) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ treeOwner->GetPrimaryContentShell(getter_AddRefs(primaryContent));
+ }
+
+ if (!primaryContent) {
+ return nullptr;
+ }
+
+ domWindow = primaryContent->GetWindow();
+ return domWindow.forget();
+}
+
+MozSelfSupport*
+nsGlobalWindow::GetMozSelfSupport(ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ if (mMozSelfSupport) {
+ return mMozSelfSupport;
+ }
+
+ // We're called from JS and want to use out existing JSContext (and,
+ // importantly, its compartment!) here.
+ AutoJSContext cx;
+ GlobalObject global(cx, FastGetGlobalJSObject());
+ mMozSelfSupport = MozSelfSupport::Constructor(global, cx, aError);
+ return mMozSelfSupport;
+}
+
+nsresult
+nsGlobalWindow::GetScriptableContent(JSContext* aCx, JS::MutableHandle<JS::Value> aVal)
+{
+ ErrorResult rv;
+ JS::Rooted<JSObject*> content(aCx);
+ GetContent(aCx, &content, rv);
+ if (!rv.Failed()) {
+ aVal.setObjectOrNull(content);
+ }
+
+ return rv.StealNSResult();
+}
+
+nsresult
+nsGlobalWindow::GetPrompter(nsIPrompt** aPrompt)
+{
+ if (IsInnerWindow()) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (!outer) {
+ NS_WARNING("No outer window available!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return outer->GetPrompter(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;
+}
+
+BarProp*
+nsGlobalWindow::GetMenubar(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mMenubar) {
+ mMenubar = new MenubarProp(this);
+ }
+
+ return mMenubar;
+}
+
+BarProp*
+nsGlobalWindow::GetToolbar(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mToolbar) {
+ mToolbar = new ToolbarProp(this);
+ }
+
+ return mToolbar;
+}
+
+BarProp*
+nsGlobalWindow::GetLocationbar(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mLocationbar) {
+ mLocationbar = new LocationbarProp(this);
+ }
+ return mLocationbar;
+}
+
+BarProp*
+nsGlobalWindow::GetPersonalbar(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mPersonalbar) {
+ mPersonalbar = new PersonalbarProp(this);
+ }
+ return mPersonalbar;
+}
+
+BarProp*
+nsGlobalWindow::GetStatusbar(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mStatusbar) {
+ mStatusbar = new StatusbarProp(this);
+ }
+ return mStatusbar;
+}
+
+BarProp*
+nsGlobalWindow::GetScrollbars(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mScrollbars) {
+ mScrollbars = new ScrollbarsProp(this);
+ }
+
+ return mScrollbars;
+}
+
+bool
+nsGlobalWindow::GetClosedOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ // If someone called close(), or if we don't have a docshell, we're closed.
+ return mIsClosed || !mDocShell;
+}
+
+bool
+nsGlobalWindow::GetClosed(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetClosedOuter, (), aError, false);
+}
+
+bool
+nsGlobalWindow::Closed()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ return GetClosedOuter();
+}
+
+nsDOMWindowList*
+nsGlobalWindow::GetWindowList()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (!mFrames && mDocShell) {
+ mFrames = new nsDOMWindowList(mDocShell);
+ }
+
+ return mFrames;
+}
+
+already_AddRefed<nsIDOMWindowCollection>
+nsGlobalWindow::GetFrames()
+{
+ FORWARD_TO_OUTER(GetFrames, (), nullptr);
+
+ nsCOMPtr<nsIDOMWindowCollection> frames = GetWindowList();
+ return frames.forget();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::IndexedGetterOuter(uint32_t aIndex)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsDOMWindowList* windows = GetWindowList();
+ NS_ENSURE_TRUE(windows, nullptr);
+
+ return windows->IndexedGetter(aIndex);
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::IndexedGetter(uint32_t aIndex)
+{
+ FORWARD_TO_OUTER(IndexedGetterOuter, (aIndex), nullptr);
+ MOZ_CRASH();
+}
+
+bool
+nsGlobalWindow::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ // Note: Keep this in sync with MayResolve.
+
+ // Note: The infallibleInit call in GlobalResolve depends on this check.
+ if (!JSID_IS_STRING(aId)) {
+ return true;
+ }
+
+ bool found;
+ if (!WebIDLGlobalNameHash::DefineIfEnabled(aCx, aObj, aId, aDesc, &found)) {
+ return false;
+ }
+
+ if (found) {
+ return true;
+ }
+
+ nsresult rv = nsWindowSH::GlobalResolve(this, aCx, aObj, aId, aDesc);
+ if (NS_FAILED(rv)) {
+ return Throw(aCx, rv);
+ }
+
+ return true;
+}
+
+/* static */
+bool
+nsGlobalWindow::MayResolve(jsid aId)
+{
+ // Note: This function does not fail and may not have any side-effects.
+ // Note: Keep this in sync with DoResolve.
+ if (!JSID_IS_STRING(aId)) {
+ return false;
+ }
+
+ if (aId == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
+ return true;
+ }
+
+ if (aId == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS)) {
+ // We only resolve .controllers in release builds and on non-chrome windows,
+ // but let's not worry about any of that stuff.
+ return true;
+ }
+
+ if (WebIDLGlobalNameHash::MayResolve(aId)) {
+ return true;
+ }
+
+ nsScriptNameSpaceManager *nameSpaceManager = PeekNameSpaceManager();
+ if (!nameSpaceManager) {
+ // Really shouldn't happen. Fail safe.
+ return true;
+ }
+
+ nsAutoString name;
+ AssignJSFlatString(name, JSID_TO_FLAT_STRING(aId));
+
+ return nameSpaceManager->LookupName(name);
+}
+
+void
+nsGlobalWindow::GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ // "Components" is marked as enumerable but only resolved on demand :-/.
+ //aNames.AppendElement(NS_LITERAL_STRING("Components"));
+
+ nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
+ if (nameSpaceManager) {
+ JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
+
+ WebIDLGlobalNameHash::GetNames(aCx, wrapper, aNames);
+
+ for (auto i = nameSpaceManager->GlobalNameIter(); !i.Done(); i.Next()) {
+ const GlobalNameMapEntry* entry = i.Get();
+ if (nsWindowSH::NameStructEnabled(aCx, this, entry->mKey,
+ entry->mGlobalName)) {
+ aNames.AppendElement(entry->mKey);
+ }
+ }
+ }
+}
+
+/* static */ bool
+nsGlobalWindow::IsPrivilegedChromeWindow(JSContext* aCx, JSObject* aObj)
+{
+ // For now, have to deal with XPConnect objects here.
+ return xpc::WindowOrNull(aObj)->IsChromeWindow() &&
+ nsContentUtils::ObjectPrincipal(aObj) == nsContentUtils::GetSystemPrincipal();
+}
+
+/* static */ bool
+nsGlobalWindow::IsShowModalDialogEnabled(JSContext*, JSObject*)
+{
+ static bool sAddedPrefCache = false;
+ static bool sIsDisabled;
+ static const char sShowModalDialogPref[] = "dom.disable_window_showModalDialog";
+
+ if (!sAddedPrefCache) {
+ Preferences::AddBoolVarCache(&sIsDisabled, sShowModalDialogPref, false);
+ sAddedPrefCache = true;
+ }
+
+ return !sIsDisabled && !XRE_IsContentProcess();
+}
+
+nsIDOMOfflineResourceList*
+nsGlobalWindow::GetApplicationCache(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mApplicationCache) {
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(GetDocShell()));
+ if (!webNav || !mDoc) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aError = webNav->GetCurrentURI(getter_AddRefs(uri));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> manifestURI;
+ nsContentUtils::GetOfflineAppManifest(mDoc, getter_AddRefs(manifestURI));
+
+ RefPtr<nsDOMOfflineResourceList> applicationCache =
+ new nsDOMOfflineResourceList(manifestURI, uri, mDoc->NodePrincipal(),
+ AsInner());
+
+ applicationCache->Init();
+
+ mApplicationCache = applicationCache;
+ }
+
+ return mApplicationCache;
+}
+
+already_AddRefed<nsIDOMOfflineResourceList>
+nsGlobalWindow::GetApplicationCache()
+{
+ FORWARD_TO_INNER(GetApplicationCache, (), nullptr);
+
+ ErrorResult dummy;
+ nsCOMPtr<nsIDOMOfflineResourceList> applicationCache =
+ GetApplicationCache(dummy);
+ dummy.SuppressException();
+ return applicationCache.forget();
+}
+
+Crypto*
+nsGlobalWindow::GetCrypto(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mCrypto) {
+ mCrypto = new Crypto();
+ mCrypto->Init(this);
+ }
+ return mCrypto;
+}
+
+mozilla::dom::U2F*
+nsGlobalWindow::GetU2f(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mU2F) {
+ RefPtr<U2F> u2f = new U2F();
+ u2f->Init(AsInner(), aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+
+ mU2F = u2f;
+ }
+ return mU2F;
+}
+
+nsIControllers*
+nsGlobalWindow::GetControllersOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mControllers) {
+ nsresult rv;
+ mControllers = do_CreateInstance(kXULControllersCID, &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ // Add in the default controller
+ nsCOMPtr<nsIController> controller = do_CreateInstance(
+ NS_WINDOWCONTROLLER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ mControllers->InsertControllerAt(0, controller);
+ nsCOMPtr<nsIControllerContext> controllerContext = do_QueryInterface(controller);
+ if (!controllerContext) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ controllerContext->SetCommandContext(static_cast<nsIDOMWindow*>(this));
+ }
+
+ return mControllers;
+}
+
+nsIControllers*
+nsGlobalWindow::GetControllers(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetControllersOuter, (aError), aError, nullptr);
+}
+
+nsresult
+nsGlobalWindow::GetControllers(nsIControllers** aResult)
+{
+ FORWARD_TO_INNER(GetControllers, (aResult), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ nsCOMPtr<nsIControllers> controllers = GetControllers(rv);
+ controllers.forget(aResult);
+
+ return rv.StealNSResult();
+}
+
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetSanitizedOpener(nsPIDOMWindowOuter* aOpener)
+{
+ if (!aOpener) {
+ return nullptr;
+ }
+
+ nsGlobalWindow* win = nsGlobalWindow::Cast(aOpener);
+
+ // First, ensure that we're not handing back a chrome window to content:
+ if (win->IsChromeWindow()) {
+ return nullptr;
+ }
+
+ // We don't want to reveal the opener if the opener is a mail window,
+ // because opener can be used to spoof the contents of a message (bug 105050).
+ // So, we look in the opener's root docshell to see if it's a mail window.
+ nsCOMPtr<nsIDocShell> openerDocShell = aOpener->GetDocShell();
+
+ if (openerDocShell) {
+ nsCOMPtr<nsIDocShellTreeItem> openerRootItem;
+ openerDocShell->GetRootTreeItem(getter_AddRefs(openerRootItem));
+ nsCOMPtr<nsIDocShell> openerRootDocShell(do_QueryInterface(openerRootItem));
+ if (openerRootDocShell) {
+ uint32_t appType;
+ nsresult rv = openerRootDocShell->GetAppType(&appType);
+ if (NS_SUCCEEDED(rv) && appType != nsIDocShell::APP_TYPE_MAIL) {
+ return aOpener;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetOpenerWindowOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsPIDOMWindowOuter> opener = do_QueryReferent(mOpener);
+
+ if (!opener) {
+ return nullptr;
+ }
+
+ // First, check if we were called from a privileged chrome script
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ // Catch the case where we're chrome but the opener is not...
+ if (GetPrincipal() == nsContentUtils::GetSystemPrincipal() &&
+ nsGlobalWindow::Cast(opener)->GetPrincipal() != nsContentUtils::GetSystemPrincipal()) {
+ return nullptr;
+ }
+ return opener;
+ }
+
+ return GetSanitizedOpener(opener);
+}
+
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetOpenerWindow(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetOpenerWindowOuter, (), aError, nullptr);
+}
+
+void
+nsGlobalWindow::GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindow(aError);
+ if (aError.Failed() || !opener) {
+ aRetval.setNull();
+ return;
+ }
+
+ aError = nsContentUtils::WrapNative(aCx, opener, aRetval);
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetOpener()
+{
+ FORWARD_TO_OUTER(GetOpener, (), nullptr);
+
+ nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindowOuter();
+ return opener.forget();
+}
+
+void
+nsGlobalWindow::SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener,
+ ErrorResult& aError)
+{
+ // Check if we were called from a privileged chrome script. If not, and if
+ // aOpener is not null, 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.
+ if (!aOpener.isNull() && !nsContentUtils::IsCallerChrome()) {
+ RedefineProperty(aCx, "opener", aOpener, aError);
+ return;
+ }
+
+ if (!aOpener.isObjectOrNull()) {
+ // Chrome code trying to set some random value as opener
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ nsPIDOMWindowInner* win = nullptr;
+ if (aOpener.isObject()) {
+ JSObject* unwrapped = js::CheckedUnwrap(&aOpener.toObject(),
+ /* stopAtWindowProxy = */ false);
+ if (!unwrapped) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ auto* globalWindow = xpc::WindowOrNull(unwrapped);
+ if (!globalWindow) {
+ // Wasn't a window
+ aError.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ win = globalWindow->AsInner();
+ }
+
+ nsPIDOMWindowOuter* outer = nullptr;
+ if (win) {
+ if (!win->IsCurrentInnerWindow()) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ outer = win->GetOuterWindow();
+ }
+
+ SetOpenerWindow(outer, false);
+}
+
+void
+nsGlobalWindow::GetStatusOuter(nsAString& aStatus)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ aStatus = mStatus;
+}
+
+void
+nsGlobalWindow::GetStatus(nsAString& aStatus, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetStatusOuter, (aStatus), aError, );
+}
+
+void
+nsGlobalWindow::SetStatusOuter(const nsAString& aStatus)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ mStatus = aStatus;
+
+ /*
+ * If caller is not chrome and dom.disable_window_status_change is true,
+ * prevent propagating window.status to the UI by exiting early
+ */
+
+ if (!CanSetProperty("dom.disable_window_status_change")) {
+ return;
+ }
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetWebBrowserChrome();
+ if (browserChrome) {
+ browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_SCRIPT,
+ PromiseFlatString(aStatus).get());
+ }
+}
+
+void
+nsGlobalWindow::SetStatus(const nsAString& aStatus, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetStatusOuter, (aStatus), aError, );
+}
+
+void
+nsGlobalWindow::GetNameOuter(nsAString& aName)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (mDocShell) {
+ mDocShell->GetName(aName);
+ }
+}
+
+void
+nsGlobalWindow::GetName(nsAString& aName, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetNameOuter, (aName), aError, );
+}
+
+void
+nsGlobalWindow::SetNameOuter(const nsAString& aName, mozilla::ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (mDocShell) {
+ aError = mDocShell->SetName(aName);
+ }
+}
+
+void
+nsGlobalWindow::SetName(const nsAString& aName, mozilla::ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetNameOuter, (aName, aError), aError, );
+}
+
+// Helper functions used by many methods below.
+int32_t
+nsGlobalWindow::DevToCSSIntPixels(int32_t px)
+{
+ if (!mDocShell)
+ return px; // assume 1:1
+
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ if (!presContext)
+ return px;
+
+ return presContext->DevPixelsToIntCSSPixels(px);
+}
+
+int32_t
+nsGlobalWindow::CSSToDevIntPixels(int32_t px)
+{
+ if (!mDocShell)
+ return px; // assume 1:1
+
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ if (!presContext)
+ return px;
+
+ return presContext->CSSPixelsToDevPixels(px);
+}
+
+nsIntSize
+nsGlobalWindow::DevToCSSIntPixels(nsIntSize px)
+{
+ if (!mDocShell)
+ return px; // assume 1:1
+
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ if (!presContext)
+ return px;
+
+ return nsIntSize(
+ presContext->DevPixelsToIntCSSPixels(px.width),
+ presContext->DevPixelsToIntCSSPixels(px.height));
+}
+
+nsIntSize
+nsGlobalWindow::CSSToDevIntPixels(nsIntSize px)
+{
+ if (!mDocShell)
+ return px; // assume 1:1
+
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ if (!presContext)
+ return px;
+
+ return nsIntSize(
+ presContext->CSSPixelsToDevPixels(px.width),
+ presContext->CSSPixelsToDevPixels(px.height));
+}
+
+nsresult
+nsGlobalWindow::GetInnerSize(CSSIntSize& aSize)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ EnsureSizeUpToDate();
+
+ NS_ENSURE_STATE(mDocShell);
+
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+
+ if (!presContext || !presShell) {
+ aSize = CSSIntSize(0, 0);
+ return NS_OK;
+ }
+
+ /*
+ * On platforms with resolution-based zooming, the CSS viewport
+ * and visual viewport may not be the same. The inner size should
+ * be the visual viewport, but we fall back to the CSS viewport
+ * if it is not set.
+ */
+ if (presShell->IsScrollPositionClampingScrollPortSizeSet()) {
+ aSize = CSSIntRect::FromAppUnitsRounded(
+ presShell->GetScrollPositionClampingScrollPortSize());
+ } else {
+ RefPtr<nsViewManager> viewManager = presShell->GetViewManager();
+ if (viewManager) {
+ viewManager->FlushDelayedResize(false);
+ }
+
+ aSize = CSSIntRect::FromAppUnitsRounded(
+ presContext->GetVisibleArea().Size());
+ }
+ return NS_OK;
+}
+
+int32_t
+nsGlobalWindow::GetInnerWidthOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ CSSIntSize size;
+ aError = GetInnerSize(size);
+ return size.width;
+}
+
+int32_t
+nsGlobalWindow::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 nsGlobalWindow::GetInnerWidth(int32_t* aInnerWidth)
+ // to actually take a useful CallerType and pass it in here.
+ FORWARD_TO_OUTER_OR_THROW(GetInnerWidthOuter, (aError), aError, 0);
+}
+
+void
+nsGlobalWindow::GetInnerWidth(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetInnerWidth, aValue,
+ aCallerType, aError);
+}
+
+nsresult
+nsGlobalWindow::GetInnerWidth(int32_t* aInnerWidth)
+{
+ FORWARD_TO_INNER(GetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ // Callee doesn't care about the caller type, but play it safe.
+ *aInnerWidth = GetInnerWidth(CallerType::NonSystem, rv);
+
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ CheckSecurityWidthAndHeight(&aInnerWidth, nullptr, aCallerIsChrome);
+
+ RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+
+ if (presShell && presShell->GetIsViewportOverridden())
+ {
+ nscoord height = 0;
+
+ RefPtr<nsPresContext> presContext;
+ presContext = presShell->GetPresContext();
+
+ nsRect shellArea = presContext->GetVisibleArea();
+ height = shellArea.height;
+ SetCSSViewportWidthAndHeight(nsPresContext::CSSPixelsToAppUnits(aInnerWidth),
+ height);
+ return;
+ }
+
+ int32_t height = 0;
+ int32_t unused = 0;
+
+ nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
+ docShellAsWin->GetSize(&unused, &height);
+ aError = SetDocShellWidthAndHeight(CSSToDevIntPixels(aInnerWidth), height);
+}
+
+void
+nsGlobalWindow::SetInnerWidth(int32_t aInnerWidth, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter, (aInnerWidth, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::SetInnerWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetInnerWidth,
+ aValue, "innerWidth", aError);
+}
+
+int32_t
+nsGlobalWindow::GetInnerHeightOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ CSSIntSize size;
+ aError = GetInnerSize(size);
+ return size.height;
+}
+
+int32_t
+nsGlobalWindow::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 nsGlobalWindow::GetInnerHeight(int32_t* aInnerWidth)
+ // to actually take a useful CallerType and pass it in here.
+ FORWARD_TO_OUTER_OR_THROW(GetInnerHeightOuter, (aError), aError, 0);
+}
+
+void
+nsGlobalWindow::GetInnerHeight(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetInnerHeight, aValue,
+ aCallerType, aError);
+}
+
+nsresult
+nsGlobalWindow::GetInnerHeight(int32_t* aInnerHeight)
+{
+ FORWARD_TO_INNER(GetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ // Callee doesn't care about the caller type, but play it safe.
+ *aInnerHeight = GetInnerHeight(CallerType::NonSystem, rv);
+
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+
+ if (presShell && presShell->GetIsViewportOverridden())
+ {
+ RefPtr<nsPresContext> presContext;
+ presContext = presShell->GetPresContext();
+
+ nsRect shellArea = presContext->GetVisibleArea();
+ nscoord height = aInnerHeight;
+ nscoord width = shellArea.width;
+ CheckSecurityWidthAndHeight(nullptr, &height, aCallerIsChrome);
+ SetCSSViewportWidthAndHeight(width,
+ nsPresContext::CSSPixelsToAppUnits(height));
+ return;
+ }
+
+ int32_t height = 0;
+ int32_t width = 0;
+
+ nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
+ docShellAsWin->GetSize(&width, &height);
+ CheckSecurityWidthAndHeight(nullptr, &aInnerHeight, aCallerIsChrome);
+ aError = SetDocShellWidthAndHeight(width, CSSToDevIntPixels(aInnerHeight));
+}
+
+void
+nsGlobalWindow::SetInnerHeight(int32_t aInnerHeight, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter, (aInnerHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::SetInnerHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ CallerType aCallerType, ErrorResult& aError)
+{
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetInnerHeight,
+ aValue, "innerHeight", aError);
+}
+
+nsIntSize
+nsGlobalWindow::GetOuterSize(CallerType aCallerType, ErrorResult& aError)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (nsContentUtils::ResistFingerprinting(aCallerType)) {
+ CSSIntSize size;
+ aError = GetInnerSize(size);
+ return nsIntSize(size.width, size.height);
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nsIntSize(0, 0);
+ }
+
+ nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot());
+ if (rootWindow) {
+ rootWindow->FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsIntSize sizeDevPixels;
+ aError = treeOwnerAsWin->GetSize(&sizeDevPixels.width, &sizeDevPixels.height);
+ if (aError.Failed()) {
+ return nsIntSize();
+ }
+
+ return DevToCSSIntPixels(sizeDevPixels);
+}
+
+int32_t
+nsGlobalWindow::GetOuterWidthOuter(CallerType aCallerType, ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ return GetOuterSize(aCallerType, aError).width;
+}
+
+int32_t
+nsGlobalWindow::GetOuterWidth(CallerType aCallerType, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetOuterWidthOuter, (aCallerType, aError),
+ aError, 0);
+}
+
+void
+nsGlobalWindow::GetOuterWidth(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetOuterWidth, aValue,
+ aCallerType, aError);
+}
+
+int32_t
+nsGlobalWindow::GetOuterHeightOuter(CallerType aCallerType, ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ return GetOuterSize(aCallerType, aError).height;
+}
+
+int32_t
+nsGlobalWindow::GetOuterHeight(CallerType aCallerType, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetOuterHeightOuter, (aCallerType, aError),
+ aError, 0);
+}
+
+void
+nsGlobalWindow::GetOuterHeight(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetOuterHeight, aValue,
+ aCallerType, aError);
+}
+
+void
+nsGlobalWindow::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
+ ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ CheckSecurityWidthAndHeight(aIsWidth ? &aLengthCSSPixels : nullptr,
+ aIsWidth ? nullptr : &aLengthCSSPixels,
+ aCallerIsChrome);
+
+ int32_t width, height;
+ aError = treeOwnerAsWin->GetSize(&width, &height);
+ if (aError.Failed()) {
+ return;
+ }
+
+ int32_t lengthDevPixels = CSSToDevIntPixels(aLengthCSSPixels);
+ if (aIsWidth) {
+ width = lengthDevPixels;
+ } else {
+ height = lengthDevPixels;
+ }
+ aError = treeOwnerAsWin->SetSize(width, height, true);
+
+ CheckForDPIChange();
+}
+
+void
+nsGlobalWindow::SetOuterWidthOuter(int32_t aOuterWidth, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ SetOuterSize(aOuterWidth, true, aError, aCallerIsChrome);
+}
+
+void
+nsGlobalWindow::SetOuterWidth(int32_t aOuterWidth, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter, (aOuterWidth, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::SetOuterWidth(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetOuterWidth,
+ aValue, "outerWidth", aError);
+}
+
+void
+nsGlobalWindow::SetOuterHeightOuter(int32_t aOuterHeight, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ SetOuterSize(aOuterHeight, false, aError, aCallerIsChrome);
+}
+
+void
+nsGlobalWindow::SetOuterHeight(int32_t aOuterHeight, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter, (aOuterHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::SetOuterHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetOuterHeight,
+ aValue, "outerHeight", aError);
+}
+
+CSSIntPoint
+nsGlobalWindow::GetScreenXY(CallerType aCallerType, ErrorResult& aError)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ // When resisting fingerprinting, always return (0,0)
+ if (nsContentUtils::ResistFingerprinting(aCallerType)) {
+ return CSSIntPoint(0, 0);
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return CSSIntPoint(0, 0);
+ }
+
+ int32_t x = 0, y = 0;
+ aError = treeOwnerAsWin->GetPosition(&x, &y); // LayoutDevice px values
+
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ if (!presContext) {
+ return CSSIntPoint(x, y);
+ }
+
+ // Find the global desktop coordinate of the top-left of the screen.
+ // We'll use this as a "fake origin" when converting to CSS px units,
+ // to avoid overlapping coordinates in cases such as a hi-dpi screen
+ // placed to the right of a lo-dpi screen on Windows. (Instead, there
+ // may be "gaps" in the resulting CSS px coordinates in some cases.)
+ nsDeviceContext *dc = presContext->DeviceContext();
+ nsRect screenRect;
+ dc->GetRect(screenRect);
+ LayoutDeviceRect screenRectDev =
+ LayoutDevicePixel::FromAppUnits(screenRect, dc->AppUnitsPerDevPixel());
+
+ DesktopToLayoutDeviceScale scale = dc->GetDesktopToDeviceScale();
+ DesktopRect screenRectDesk = screenRectDev / scale;
+
+ CSSPoint cssPt =
+ LayoutDevicePoint(x - screenRectDev.x, y - screenRectDev.y) /
+ presContext->CSSToDevPixelScale();
+ cssPt.x += screenRectDesk.x;
+ cssPt.y += screenRectDesk.y;
+
+ return CSSIntPoint(NSToIntRound(cssPt.x), NSToIntRound(cssPt.y));
+}
+
+int32_t
+nsGlobalWindow::GetScreenXOuter(CallerType aCallerType, ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ return GetScreenXY(aCallerType, aError).x;
+}
+
+int32_t
+nsGlobalWindow::GetScreenX(CallerType aCallerType, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetScreenXOuter, (aCallerType, aError), aError, 0);
+}
+
+void
+nsGlobalWindow::GetScreenX(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetScreenX, aValue,
+ aCallerType, aError);
+}
+
+nsRect
+nsGlobalWindow::GetInnerScreenRect()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return nsRect();
+ }
+
+ nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot());
+ if (rootWindow) {
+ rootWindow->FlushPendingNotifications(Flush_Layout);
+ }
+
+ if (!mDocShell) {
+ return nsRect();
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return nsRect();
+ }
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame) {
+ return nsRect();
+ }
+
+ return rootFrame->GetScreenRectInAppUnits();
+}
+
+float
+nsGlobalWindow::GetMozInnerScreenXOuter(CallerType aCallerType)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ // When resisting fingerprinting, always return 0.
+ if (nsContentUtils::ResistFingerprinting(aCallerType)) {
+ return 0.0;
+ }
+
+ nsRect r = GetInnerScreenRect();
+ return nsPresContext::AppUnitsToFloatCSSPixels(r.x);
+}
+
+float
+nsGlobalWindow::GetMozInnerScreenX(CallerType aCallerType, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenXOuter, (aCallerType), aError, 0);
+}
+
+float
+nsGlobalWindow::GetMozInnerScreenYOuter(CallerType aCallerType)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ // Return 0 to prevent fingerprinting.
+ if (nsContentUtils::ResistFingerprinting(aCallerType)) {
+ return 0.0;
+ }
+
+ nsRect r = GetInnerScreenRect();
+ return nsPresContext::AppUnitsToFloatCSSPixels(r.y);
+}
+
+float
+nsGlobalWindow::GetMozInnerScreenY(CallerType aCallerType, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenYOuter, (aCallerType), aError, 0);
+}
+
+float
+nsGlobalWindow::GetDevicePixelRatioOuter(CallerType aCallerType)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return 1.0;
+ }
+
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ if (!presContext) {
+ return 1.0;
+ }
+
+ if (nsContentUtils::ResistFingerprinting(aCallerType)) {
+ return 1.0;
+ }
+
+ float overrideDPPX = presContext->GetOverrideDPPX();
+
+ if (overrideDPPX > 0) {
+ return overrideDPPX;
+ }
+
+ return float(nsPresContext::AppUnitsPerCSSPixel())/
+ presContext->AppUnitsPerDevPixel();
+}
+
+float
+nsGlobalWindow::GetDevicePixelRatio(CallerType aCallerType, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetDevicePixelRatioOuter, (aCallerType), aError, 0.0);
+}
+
+float
+nsPIDOMWindowOuter::GetDevicePixelRatio(CallerType aCallerType)
+{
+ return nsGlobalWindow::Cast(this)->GetDevicePixelRatioOuter(aCallerType);
+}
+
+uint64_t
+nsGlobalWindow::GetMozPaintCountOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return 0;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+ return presShell ? presShell->GetPaintCount() : 0;
+}
+
+uint64_t
+nsGlobalWindow::GetMozPaintCount(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetMozPaintCountOuter, (), aError, 0);
+}
+
+int32_t
+nsGlobalWindow::RequestAnimationFrame(FrameRequestCallback& aCallback,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mDoc) {
+ return 0;
+ }
+
+ if (GetWrapperPreserveColor()) {
+ js::NotifyAnimationActivity(GetWrapperPreserveColor());
+ }
+
+ int32_t handle;
+ aError = mDoc->ScheduleFrameRequestCallback(aCallback, &handle);
+ return handle;
+}
+
+void
+nsGlobalWindow::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mDoc) {
+ return;
+ }
+
+ mDoc->CancelFrameRequestCallback(aHandle);
+}
+
+already_AddRefed<MediaQueryList>
+nsGlobalWindow::MatchMediaOuter(const nsAString& aMediaQueryList)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDoc) {
+ return nullptr;
+ }
+
+ return mDoc->MatchMedia(aMediaQueryList);
+}
+
+already_AddRefed<MediaQueryList>
+nsGlobalWindow::MatchMedia(const nsAString& aMediaQueryList,
+ ErrorResult& aError)
+{
+ // FIXME: This whole forward-to-outer and then get a pres
+ // shell/context off the docshell dance is sort of silly; it'd make
+ // more sense to forward to the inner, but it's what everyone else
+ // (GetSelection, GetScrollXY, etc.) does around here.
+ FORWARD_TO_OUTER_OR_THROW(MatchMediaOuter, (aMediaQueryList), aError, nullptr);
+}
+
+void
+nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ int32_t x, y;
+ aError = treeOwnerAsWin->GetPosition(&x, &y);
+ if (aError.Failed()) {
+ return;
+ }
+
+ CheckSecurityLeftAndTop(&aScreenX, nullptr, aCallerIsChrome);
+ x = CSSToDevIntPixels(aScreenX);
+
+ aError = treeOwnerAsWin->SetPosition(x, y);
+
+ CheckForDPIChange();
+}
+
+void
+nsGlobalWindow::SetScreenX(int32_t aScreenX, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::SetScreenX(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ CallerType aCallerType, ErrorResult& aError)
+{
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetScreenX,
+ aValue, "screenX", aError);
+}
+
+int32_t
+nsGlobalWindow::GetScreenYOuter(CallerType aCallerType, ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ return GetScreenXY(aCallerType, aError).y;
+}
+
+int32_t
+nsGlobalWindow::GetScreenY(CallerType aCallerType, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetScreenYOuter, (aCallerType, aError), aError, 0);
+}
+
+void
+nsGlobalWindow::GetScreenY(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue,
+ CallerType aCallerType, ErrorResult& aError)
+{
+ GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetScreenY, aValue,
+ aCallerType, aError);
+}
+
+void
+nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ int32_t x, y;
+ aError = treeOwnerAsWin->GetPosition(&x, &y);
+ if (aError.Failed()) {
+ return;
+ }
+
+ CheckSecurityLeftAndTop(nullptr, &aScreenY, aCallerIsChrome);
+ y = CSSToDevIntPixels(aScreenY);
+
+ aError = treeOwnerAsWin->SetPosition(x, y);
+
+ CheckForDPIChange();
+}
+
+void
+nsGlobalWindow::SetScreenY(int32_t aScreenY, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::SetScreenY(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetScreenY,
+ aValue, "screenY", aError);
+}
+
+// NOTE: Arguments to this function should have values scaled to
+// CSS pixels, not device pixels.
+void
+nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight, bool aCallerIsChrome)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+#ifdef MOZ_XUL
+ if (!aCallerIsChrome) {
+ // if attempting to resize the window, hide any open popups
+ nsContentUtils::HidePopupsInDocument(mDoc);
+ }
+#endif
+
+ // 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 (!nsContentUtils::IsCallerChrome()) {
+ //sec check failed
+ if (aWidth && *aWidth < 100) {
+ *aWidth = 100;
+ }
+ if (aHeight && *aHeight < 100) {
+ *aHeight = 100;
+ }
+ }
+ }
+}
+
+// NOTE: Arguments to this function should have values in device pixels
+nsresult
+nsGlobalWindow::SetDocShellWidthAndHeight(int32_t aInnerWidth, int32_t aInnerHeight)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ NS_ENSURE_TRUE(treeOwner, NS_ERROR_FAILURE);
+
+ NS_ENSURE_SUCCESS(treeOwner->SizeShellTo(mDocShell, aInnerWidth, aInnerHeight),
+ NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+// NOTE: Arguments to this function should have values in app units
+void
+nsGlobalWindow::SetCSSViewportWidthAndHeight(nscoord aInnerWidth, nscoord aInnerHeight)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+
+ nsRect shellArea = presContext->GetVisibleArea();
+ shellArea.height = aInnerHeight;
+ shellArea.width = aInnerWidth;
+
+ presContext->SetVisibleArea(shellArea);
+}
+
+// NOTE: Arguments to this function should have values scaled to
+// CSS pixels, not device pixels.
+void
+nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop, bool aCallerIsChrome)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ // This one is harder. We have to get the screen size and window dimensions.
+
+ // Check security state for use in determing window dimensions
+
+ if (!aCallerIsChrome) {
+#ifdef MOZ_XUL
+ // if attempting to move the window, hide any open popups
+ nsContentUtils::HidePopupsInDocument(mDoc);
+#endif
+
+ if (nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot())) {
+ rootWindow->FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwner = GetTreeOwnerWindow();
+
+ nsCOMPtr<nsIDOMScreen> screen = GetScreen();
+
+ if (treeOwner && screen) {
+ int32_t screenLeft, screenTop, screenWidth, screenHeight;
+ int32_t winLeft, winTop, winWidth, winHeight;
+
+ // Get the window size
+ treeOwner->GetPositionAndSize(&winLeft, &winTop, &winWidth, &winHeight);
+
+ // convert those values to CSS pixels
+ // XXX four separate retrievals of the prescontext
+ winLeft = DevToCSSIntPixels(winLeft);
+ winTop = DevToCSSIntPixels(winTop);
+ winWidth = DevToCSSIntPixels(winWidth);
+ winHeight = DevToCSSIntPixels(winHeight);
+
+ // Get the screen dimensions
+ // XXX This should use nsIScreenManager once it's fully fleshed out.
+ screen->GetAvailLeft(&screenLeft);
+ screen->GetAvailWidth(&screenWidth);
+ screen->GetAvailHeight(&screenHeight);
+#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.
+ */
+ screen->GetTop(&screenTop);
+#else
+ screen->GetAvailTop(&screenTop);
+#endif
+
+ if (aLeft) {
+ if (screenLeft+screenWidth < *aLeft+winWidth)
+ *aLeft = screenLeft+screenWidth - winWidth;
+ if (screenLeft > *aLeft)
+ *aLeft = screenLeft;
+ }
+ if (aTop) {
+ if (screenTop+screenHeight < *aTop+winHeight)
+ *aTop = screenTop+screenHeight - winHeight;
+ if (screenTop > *aTop)
+ *aTop = screenTop;
+ }
+ } else {
+ if (aLeft)
+ *aLeft = 0;
+ if (aTop)
+ *aTop = 0;
+ }
+ }
+}
+
+int32_t
+nsGlobalWindow::GetScrollBoundaryOuter(Side aSide)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ FlushPendingNotifications(Flush_Layout);
+ if (nsIScrollableFrame *sf = GetScrollFrame()) {
+ return nsPresContext::
+ AppUnitsToIntCSSPixels(sf->GetScrollRange().Edge(aSide));
+ }
+ return 0;
+}
+
+int32_t
+nsGlobalWindow::GetScrollMinX(ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideLeft), aError, 0);
+}
+
+int32_t
+nsGlobalWindow::GetScrollMinY(ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideTop), aError, 0);
+}
+
+int32_t
+nsGlobalWindow::GetScrollMaxX(ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideRight), aError, 0);
+}
+
+int32_t
+nsGlobalWindow::GetScrollMaxY(ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideBottom), aError, 0);
+}
+
+CSSIntPoint
+nsGlobalWindow::GetScrollXY(bool aDoFlush)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (aDoFlush) {
+ FlushPendingNotifications(Flush_Layout);
+ } else {
+ EnsureSizeUpToDate();
+ }
+
+ 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 sf->GetScrollPositionCSSPixels();
+}
+
+int32_t
+nsGlobalWindow::GetScrollXOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ return GetScrollXY(false).x;
+}
+
+int32_t
+nsGlobalWindow::GetScrollX(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetScrollXOuter, (), aError, 0);
+}
+
+int32_t
+nsGlobalWindow::GetScrollYOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ return GetScrollXY(false).y;
+}
+
+int32_t
+nsGlobalWindow::GetScrollY(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetScrollYOuter, (), aError, 0);
+}
+
+uint32_t
+nsGlobalWindow::Length()
+{
+ FORWARD_TO_OUTER(Length, (), 0);
+
+ nsDOMWindowList* windows = GetWindowList();
+
+ return windows ? windows->GetLength() : 0;
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetTopOuter()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsPIDOMWindowOuter> top = GetScriptableTop();
+ return top.forget();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetTop(mozilla::ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetTopOuter, (), aError, nullptr);
+}
+
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetChildWindow(const nsAString& aName)
+{
+ nsCOMPtr<nsIDocShell> docShell(GetDocShell());
+ NS_ENSURE_TRUE(docShell, nullptr);
+
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ docShell->FindChildWithName(aName, false, true, nullptr, nullptr,
+ getter_AddRefs(child));
+
+ return child ? child->GetWindow() : nullptr;
+}
+
+bool
+nsGlobalWindow::DispatchCustomEvent(const nsAString& aEventName)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ bool defaultActionEnabled = true;
+ nsContentUtils::DispatchTrustedEvent(mDoc, ToSupports(this), aEventName,
+ true, true, &defaultActionEnabled);
+
+ return defaultActionEnabled;
+}
+
+bool
+nsGlobalWindow::DispatchResizeEvent(const CSSIntSize& aSize)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ ErrorResult res;
+ RefPtr<Event> domEvent =
+ mDoc->CreateEvent(NS_LITERAL_STRING("CustomEvent"), 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();
+ JSAutoCompartment ac(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,
+ NS_LITERAL_STRING("DOMWindowResize"),
+ /* aCanBubble = */ true,
+ /* aCancelable = */ true,
+ detailValue,
+ res);
+ if (res.Failed()) {
+ return false;
+ }
+
+ domEvent->SetTrusted(true);
+ domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(GetOuterWindow());
+ domEvent->SetTarget(target);
+
+ bool defaultActionEnabled = true;
+ target->DispatchEvent(domEvent, &defaultActionEnabled);
+
+ return defaultActionEnabled;
+}
+
+void
+nsGlobalWindow::RefreshCompartmentPrincipal()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ JS_SetCompartmentPrincipals(js::GetObjectCompartment(GetWrapperPreserveColor()),
+ nsJSPrincipals::get(mDoc->NodePrincipal()));
+}
+
+static already_AddRefed<nsIDocShellTreeItem>
+GetCallerDocShellTreeItem()
+{
+ nsCOMPtr<nsIWebNavigation> callerWebNav = do_GetInterface(GetEntryGlobal());
+ nsCOMPtr<nsIDocShellTreeItem> callerItem = do_QueryInterface(callerWebNav);
+
+ return callerItem.forget();
+}
+
+bool
+nsGlobalWindow::WindowExists(const nsAString& aName,
+ bool aForceNoOpener,
+ bool aLookForCallerOnJSStack)
+{
+ NS_PRECONDITION(IsOuterWindow(), "Must be outer window");
+ NS_PRECONDITION(mDocShell, "Must have docshell");
+
+ if (aForceNoOpener) {
+ return aName.LowerCaseEqualsLiteral("_self") ||
+ aName.LowerCaseEqualsLiteral("_top") ||
+ aName.LowerCaseEqualsLiteral("_parent");
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> caller;
+ if (aLookForCallerOnJSStack) {
+ caller = GetCallerDocShellTreeItem();
+ }
+
+ if (!caller) {
+ caller = mDocShell;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> namedItem;
+ mDocShell->FindItemWithName(aName, nullptr, caller,
+ getter_AddRefs(namedItem));
+ return namedItem != nullptr;
+}
+
+already_AddRefed<nsIWidget>
+nsGlobalWindow::GetMainWidget()
+{
+ FORWARD_TO_OUTER(GetMainWidget, (), nullptr);
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+
+ nsCOMPtr<nsIWidget> widget;
+
+ if (treeOwnerAsWin) {
+ treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
+ }
+
+ return widget.forget();
+}
+
+nsIWidget*
+nsGlobalWindow::GetNearestWidget() const
+{
+ nsIDocShell* docShell = GetDocShell();
+ NS_ENSURE_TRUE(docShell, nullptr);
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_TRUE(presShell, nullptr);
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ NS_ENSURE_TRUE(rootFrame, nullptr);
+ return rootFrame->GetView()->GetNearestWidget(nullptr);
+}
+
+void
+nsGlobalWindow::SetFullScreenOuter(bool aFullScreen, mozilla::ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ aError = SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullScreen);
+}
+
+void
+nsGlobalWindow::SetFullScreen(bool aFullScreen, mozilla::ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetFullScreenOuter, (aFullScreen, aError), aError, /* void */);
+}
+
+nsresult
+nsGlobalWindow::SetFullScreen(bool aFullScreen)
+{
+ FORWARD_TO_OUTER(SetFullScreen, (aFullScreen), NS_ERROR_NOT_INITIALIZED);
+
+ return SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullScreen);
+}
+
+static void
+FinishDOMFullscreenChange(nsIDocument* aDoc, bool aInDOMFullscreen)
+{
+ if (aInDOMFullscreen) {
+ // Ask the document to handle any pending DOM fullscreen change.
+ if (!nsIDocument::HandlePendingFullscreenRequests(aDoc)) {
+ // If we don't end up having anything in fullscreen,
+ // async request exiting fullscreen.
+ nsIDocument::AsyncExitFullscreen(aDoc);
+ }
+ } else {
+ // If the window is leaving fullscreen state, also ask the document
+ // to exit from DOM Fullscreen.
+ nsIDocument::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";
+ nsAdoptingCString prefValue = Preferences::GetCString(pref);
+ if (!prefValue.IsEmpty()) {
+ sscanf(prefValue.get(), "%hu%hu",
+ &aDuration->mFadeIn, &aDuration->mFadeOut);
+ }
+}
+
+class FullscreenTransitionTask : public Runnable
+{
+public:
+ FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration,
+ nsGlobalWindow* aWindow, bool aFullscreen,
+ nsIWidget* aWidget, nsIScreen* aScreen,
+ nsISupports* aTransitionData)
+ : mWindow(aWindow)
+ , mWidget(aWidget)
+ , mScreen(aScreen)
+ , mTransitionData(aTransitionData)
+ , mDuration(aDuration)
+ , mStage(eBeforeToggle)
+ , mFullscreen(aFullscreen)
+ {
+ MOZ_COUNT_CTOR(FullscreenTransitionTask);
+ }
+
+ NS_IMETHOD Run() override;
+
+private:
+ virtual ~FullscreenTransitionTask()
+ {
+ MOZ_COUNT_DTOR(FullscreenTransitionTask);
+ }
+
+ /**
+ * 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:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ explicit Observer(FullscreenTransitionTask* aTask)
+ : mTask(aTask) { }
+
+ private:
+ ~Observer() {}
+
+ RefPtr<FullscreenTransitionTask> mTask;
+ };
+
+ static const char* const kPaintedTopic;
+
+ RefPtr<nsGlobalWindow> mWindow;
+ nsCOMPtr<nsIWidget> mWidget;
+ nsCOMPtr<nsIScreen> mScreen;
+ 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");
+ return NS_OK;
+ }
+ if (stage == eBeforeToggle) {
+ PROFILER_MARKER("Fullscreen transition start");
+ mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle,
+ mDuration.mFadeIn, mTransitionData,
+ this);
+ } else if (stage == eToggleFullscreen) {
+ PROFILER_MARKER("Fullscreen toggle start");
+ mFullscreenChangeStartTime = TimeStamp::Now();
+ if (MOZ_UNLIKELY(mWindow->mFullScreen != mFullscreen)) {
+ // This could happen in theory if several fullscreen requests in
+ // different direction happen continuously in a short time. We
+ // need to ensure the fullscreen state matches our target here,
+ // otherwise the widget would change the window state as if we
+ // toggle for Fullscreen Mode instead of Fullscreen API.
+ NS_WARNING("The fullscreen state of the window does not match");
+ mWindow->mFullScreen = mFullscreen;
+ }
+ // Toggle the fullscreen state on the widget
+ if (!mWindow->SetWidgetFullscreen(FullscreenReason::ForFullscreenAPI,
+ mFullscreen, mWidget, mScreen)) {
+ // 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.
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ uint32_t timeout =
+ Preferences::GetUint("full-screen-api.transition.timeout", 1000);
+ mTimer->Init(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("Fullscreen transition end");
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver)
+
+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 ?
+ nsGlobalWindow::Cast(win)->GetMainWidget() : nullptr;
+ if (widget == mTask->mWidget) {
+ // The paint notification arrives first. Cancel the timer.
+ mTask->mTimer->Cancel();
+ shouldContinue = true;
+ PROFILER_MARKER("Fullscreen toggle end");
+ }
+ } 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("Fullscreen toggle timeout");
+ }
+ if (shouldContinue) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->RemoveObserver(this, kPaintedTopic);
+ mTask->mTimer = nullptr;
+ mTask->Run();
+ }
+ return NS_OK;
+}
+
+static bool
+MakeWidgetFullscreen(nsGlobalWindow* 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));
+ }
+ }
+ // We pass nullptr as the screen to SetWidgetFullscreen
+ // and FullscreenTransitionTask, as we do not wish to override
+ // the default screen selection behavior. The screen containing
+ // most of the widget will be selected.
+ if (!performTransition) {
+ return aWindow->SetWidgetFullscreen(aReason, aFullscreen, widget, nullptr);
+ } else {
+ nsCOMPtr<nsIRunnable> task =
+ new FullscreenTransitionTask(duration, aWindow, aFullscreen,
+ widget, nullptr, transitionData);
+ task->Run();
+ return true;
+ }
+}
+
+nsresult
+nsGlobalWindow::SetFullscreenInternal(FullscreenReason aReason,
+ bool aFullScreen)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ 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->GetRootTreeItem(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;
+
+ // If we are already in full screen mode, just return.
+ if (mFullScreen == aFullScreen)
+ 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 && !mFullscreenMode) {
+ // If we are exiting fullscreen mode, but we actually didn't
+ // entered 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 {
+ mFullscreenMode = aFullScreen;
+ }
+ } else {
+ // If we are exiting from DOM fullscreen while we initially make
+ // the window fullscreen because of fullscreen mode, don't restore
+ // the window. But we still need to exit the DOM fullscreen state.
+ if (!aFullScreen && mFullscreenMode) {
+ FinishDOMFullscreenChange(mDoc, false);
+ return NS_OK;
+ }
+ }
+
+ // Prevent chrome documents which are still loading from resizing
+ // the window after we set fullscreen mode.
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwnerAsWin));
+ if (aFullScreen && xulWin) {
+ xulWin->SetIntrinsicallySized(false);
+ }
+
+ // Set this before so if widget sends an event indicating its
+ // gone full screen, the state trap above works.
+ mFullScreen = aFullScreen;
+
+ // Sometimes we don't want the top-level widget to actually go fullscreen,
+ // for example 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.
+ if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) {
+ if (MakeWidgetFullscreen(this, aReason, aFullScreen)) {
+ // The rest of code for switching fullscreen is in nsGlobalWindow::
+ // FinishFullscreenChange() which will be called after sizemodechange
+ // event is dispatched.
+ return NS_OK;
+ }
+ }
+
+ FinishFullscreenChange(aFullScreen);
+ return NS_OK;
+}
+
+bool
+nsGlobalWindow::SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
+ nsIWidget* aWidget, nsIScreen* aScreen)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(this == GetTopInternal(), "Only topmost window should call this");
+ MOZ_ASSERT(!AsOuter()->GetFrameElementInternal(), "Content window should not call this");
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ if (!NS_WARN_IF(!IsChromeWindow())) {
+ auto chromeWin = static_cast<nsGlobalChromeWindow*>(this);
+ if (!NS_WARN_IF(chromeWin->mFullscreenPresShell)) {
+ if (nsIPresShell* shell = mDocShell->GetPresShell()) {
+ if (nsRefreshDriver* rd = shell->GetRefreshDriver()) {
+ chromeWin->mFullscreenPresShell = do_GetWeakReference(shell);
+ MOZ_ASSERT(chromeWin->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, aScreen) :
+ aWidget->MakeFullScreen(aIsFullscreen, aScreen);
+ return NS_SUCCEEDED(rv);
+}
+
+/* virtual */ void
+nsGlobalWindow::FinishFullscreenChange(bool aIsFullscreen)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (aIsFullscreen != mFullScreen) {
+ 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 = false;
+ mFullscreenMode = false;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Failed to exit fullscreen?");
+ mFullScreen = true;
+ // We don't know how code can reach here. Not sure
+ // what value should be set for fullscreen mode.
+ mFullscreenMode = false;
+ }
+ 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, mFullScreen);
+
+ // dispatch a "fullscreen" DOM event so that XUL apps can
+ // respond visually if we are kicked into full screen mode
+ DispatchCustomEvent(NS_LITERAL_STRING("fullscreen"));
+
+ if (!NS_WARN_IF(!IsChromeWindow())) {
+ auto chromeWin = static_cast<nsGlobalChromeWindow*>(this);
+ if (nsCOMPtr<nsIPresShell> shell =
+ do_QueryReferent(chromeWin->mFullscreenPresShell)) {
+ if (nsRefreshDriver* rd = shell->GetRefreshDriver()) {
+ rd->Thaw();
+ }
+ chromeWin->mFullscreenPresShell = nullptr;
+ }
+ }
+
+ if (!mWakeLock && mFullScreen) {
+ RefPtr<power::PowerManagerService> pmService =
+ power::PowerManagerService::GetInstance();
+ if (!pmService) {
+ return;
+ }
+
+ // XXXkhuey using the inner here, do we need to do something if it changes?
+ ErrorResult rv;
+ mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("DOM_Fullscreen"),
+ AsOuter()->GetCurrentInnerWindow(), rv);
+ NS_WARNING_ASSERTION(!rv.Failed(), "Failed to lock the wakelock");
+ rv.SuppressException();
+ } else if (mWakeLock && !mFullScreen) {
+ ErrorResult rv;
+ mWakeLock->Unlock(rv);
+ mWakeLock = nullptr;
+ rv.SuppressException();
+ }
+}
+
+bool
+nsGlobalWindow::FullScreen() const
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ NS_ENSURE_TRUE(mDocShell, mFullScreen);
+
+ // Get the fullscreen value of the root window, to always have the value
+ // accurate, even when called from content.
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
+ if (rootItem == mDocShell) {
+ if (!XRE_IsContentProcess()) {
+ // We are the root window. Return our internal value.
+ return mFullScreen;
+ }
+ 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);
+
+ return nsGlobalWindow::Cast(window)->FullScreen();
+}
+
+bool
+nsGlobalWindow::GetFullScreenOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ return FullScreen();
+}
+
+bool
+nsGlobalWindow::GetFullScreen(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetFullScreenOuter, (), aError, false);
+}
+
+bool
+nsGlobalWindow::GetFullScreen()
+{
+ FORWARD_TO_INNER(GetFullScreen, (), false);
+
+ ErrorResult dummy;
+ bool fullscreen = GetFullScreen(dummy);
+ dummy.SuppressException();
+ return fullscreen;
+}
+
+void
+nsGlobalWindow::Dump(const nsAString& aStr)
+{
+ if (!nsContentUtils::DOMWindowDumpEnabled()) {
+ 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
+nsGlobalWindow::EnsureReflowFlushAndPaint()
+{
+ MOZ_ASSERT(IsOuterWindow());
+ NS_ASSERTION(mDocShell, "EnsureReflowFlushAndPaint() called with no "
+ "docshell!");
+
+ if (!mDocShell)
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+
+ if (!presShell)
+ return;
+
+ // Flush pending reflows.
+ if (mDoc) {
+ mDoc->FlushPendingNotifications(Flush_Layout);
+ }
+
+ // Unsuppress painting.
+ presShell->UnsuppressPainting();
+}
+
+// static
+void
+nsGlobalWindow::MakeScriptDialogTitle(nsAString& aOutTitle,
+ nsIPrincipal* aSubjectPrincipal)
+{
+ MOZ_ASSERT(aSubjectPrincipal);
+
+ aOutTitle.Truncate();
+
+ // Try to get a host from the running principal -- this will do the
+ // right thing for javascript: and data: documents.
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aSubjectPrincipal->GetURI(getter_AddRefs(uri));
+ // Note - The check for the current JSContext here isn't necessarily sensical.
+ // It's just designed to preserve existing behavior during a mass-conversion
+ // patch.
+ if (NS_SUCCEEDED(rv) && uri && nsContentUtils::GetCurrentJSContext()) {
+ // remove user:pass for privacy and spoof prevention
+
+ nsCOMPtr<nsIURIFixup> fixup(do_GetService(NS_URIFIXUP_CONTRACTID));
+ if (fixup) {
+ nsCOMPtr<nsIURI> fixedURI;
+ rv = fixup->CreateExposableURI(uri, getter_AddRefs(fixedURI));
+ if (NS_SUCCEEDED(rv) && fixedURI) {
+ nsAutoCString host;
+ fixedURI->GetHost(host);
+
+ if (!host.IsEmpty()) {
+ // if this URI has a host we'll show it. For other
+ // schemes (e.g. file:) we fall back to the localized
+ // generic string
+
+ nsAutoCString prepath;
+ fixedURI->GetPrePath(prepath);
+
+ NS_ConvertUTF8toUTF16 ucsPrePath(prepath);
+ const char16_t *formatStrings[] = { ucsPrePath.get() };
+ nsXPIDLString tempString;
+ nsContentUtils::FormatLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ "ScriptDlgHeading",
+ formatStrings,
+ tempString);
+ aOutTitle = tempString;
+ }
+ }
+ }
+ }
+
+ if (aOutTitle.IsEmpty()) {
+ // We didn't find a host so use the generic heading
+ nsXPIDLString tempString;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ "ScriptDlgGenericHeading",
+ tempString);
+ aOutTitle = tempString;
+ }
+
+ // Just in case
+ if (aOutTitle.IsEmpty()) {
+ NS_WARNING("could not get ScriptDlgGenericHeading string from string bundle");
+ aOutTitle.AssignLiteral("[Script]");
+ }
+}
+
+bool
+nsGlobalWindow::CanMoveResizeWindows(bool aCallerIsChrome)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ // When called from chrome, we can avoid the following checks.
+ if (!aCallerIsChrome) {
+ // Don't allow scripts to move or resize windows that were not opened by a
+ // script.
+ if (!mHadOriginalOpener) {
+ return false;
+ }
+
+ if (!CanSetProperty("dom.disable_window_move_resize")) {
+ return false;
+ }
+
+ // Ignore the request if we have more than one tab in the window.
+ uint32_t itemCount = 0;
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsITabChild> child = docShell->GetTabChild();
+ if (child) {
+ child->SendGetTabCount(&itemCount);
+ }
+ }
+ } else {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+ if (treeOwner) {
+ treeOwner->GetTargetableShellCount(&itemCount);
+ }
+ }
+ if (itemCount > 1) {
+ return false;
+ }
+ }
+
+ if (mDocShell) {
+ bool allow;
+ nsresult rv = mDocShell->GetAllowWindowControl(&allow);
+ if (NS_SUCCEEDED(rv) && !allow)
+ return false;
+ }
+
+ if (gMouseDown && !gDragServiceDisabled) {
+ nsCOMPtr<nsIDragService> ds =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (ds) {
+ gDragServiceDisabled = true;
+ ds->Suppress();
+ }
+ }
+ return true;
+}
+
+bool
+nsGlobalWindow::AlertOrConfirm(bool aAlert,
+ const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ // XXX This method is very similar to nsGlobalWindow::Prompt, make
+ // sure any modifications here don't need to happen over there!
+ MOZ_ASSERT(IsOuterWindow());
+
+ 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.
+ nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+
+ // Before bringing up the window, unsuppress painting and flush
+ // pending reflows.
+ EnsureReflowFlushAndPaint();
+
+ nsAutoString title;
+ MakeScriptDialogTitle(title, &aSubjectPrincipal);
+
+ // Remove non-terminating null characters from the
+ // string. See bug #310037.
+ nsAutoString final;
+ nsContentUtils::StripNullChars(aMessage, 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(AsOuter(), NS_GET_IID(nsIPrompt),
+ getter_AddRefs(prompt));
+ if (aError.Failed()) {
+ return false;
+ }
+
+ // Always allow tab modal prompts for alert and confirm.
+ if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
+ promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
+ }
+
+ bool result = false;
+ nsAutoSyncOperation sync(mDoc);
+ if (ShouldPromptToBlockDialogs()) {
+ bool disallowDialog = false;
+ nsXPIDLString label;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ "ScriptDialogLabel", label);
+
+ 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
+nsGlobalWindow::Alert(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ Alert(EmptyString(), aSubjectPrincipal, aError);
+}
+
+void
+nsGlobalWindow::AlertOuter(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ AlertOrConfirm(/* aAlert = */ true, aMessage, aSubjectPrincipal, aError);
+}
+
+void
+nsGlobalWindow::Alert(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(AlertOuter, (aMessage, aSubjectPrincipal, aError),
+ aError, );
+}
+
+bool
+nsGlobalWindow::ConfirmOuter(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ return AlertOrConfirm(/* aAlert = */ false, aMessage, aSubjectPrincipal,
+ aError);
+}
+
+bool
+nsGlobalWindow::Confirm(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aSubjectPrincipal, aError),
+ aError, false);
+}
+
+already_AddRefed<Promise>
+nsGlobalWindow::Fetch(const RequestOrUSVString& aInput,
+ const RequestInit& aInit, ErrorResult& aRv)
+{
+ return FetchRequest(this, aInput, aInit, aRv);
+}
+
+void
+nsGlobalWindow::PromptOuter(const nsAString& aMessage,
+ const nsAString& aInitial,
+ nsAString& aReturn,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ // XXX This method is very similar to nsGlobalWindow::AlertOrConfirm, make
+ // sure any modifications here don't need to happen over there!
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ 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.
+ nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+
+ // Before bringing up the window, unsuppress painting and flush
+ // pending reflows.
+ EnsureReflowFlushAndPaint();
+
+ nsAutoString title;
+ MakeScriptDialogTitle(title, &aSubjectPrincipal);
+
+ // Remove non-terminating null characters from the
+ // string. See bug #310037.
+ nsAutoString fixedMessage, fixedInitial;
+ nsContentUtils::StripNullChars(aMessage, 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(AsOuter(), NS_GET_IID(nsIPrompt),
+ getter_AddRefs(prompt));
+ if (aError.Failed()) {
+ return;
+ }
+
+ // Always allow tab modal prompts for prompt.
+ if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
+ promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
+ }
+
+ // Pass in the default value, if any.
+ char16_t *inoutValue = ToNewUnicode(fixedInitial);
+ bool disallowDialog = false;
+
+ nsXPIDLString label;
+ if (ShouldPromptToBlockDialogs()) {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ "ScriptDialogLabel", label);
+ }
+
+ nsAutoSyncOperation sync(mDoc);
+ bool ok;
+ aError = prompt->Prompt(title.get(), fixedMessage.get(),
+ &inoutValue, label.get(), &disallowDialog, &ok);
+
+ if (disallowDialog) {
+ DisableDialogs();
+ }
+
+ if (aError.Failed()) {
+ return;
+ }
+
+ nsAdoptingString outValue(inoutValue);
+
+ if (ok && outValue) {
+ aReturn.Assign(outValue);
+ }
+}
+
+void
+nsGlobalWindow::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
+nsGlobalWindow::FocusOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell);
+
+ bool isVisible = false;
+ if (baseWin) {
+ baseWin->GetVisibility(&isVisible);
+ }
+
+ if (!isVisible) {
+ // A hidden tab is being focused, ignore this call.
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
+ nsPIDOMWindowOuter* callerOuter = caller ? caller->GetOuterWindow() : nullptr;
+ nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpener();
+
+ // Enforce dom.disable_window_flip (for non-chrome), but still allow the
+ // window which opened us to raise us at times when popups are allowed
+ // (bugs 355482 and 369306).
+ bool canFocus = CanSetProperty("dom.disable_window_flip") ||
+ (opener == callerOuter &&
+ RevisePopupAbuseLevel(gPopupControlState) < openAbused);
+
+ nsCOMPtr<mozIDOMWindowProxy> activeDOMWindow;
+ fm->GetActiveWindow(getter_AddRefs(activeDOMWindow));
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem ? rootItem->GetWindow() : nullptr;
+ auto* activeWindow = nsPIDOMWindowOuter::From(activeDOMWindow);
+ bool isActive = (rootWin == activeWindow);
+
+ 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;
+ }
+
+ // XXXndeakin not sure what this is for or if it should go somewhere else
+ nsCOMPtr<nsIEmbeddingSiteWindow> embeddingWin(do_GetInterface(treeOwnerAsWin));
+ if (embeddingWin)
+ embeddingWin->SetFocus();
+ }
+
+ if (!mDocShell) {
+ return;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell;
+ // Don't look for a presshell if we're a root chrome window that's got
+ // about:blank loaded. We don't want to focus our widget in that case.
+ // XXXbz should we really be checking for IsInitialDocument() instead?
+ bool lookForPresShell = true;
+ if (mDocShell->ItemType() == nsIDocShellTreeItem::typeChrome &&
+ GetPrivateRoot() == AsOuter() && mDoc) {
+ nsIURI* ourURI = mDoc->GetDocumentURI();
+ if (ourURI) {
+ lookForPresShell = !NS_IsAboutBlank(ourURI);
+ }
+ }
+
+ if (lookForPresShell) {
+ mDocShell->GetEldestPresShell(getter_AddRefs(presShell));
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parentDsti;
+ mDocShell->GetParent(getter_AddRefs(parentDsti));
+
+ // set the parent's current focus to the frame containing this window.
+ nsCOMPtr<nsPIDOMWindowOuter> parent =
+ parentDsti ? parentDsti->GetWindow() : nullptr;
+ if (parent) {
+ nsCOMPtr<nsIDocument> parentdoc = parent->GetDoc();
+ if (!parentdoc) {
+ return;
+ }
+
+ nsIContent* frame = parentdoc->FindContentForSubDocument(mDoc);
+ nsCOMPtr<nsIDOMElement> frameElement = do_QueryInterface(frame);
+ if (frameElement) {
+ uint32_t flags = nsIFocusManager::FLAG_NOSCROLL;
+ if (canFocus)
+ flags |= nsIFocusManager::FLAG_RAISE;
+ aError = fm->SetFocus(frameElement, flags);
+ }
+ 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.
+ aError = fm->SetActiveWindow(AsOuter());
+ }
+}
+
+void
+nsGlobalWindow::Focus(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(FocusOuter, (aError), aError, );
+}
+
+nsresult
+nsGlobalWindow::Focus()
+{
+ FORWARD_TO_INNER(Focus, (), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ Focus(rv);
+
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::BlurOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ // If dom.disable_window_flip == true, then content should not be allowed
+ // to call this function (this would allow popunders, bug 369306)
+ if (!CanSetProperty("dom.disable_window_flip")) {
+ return;
+ }
+
+ // If embedding apps don't implement nsIEmbeddingSiteWindow, we
+ // shouldn't throw exceptions to web content.
+
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+ nsCOMPtr<nsIEmbeddingSiteWindow> siteWindow(do_GetInterface(treeOwner));
+ if (siteWindow) {
+ // This method call may cause mDocShell to become nullptr.
+ siteWindow->Blur();
+
+ // if the root is focused, clear the focus
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && mDoc) {
+ nsCOMPtr<nsIDOMElement> element;
+ fm->GetFocusedElementForWindow(AsOuter(), false, nullptr, getter_AddRefs(element));
+ nsCOMPtr<nsIContent> content = do_QueryInterface(element);
+ if (content == mDoc->GetRootElement()) {
+ fm->ClearFocus(AsOuter());
+ }
+ }
+ }
+}
+
+void
+nsGlobalWindow::Blur(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(BlurOuter, (), aError, );
+}
+
+void
+nsGlobalWindow::BackOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ if (!webNav) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aError = webNav->GoBack();
+}
+
+void
+nsGlobalWindow::Back(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(BackOuter, (aError), aError, );
+}
+
+void
+nsGlobalWindow::ForwardOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ if (!webNav) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aError = webNav->GoForward();
+}
+
+void
+nsGlobalWindow::Forward(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(ForwardOuter, (aError), aError, );
+}
+
+void
+nsGlobalWindow::HomeOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return;
+ }
+
+ nsAdoptingString homeURL =
+ Preferences::GetLocalizedString(PREF_BROWSER_STARTUP_HOMEPAGE);
+
+ if (homeURL.IsEmpty()) {
+ // if all else fails, use this
+#ifdef DEBUG_seth
+ printf("all else failed. using %s as the home page\n", DEFAULT_HOME_PAGE);
+#endif
+ CopyASCIItoUTF16(DEFAULT_HOME_PAGE, homeURL);
+ }
+
+#ifdef MOZ_PHOENIX
+ {
+ // Firefox lets the user specify multiple home pages to open in
+ // individual tabs by separating them with '|'. Since we don't
+ // have the machinery in place to easily open new tabs from here,
+ // simply truncate the homeURL at the first '|' character to
+ // prevent any possibilities of leaking the users list of home
+ // pages to the first home page.
+ //
+ // Once bug https://bugzilla.mozilla.org/show_bug.cgi?id=221445 is
+ // fixed we can revisit this.
+ int32_t firstPipe = homeURL.FindChar('|');
+
+ if (firstPipe > 0) {
+ homeURL.Truncate(firstPipe);
+ }
+ }
+#endif
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ if (!webNav) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aError = webNav->LoadURI(homeURL.get(),
+ nsIWebNavigation::LOAD_FLAGS_NONE,
+ nullptr,
+ nullptr,
+ nullptr);
+}
+
+void
+nsGlobalWindow::Home(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(HomeOuter, (aError), aError, );
+}
+
+void
+nsGlobalWindow::StopOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ if (webNav) {
+ aError = webNav->Stop(nsIWebNavigation::STOP_ALL);
+ }
+}
+
+void
+nsGlobalWindow::Stop(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(StopOuter, (aError), aError, );
+}
+
+void
+nsGlobalWindow::PrintOuter(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+#ifdef NS_PRINTING
+ if (Preferences::GetBool("dom.disable_window_print", false)) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ if (!AreDialogsEnabled()) {
+ // We probably want to keep throwing here; silently doing nothing is a bit
+ // weird given the typical use cases of print().
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ if (ShouldPromptToBlockDialogs() && !ConfirmDialogIfNeeded()) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint;
+ if (NS_SUCCEEDED(GetInterface(NS_GET_IID(nsIWebBrowserPrint),
+ getter_AddRefs(webBrowserPrint)))) {
+ nsAutoSyncOperation sync(GetCurrentInnerWindowInternal() ?
+ GetCurrentInnerWindowInternal()->mDoc.get() :
+ nullptr);
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+
+ nsCOMPtr<nsIPrintSettings> printSettings;
+ if (printSettingsService) {
+ bool printSettingsAreGlobal =
+ Preferences::GetBool("print.use_global_printsettings", false);
+
+ if (printSettingsAreGlobal) {
+ printSettingsService->GetGlobalPrintSettings(getter_AddRefs(printSettings));
+
+ nsXPIDLString printerName;
+ printSettings->GetPrinterName(getter_Copies(printerName));
+ if (printerName.IsEmpty()) {
+ printSettingsService->GetDefaultPrinterName(getter_Copies(printerName));
+ printSettings->SetPrinterName(printerName);
+ }
+ printSettingsService->InitPrintSettingsFromPrinter(printerName, printSettings);
+ printSettingsService->InitPrintSettingsFromPrefs(printSettings,
+ true,
+ nsIPrintSettings::kInitSaveAll);
+ } else {
+ printSettingsService->GetNewPrintSettings(getter_AddRefs(printSettings));
+ }
+
+ EnterModalState();
+ webBrowserPrint->Print(printSettings, nullptr);
+ LeaveModalState();
+
+ bool savePrintSettings =
+ Preferences::GetBool("print.save_print_settings", false);
+ if (printSettingsAreGlobal && savePrintSettings) {
+ printSettingsService->
+ SavePrintSettingsToPrefs(printSettings,
+ true,
+ nsIPrintSettings::kInitSaveAll);
+ printSettingsService->
+ SavePrintSettingsToPrefs(printSettings,
+ false,
+ nsIPrintSettings::kInitSavePrinterName);
+ }
+ } else {
+ webBrowserPrint->GetGlobalPrintSettings(getter_AddRefs(printSettings));
+ webBrowserPrint->Print(printSettings, nullptr);
+ }
+ }
+#endif //NS_PRINTING
+}
+
+void
+nsGlobalWindow::Print(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(PrintOuter, (aError), aError, );
+}
+
+void
+nsGlobalWindow::MoveToOuter(int32_t aXPos, int32_t aYPos, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.moveTo() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ nsCOMPtr<nsIScreen> screen;
+ if (screenMgr) {
+ CSSIntSize size;
+ GetInnerSize(size);
+ screenMgr->ScreenForRect(aXPos, aYPos, size.width, size.height,
+ getter_AddRefs(screen));
+ }
+
+ if (screen) {
+ // On secondary displays, the "CSS px" coordinates are offset so that they
+ // share their origin with global desktop pixels, to avoid ambiguities in
+ // the coordinate space when there are displays with different DPIs.
+ // (See the corresponding code in GetScreenXY() above.)
+ int32_t screenLeftDeskPx, screenTopDeskPx, w, h;
+ screen->GetRectDisplayPix(&screenLeftDeskPx, &screenTopDeskPx, &w, &h);
+ CSSIntPoint cssPos(aXPos - screenLeftDeskPx, aYPos - screenTopDeskPx);
+ CheckSecurityLeftAndTop(&cssPos.x, &cssPos.y, aCallerIsChrome);
+
+ double scale;
+ screen->GetDefaultCSSScaleFactor(&scale);
+ LayoutDevicePoint devPos = cssPos * CSSToLayoutDeviceScale(scale);
+
+ screen->GetContentsScaleFactor(&scale);
+ DesktopPoint deskPos = devPos / DesktopToLayoutDeviceScale(scale);
+ aError = treeOwnerAsWin->SetPositionDesktopPix(screenLeftDeskPx + deskPos.x,
+ screenTopDeskPx + deskPos.y);
+ } else {
+ // We couldn't find a screen? Just assume a 1:1 mapping.
+ CSSIntPoint cssPos(aXPos, aXPos);
+ CheckSecurityLeftAndTop(&cssPos.x, &cssPos.y, aCallerIsChrome);
+ LayoutDevicePoint devPos = cssPos * CSSToLayoutDeviceScale(1.0);
+ aError = treeOwnerAsWin->SetPosition(devPos.x, devPos.y);
+ }
+
+ CheckForDPIChange();
+}
+
+void
+nsGlobalWindow::MoveTo(int32_t aXPos, int32_t aYPos, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(MoveToOuter, (aXPos, aYPos, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::MoveByOuter(int32_t aXDif, int32_t aYDif, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.moveBy() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) {
+ 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;
+ }
+
+ // mild abuse of a "size" object so we don't need more helper functions
+ nsIntSize cssPos(DevToCSSIntPixels(nsIntSize(x, y)));
+
+ cssPos.width += aXDif;
+ cssPos.height += aYDif;
+
+ CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height, aCallerIsChrome);
+
+ nsIntSize newDevPos(CSSToDevIntPixels(cssPos));
+
+ aError = treeOwnerAsWin->SetPosition(newDevPos.width, newDevPos.height);
+
+ CheckForDPIChange();
+}
+
+void
+nsGlobalWindow::MoveBy(int32_t aXDif, int32_t aYDif, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(MoveByOuter, (aXDif, aYDif, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+nsresult
+nsGlobalWindow::MoveBy(int32_t aXDif, int32_t aYDif)
+{
+ FORWARD_TO_OUTER(MoveBy, (aXDif, aYDif), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ MoveByOuter(aXDif, aYDif, rv, /* aCallerIsChrome = */ true);
+
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::ResizeToOuter(int32_t aWidth, int32_t aHeight, ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ /*
+ * If caller is a browser-element then dispatch a resize event to
+ * the embedder.
+ */
+ if (mDocShell && mDocShell->GetIsMozBrowserOrApp()) {
+ CSSIntSize size(aWidth, aHeight);
+ if (!DispatchResizeEvent(size)) {
+ // The embedder chose to prevent the default action for this
+ // event, so let's not resize this window after all...
+ return;
+ }
+ }
+
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.resizeTo() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsIntSize cssSize(aWidth, aHeight);
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
+
+ nsIntSize devSz(CSSToDevIntPixels(cssSize));
+
+ aError = treeOwnerAsWin->SetSize(devSz.width, devSz.height, true);
+
+ CheckForDPIChange();
+}
+
+void
+nsGlobalWindow::ResizeTo(int32_t aWidth, int32_t aHeight, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(ResizeToOuter, (aWidth, aHeight, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif,
+ ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ /*
+ * If caller is a browser-element then dispatch a resize event to
+ * parent.
+ */
+ if (mDocShell && mDocShell->GetIsMozBrowserOrApp()) {
+ CSSIntSize size;
+ if (NS_FAILED(GetInnerSize(size))) {
+ return;
+ }
+
+ size.width += aWidthDif;
+ size.height += aHeightDif;
+
+ if (!DispatchResizeEvent(size)) {
+ // The embedder chose to prevent the default action for this
+ // event, so let's not resize this window after all...
+ return;
+ }
+ }
+
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.resizeBy() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ int32_t width, height;
+ aError = treeOwnerAsWin->GetSize(&width, &height);
+ if (aError.Failed()) {
+ return;
+ }
+
+ // 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.
+
+ nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
+
+ cssSize.width += aWidthDif;
+ cssSize.height += aHeightDif;
+
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
+
+ nsIntSize newDevSize(CSSToDevIntPixels(cssSize));
+
+ aError = treeOwnerAsWin->SetSize(newDevSize.width, newDevSize.height, true);
+
+ CheckForDPIChange();
+}
+
+void
+nsGlobalWindow::ResizeBy(int32_t aWidthDif, int32_t aHeightDif,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(ResizeByOuter, (aWidthDif, aHeightDif, aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+void
+nsGlobalWindow::SizeToContentOuter(ErrorResult& aError, bool aCallerIsChrome)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ 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(aCallerIsChrome) || IsFrame()) {
+ 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) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ int32_t width, height;
+ aError = cv->GetContentSize(&width, &height);
+ if (aError.Failed()) {
+ return;
+ }
+
+ // Make sure the new size is following the CheckSecurityWidthAndHeight
+ // rules.
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+ if (!treeOwner) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height)));
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome);
+
+ nsIntSize newDevSize(CSSToDevIntPixels(cssSize));
+
+ aError = treeOwner->SizeShellTo(mDocShell, newDevSize.width,
+ newDevSize.height);
+}
+
+void
+nsGlobalWindow::SizeToContent(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SizeToContentOuter, (aError, nsContentUtils::IsCallerChrome()), aError, );
+}
+
+already_AddRefed<nsPIWindowRoot>
+nsGlobalWindow::GetTopWindowRoot()
+{
+ nsPIDOMWindowOuter* piWin = GetPrivateRoot();
+ if (!piWin) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIWindowRoot> window = do_QueryInterface(piWin->GetChromeEventHandler());
+ return window.forget();
+}
+
+void
+nsGlobalWindow::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
+nsGlobalWindow::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
+nsGlobalWindow::ScrollTo(const ScrollToOptions& aOptions)
+{
+ FlushPendingNotifications(Flush_Layout);
+ 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
+nsGlobalWindow::Scroll(const ScrollToOptions& aOptions)
+{
+ ScrollTo(aOptions);
+}
+
+void
+nsGlobalWindow::ScrollTo(const CSSIntPoint& aScroll,
+ const ScrollOptions& aOptions)
+{
+ FlushPendingNotifications(Flush_Layout);
+ 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;
+ }
+
+ bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior);
+
+ sf->ScrollToCSSPixels(scroll, smoothScroll
+ ? nsIScrollableFrame::SMOOTH_MSD
+ : nsIScrollableFrame::INSTANT);
+ }
+}
+
+void
+nsGlobalWindow::ScrollBy(double aXScrollDif, double aYScrollDif)
+{
+ FlushPendingNotifications(Flush_Layout);
+ nsIScrollableFrame *sf = GetScrollFrame();
+
+ if (sf) {
+ // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast.
+ auto scrollDif = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScrollDif),
+ mozilla::ToZeroIfNonfinite(aYScrollDif));
+ // 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.
+ ScrollTo(sf->GetScrollPositionCSSPixels() + scrollDif, ScrollOptions());
+ }
+}
+
+void
+nsGlobalWindow::ScrollBy(const ScrollToOptions& aOptions)
+{
+ FlushPendingNotifications(Flush_Layout);
+ 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
+nsGlobalWindow::ScrollByLines(int32_t numLines,
+ const ScrollOptions& aOptions)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ FlushPendingNotifications(Flush_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.
+ bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior);
+
+ sf->ScrollBy(nsIntPoint(0, numLines), nsIScrollableFrame::LINES,
+ smoothScroll
+ ? nsIScrollableFrame::SMOOTH_MSD
+ : nsIScrollableFrame::INSTANT);
+ }
+}
+
+void
+nsGlobalWindow::ScrollByPages(int32_t numPages,
+ const ScrollOptions& aOptions)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ FlushPendingNotifications(Flush_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.
+ bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior);
+
+ sf->ScrollBy(nsIntPoint(0, numPages), nsIScrollableFrame::PAGES,
+ smoothScroll
+ ? nsIScrollableFrame::SMOOTH_MSD
+ : nsIScrollableFrame::INSTANT);
+ }
+}
+
+void
+nsGlobalWindow::MozScrollSnap()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ FlushPendingNotifications(Flush_Layout);
+ nsIScrollableFrame *sf = GetScrollFrame();
+ if (sf) {
+ sf->ScrollSnap();
+ }
+}
+
+void
+nsGlobalWindow::MozRequestOverfill(OverfillCallback& aCallback,
+ mozilla::ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsIWidget* widget = nsContentUtils::WidgetForDocument(mDoc);
+ if (widget) {
+ mozilla::layers::LayerManager* manager = widget->GetLayerManager();
+ if (manager) {
+ manager->RequestOverfill(&aCallback);
+ return;
+ }
+ }
+
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+}
+
+void
+nsGlobalWindow::ClearTimeout(int32_t aHandle)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (aHandle > 0) {
+ ClearTimeoutOrInterval(aHandle, Timeout::Reason::eTimeoutOrInterval);
+ }
+}
+
+void
+nsGlobalWindow::ClearInterval(int32_t aHandle)
+{
+ if (aHandle > 0) {
+ ClearTimeoutOrInterval(aHandle, Timeout::Reason::eTimeoutOrInterval);
+ }
+}
+
+void
+nsGlobalWindow::SetResizable(bool aResizable) const
+{
+ // nop
+}
+
+void
+nsGlobalWindow::CaptureEvents()
+{
+ if (mDoc) {
+ mDoc->WarnOnceAbout(nsIDocument::eUseOfCaptureEvents);
+ }
+}
+
+void
+nsGlobalWindow::ReleaseEvents()
+{
+ if (mDoc) {
+ mDoc->WarnOnceAbout(nsIDocument::eUseOfReleaseEvents);
+ }
+}
+
+static
+bool IsPopupBlocked(nsIDocument* aDoc)
+{
+ nsCOMPtr<nsIPopupWindowManager> pm =
+ do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
+
+ if (!pm) {
+ return false;
+ }
+
+ if (!aDoc) {
+ return true;
+ }
+
+ uint32_t permission = nsIPopupWindowManager::ALLOW_POPUP;
+ pm->TestPermission(aDoc->NodePrincipal(), &permission);
+ return permission == nsIPopupWindowManager::DENY_POPUP;
+}
+
+void
+nsGlobalWindow::FirePopupBlockedEvent(nsIDocument* 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;
+ init.mRequestingWindow = this;
+ init.mPopupWindowURI = aPopupURI;
+ init.mPopupWindowName = aPopupWindowName;
+ init.mPopupWindowFeatures = aPopupWindowFeatures;
+
+ RefPtr<PopupBlockedEvent> event =
+ PopupBlockedEvent::Constructor(aDoc,
+ NS_LITERAL_STRING("DOMPopupBlocked"),
+ init);
+
+ event->SetTrusted(true);
+
+ bool defaultActionEnabled;
+ aDoc->DispatchEvent(event, &defaultActionEnabled);
+}
+
+// static
+bool
+nsGlobalWindow::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);
+}
+
+bool
+nsGlobalWindow::PopupWhitelisted()
+{
+ if (!IsPopupBlocked(mDoc))
+ return true;
+
+ nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
+ if (parent == AsOuter())
+ {
+ return false;
+ }
+
+ return nsGlobalWindow::Cast(parent)->PopupWhitelisted();
+}
+
+/*
+ * Examine the current document state to see if we're in a way that is
+ * typically abused by web designers. The window.open code uses this
+ * routine to determine whether to allow the new window.
+ * Returns a value from the PopupControlState enum.
+ */
+PopupControlState
+nsGlobalWindow::RevisePopupAbuseLevel(PopupControlState aControl)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ NS_ASSERTION(mDocShell, "Must have docshell");
+
+ if (mDocShell->ItemType() != nsIDocShellTreeItem::typeContent) {
+ return openAllowed;
+ }
+
+ PopupControlState abuse = aControl;
+ switch (abuse) {
+ case openControlled:
+ case openAbused:
+ case openOverridden:
+ if (PopupWhitelisted())
+ abuse = PopupControlState(abuse - 1);
+ break;
+ case openAllowed: break;
+ default:
+ NS_WARNING("Strange PopupControlState!");
+ }
+
+ // limit the number of simultaneously open popups
+ if (abuse == openAbused || abuse == openControlled) {
+ int32_t popupMax = Preferences::GetInt("dom.popup_maximum", -1);
+ if (popupMax >= 0 && gOpenPopupSpamCount >= popupMax)
+ abuse = openOverridden;
+ }
+
+ return abuse;
+}
+
+/* If a window open is blocked, fire the appropriate DOM events. */
+void
+nsGlobalWindow::FireAbuseEvents(const nsAString &aPopupURL,
+ const nsAString &aPopupWindowName,
+ const nsAString &aPopupWindowFeatures)
+{
+ // fetch the URI of the window requesting the opened window
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetTop();
+ if (!window) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> topDoc = window->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<nsIDocument> 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), 0, baseURL,
+ getter_AddRefs(popupURI));
+
+ // fire an event chock full of informative URIs
+ FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName,
+ aPopupWindowFeatures);
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::OpenOuter(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ aError = OpenJS(aUrl, aName, aOptions, getter_AddRefs(window));
+ return window.forget();
+}
+
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(OpenOuter, (aUrl, aName, aOptions, aError), aError,
+ nullptr);
+}
+
+nsresult
+nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, nsIDocShellLoadInfo* aLoadInfo,
+ bool aForceNoOpener, nsPIDOMWindowOuter **_retval)
+{
+ FORWARD_TO_OUTER(Open, (aUrl, aName, aOptions, aLoadInfo, aForceNoOpener,
+ _retval),
+ NS_ERROR_NOT_INITIALIZED);
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ nullptr, nullptr, // No args
+ aLoadInfo,
+ aForceNoOpener,
+ _retval);
+}
+
+nsresult
+nsGlobalWindow::OpenJS(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, nsPIDOMWindowOuter **_retval)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ false, // aCalledNoScript
+ true, // aDoJSFixups
+ true, // aNavigate
+ nullptr, nullptr, // No args
+ nullptr, // aLoadInfo
+ false, // aForceNoOpener
+ _retval);
+}
+
+// like Open, but attaches to the new window any extra parameters past
+// [features] as a JS property named "arguments"
+nsresult
+nsGlobalWindow::OpenDialog(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ nsISupports* aExtraArgument,
+ nsPIDOMWindowOuter** _retval)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ return OpenInternal(aUrl, aName, aOptions,
+ true, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ nullptr, aExtraArgument, // Arguments
+ nullptr, // aLoadInfo
+ false, // aForceNoOpener
+ _retval);
+}
+
+// Like Open, but passes aNavigate=false.
+/* virtual */ nsresult
+nsGlobalWindow::OpenNoNavigate(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ nsPIDOMWindowOuter **_retval)
+{
+ MOZ_ASSERT(IsOuterWindow());
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ false, // aNavigate
+ nullptr, nullptr, // No args
+ nullptr, // aLoadInfo
+ false, // aForceNoOpener
+ _retval);
+
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::OpenDialogOuter(JSContext* aCx, const nsAString& aUrl,
+ const nsAString& aName, const nsAString& aOptions,
+ const Sequence<JS::Value>& aExtraArgument,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsIJSArgArray> argvArray;
+ aError = NS_CreateJSArgv(aCx, aExtraArgument.Length(),
+ aExtraArgument.Elements(),
+ getter_AddRefs(argvArray));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> dialog;
+ aError = OpenInternal(aUrl, aName, aOptions,
+ true, // aDialog
+ false, // aContentModal
+ false, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ argvArray, nullptr, // Arguments
+ nullptr, // aLoadInfo
+ false, // aForceNoOpener
+ getter_AddRefs(dialog));
+ return dialog.forget();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::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);
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetFramesOuter()
+{
+ RefPtr<nsPIDOMWindowOuter> frames(AsOuter());
+ FlushPendingNotifications(Flush_ContentAndNotify);
+ return frames.forget();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindow::GetFrames(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetFramesOuter, (), aError, nullptr);
+}
+
+nsGlobalWindow*
+nsGlobalWindow::CallerInnerWindow()
+{
+ JSContext *cx = nsContentUtils::GetCurrentJSContext();
+ NS_ENSURE_TRUE(cx, nullptr);
+ nsIGlobalObject* global = GetIncumbentGlobal();
+ NS_ENSURE_TRUE(global, nullptr);
+ JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject());
+ NS_ENSURE_TRUE(scope, nullptr);
+
+ // When Jetpack runs 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.
+ if (xpc::IsSandbox(scope)) {
+ JSAutoCompartment ac(cx, scope);
+ JS::Rooted<JSObject*> scopeProto(cx);
+ bool ok = JS_GetPrototype(cx, scope, &scopeProto);
+ NS_ENSURE_TRUE(ok, nullptr);
+ if (scopeProto && xpc::IsSandboxPrototypeProxy(scopeProto) &&
+ (scopeProto = js::CheckedUnwrap(scopeProto, /* stopAtWindowProxy = */ false)))
+ {
+ global = xpc::NativeGlobal(scopeProto);
+ NS_ENSURE_TRUE(global, nullptr);
+ }
+ }
+
+ // The calling window must be holding a reference, so we can return a weak
+ // pointer.
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
+ return nsGlobalWindow::Cast(win);
+}
+
+void
+nsGlobalWindow::PostMessageMozOuter(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ JS::Handle<JS::Value> aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ //
+ // 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<nsGlobalWindow> callerInnerWin = CallerInnerWindow();
+ nsIPrincipal* callerPrin;
+ if (callerInnerWin) {
+ MOZ_ASSERT(callerInnerWin->IsInnerWindow(),
+ "should have gotten an inner window here");
+
+ // 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) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> callerOuterURI;
+ if (NS_FAILED(callerPrin->GetURI(getter_AddRefs(callerOuterURI)))) {
+ return;
+ }
+
+ nsAutoString origin;
+ if (callerOuterURI) {
+ // if the principal has a URI, use that to generate the origin
+ nsContentUtils::GetUTFOrigin(callerPrin, origin);
+ }
+ else if (callerInnerWin) {
+ // otherwise use the URI of the document to generate origin
+ nsCOMPtr<nsIDocument> doc = callerInnerWin->GetExtantDoc();
+ if (!doc) {
+ return;
+ }
+ callerOuterURI = doc->GetDocumentURI();
+ // if the principal has a URI, use that to generate the origin
+ nsContentUtils::GetUTFOrigin(callerOuterURI, origin);
+ }
+ else {
+ // in case of a sandbox with a system principal origin can be empty
+ if (!nsContentUtils::IsSystemPrincipal(callerPrin)) {
+ return;
+ }
+ }
+
+ // Convert the provided origin string into a URI for comparison purposes.
+ nsCOMPtr<nsIPrincipal> providedPrincipal;
+
+ if (aTargetOrigin.EqualsASCII("/")) {
+ providedPrincipal = callerPrin;
+ }
+ // "*" indicates no specific origin is required.
+ else if (!aTargetOrigin.EqualsASCII("*")) {
+ nsCOMPtr<nsIURI> originURI;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(originURI), aTargetOrigin))) {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+
+ if (NS_FAILED(originURI->SetUserPass(EmptyCString())) ||
+ NS_FAILED(originURI->SetPath(EmptyCString()))) {
+ return;
+ }
+
+ PrincipalOriginAttributes attrs =
+ BasePrincipal::Cast(&aSubjectPrincipal)->OriginAttributesRef();
+ if (aSubjectPrincipal.GetIsSystemPrincipal()) {
+ auto principal = BasePrincipal::Cast(GetPrincipal());
+
+ if (attrs != principal->OriginAttributesRef()) {
+ nsCOMPtr<nsIURI> targetURI;
+ nsAutoCString targetURL;
+ nsAutoCString sourceOrigin;
+ nsAutoCString targetOrigin;
+
+ if (NS_FAILED(principal->GetURI(getter_AddRefs(targetURI))) ||
+ NS_FAILED(targetURI->GetAsciiSpec(targetURL)) ||
+ NS_FAILED(principal->GetOrigin(targetOrigin)) ||
+ NS_FAILED(aSubjectPrincipal.GetOrigin(sourceOrigin))) {
+ NS_WARNING("Failed to get source and target origins");
+ return;
+ }
+
+ nsContentUtils::LogSimpleConsoleError(
+ NS_ConvertUTF8toUTF16(nsPrintfCString(
+ "Attempting to post a message to window with url \"%s\" and "
+ "origin \"%s\" from a system principal scope with mismatched "
+ "origin \"%s\".",
+ targetURL.get(), targetOrigin.get(), sourceOrigin.get())),
+ "DOM");
+
+ attrs = principal->OriginAttributesRef();
+ }
+ }
+
+ // Create a nsIPrincipal inheriting the app/browser attributes from the
+ // caller.
+ providedPrincipal = BasePrincipal::CreateCodebasePrincipal(originURI, attrs);
+ if (NS_WARN_IF(!providedPrincipal)) {
+ return;
+ }
+ }
+
+ // Create and asynchronously dispatch a runnable which will handle actual DOM
+ // event creation and dispatch.
+ RefPtr<PostMessageEvent> event =
+ new PostMessageEvent(nsContentUtils::IsCallerChrome() || !callerInnerWin
+ ? nullptr
+ : callerInnerWin->GetOuterWindowInternal(),
+ origin,
+ this,
+ providedPrincipal,
+ callerInnerWin
+ ? callerInnerWin->GetDoc()
+ : nullptr,
+ nsContentUtils::IsCallerChrome());
+
+ JS::Rooted<JS::Value> message(aCx, aMessage);
+ JS::Rooted<JS::Value> transfer(aCx, aTransfer);
+
+ event->Write(aCx, message, transfer, JS::CloneDataPolicy(), aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ aError = NS_DispatchToCurrentThread(event);
+}
+
+void
+nsGlobalWindow::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
+nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const Optional<Sequence<JS::Value>>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());
+ if (aTransfer.WasPassed()) {
+ const Sequence<JS::Value >& values = aTransfer.Value();
+
+ // The input sequence only comes from the generated bindings code, which
+ // ensures it is rooted.
+ JS::HandleValueArray elements =
+ JS::HandleValueArray::fromMarkedLocation(values.Length(), values.Elements());
+
+ transferArray = JS::ObjectOrNullValue(JS_NewArrayObject(aCx, elements));
+ if (transferArray.isNull()) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ PostMessageMoz(aCx, aMessage, aTargetOrigin, transferArray,
+ aSubjectPrincipal, aError);
+}
+
+class nsCloseEvent : public Runnable {
+
+ RefPtr<nsGlobalWindow> mWindow;
+ bool mIndirect;
+
+ nsCloseEvent(nsGlobalWindow *aWindow, bool aIndirect)
+ : mWindow(aWindow)
+ , mIndirect(aIndirect)
+ {}
+
+public:
+
+ static nsresult
+ PostCloseEvent(nsGlobalWindow* aWindow, bool aIndirect) {
+ nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(aWindow, aIndirect);
+ nsresult rv = NS_DispatchToCurrentThread(ev);
+ if (NS_SUCCEEDED(rv))
+ aWindow->MaybeForgiveSpamCount();
+ return rv;
+ }
+
+ NS_IMETHOD Run() override {
+ if (mWindow) {
+ if (mIndirect) {
+ return PostCloseEvent(mWindow, false);
+ }
+ mWindow->ReallyCloseWindow();
+ }
+ return NS_OK;
+ }
+
+};
+
+bool
+nsGlobalWindow::CanClose()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (mIsChrome) {
+ nsCOMPtr<nsIBrowserDOMWindow> bwin;
+ nsIDOMChromeWindow* chromeWin = static_cast<nsGlobalChromeWindow*>(this);
+ chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
+
+ bool canClose = true;
+ if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) {
+ return canClose;
+ }
+ }
+
+ if (!mDocShell) {
+ return true;
+ }
+
+ // Ask the content viewer whether the toplevel window can close.
+ // If the content viewer returns false, it is responsible for calling
+ // Close() as soon as it is possible for the window to close.
+ // This allows us to not close the window while printing is happening.
+
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ bool canClose;
+ nsresult rv = cv->PermitUnload(&canClose);
+ if (NS_SUCCEEDED(rv) && !canClose)
+ return false;
+
+ rv = cv->RequestWindowClose(&canClose);
+ if (NS_SUCCEEDED(rv) && !canClose)
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsGlobalWindow::CloseOuter(bool aTrustedCaller)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell || IsInModalState() ||
+ (IsFrame() && !mDocShell->GetIsMozBrowserOrApp())) {
+ // 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-app or non-neterror
+ // windows that were not opened by script.
+ nsAutoString url;
+ nsresult rv = mDoc->GetURL(url);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (!mDocShell->GetIsApp() &&
+ !StringBeginsWith(url, NS_LITERAL_STRING("about:neterror")) &&
+ !mHadOriginalOpener && !aTrustedCaller) {
+ 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,
+ NS_LITERAL_CSTRING("DOM Window"), 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(NS_LITERAL_STRING("DOMWindowClose"))) {
+ // Someone chose to prevent the default action for this event, if
+ // so, let's not close this window after all...
+
+ mInClose = wasInClose;
+ return;
+ }
+
+ FinalClose();
+}
+
+void
+nsGlobalWindow::Close(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(CloseOuter, (nsContentUtils::IsCallerChrome()), aError, );
+}
+
+nsresult
+nsGlobalWindow::Close()
+{
+ FORWARD_TO_OUTER(Close, (), NS_ERROR_UNEXPECTED);
+ CloseOuter(/* aTrustedCaller = */ true);
+ return NS_OK;
+}
+
+void
+nsGlobalWindow::ForceClose()
+{
+ MOZ_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ if (IsFrame() || !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(NS_LITERAL_STRING("DOMWindowClose"));
+
+ FinalClose();
+}
+
+void
+nsGlobalWindow::FinalClose()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ // Flag that we were closed.
+ mIsClosed = 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->AsOuter();
+ if (NS_FAILED(nsCloseEvent::PostCloseEvent(this, indirect))) {
+ ReallyCloseWindow();
+ } else {
+ mHavePendingClose = true;
+ }
+}
+
+
+void
+nsGlobalWindow::ReallyCloseWindow()
+{
+ FORWARD_TO_OUTER_VOID(ReallyCloseWindow, ());
+
+ // Make sure we never reenter this method.
+ mHavePendingClose = true;
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+
+ // If there's no treeOwnerAsWin, this window must already be closed.
+
+ if (treeOwnerAsWin) {
+
+ // but if we're a browser window we could be in some nasty
+ // self-destroying cascade that we should mostly ignore
+
+ if (mDocShell) {
+ nsCOMPtr<nsIBrowserDOMWindow> bwin;
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
+ nsCOMPtr<nsPIDOMWindowOuter> rootWin =
+ rootItem ? rootItem->GetWindow() : nullptr;
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(rootWin));
+ if (chromeWin)
+ chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
+
+ if (rootWin) {
+ /* Normally we destroy the entire window, but not if
+ this DOM window belongs to a tabbed browser and doesn't
+ correspond to a tab. This allows a well-behaved tab
+ to destroy the container as it should but is a final measure
+ to prevent an errant tab from doing so when it shouldn't.
+ This works because we reach this code when we shouldn't only
+ in the particular circumstance that we belong to a tab
+ that has just been closed (and is therefore already missing
+ from the list of browsers) (and has an unload handler
+ that closes the window). */
+ // XXXbz now that we have mHavePendingClose, is this needed?
+ bool isTab = false;
+ if (rootWin == AsOuter() ||
+ !bwin || (bwin->IsTabContentWindow(GetOuterWindowInternal(),
+ &isTab), isTab))
+ treeOwnerAsWin->Destroy();
+ }
+ }
+
+ CleanUp();
+ }
+}
+
+void
+nsGlobalWindow::EnterModalState()
+{
+ MOZ_ASSERT(IsOuterWindow(), "Modal state is maintained on outer windows");
+
+ // GetScriptableTop, not GetTop, so that EnterModalState works properly with
+ // <iframe mozbrowser>.
+ nsGlobalWindow* topWin = GetScriptableTopInternal();
+
+ if (!topWin) {
+ NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
+ return;
+ }
+
+ // 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()) {
+ nsIPresShell* activeShell = activeESM->GetPresContext()->GetPresShell();
+ if (activeShell && (
+ nsContentUtils::ContentIsCrossDocDescendantOf(activeShell->GetDocument(), mDoc) ||
+ nsContentUtils::ContentIsCrossDocDescendantOf(mDoc, activeShell->GetDocument()))) {
+ EventStateManager::ClearGlobalActiveContent(activeESM);
+
+ activeShell->SetCapturingContent(nullptr, 0);
+
+ if (activeShell) {
+ RefPtr<nsFrameSelection> frameSelection = activeShell->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);
+ }
+
+ // 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.
+ nsIDocument* topDoc = topWin->GetExtantDoc();
+ nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
+ if (capturingContent && topDoc &&
+ nsContentUtils::ContentIsCrossDocDescendantOf(capturingContent, topDoc)) {
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+ }
+
+ if (topWin->mModalStateDepth == 0) {
+ NS_ASSERTION(!topWin->mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
+
+ topWin->mSuspendedDoc = topDoc;
+ if (topDoc) {
+ topDoc->SuppressEventHandling(nsIDocument::eAnimationsOnly);
+ }
+
+ nsGlobalWindow* inner = topWin->GetCurrentInnerWindowInternal();
+ if (inner) {
+ topWin->GetCurrentInnerWindowInternal()->Suspend();
+ }
+ }
+ topWin->mModalStateDepth++;
+}
+
+void
+nsGlobalWindow::LeaveModalState()
+{
+ MOZ_ASSERT(IsOuterWindow(), "Modal state is maintained on outer windows");
+
+ nsGlobalWindow* topWin = GetScriptableTopInternal();
+
+ if (!topWin) {
+ NS_ERROR("Uh, LeaveModalState() called w/o a reachable top window?");
+ return;
+ }
+
+ MOZ_ASSERT(topWin->mModalStateDepth != 0);
+ MOZ_ASSERT(IsSuspended());
+ MOZ_ASSERT(topWin->IsSuspended());
+ topWin->mModalStateDepth--;
+
+ nsGlobalWindow* inner = topWin->GetCurrentInnerWindowInternal();
+
+ if (topWin->mModalStateDepth == 0) {
+ if (inner) {
+ inner->Resume();
+ }
+
+ if (topWin->mSuspendedDoc) {
+ nsCOMPtr<nsIDocument> currentDoc = topWin->GetExtantDoc();
+ topWin->mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eAnimationsOnly,
+ currentDoc == topWin->mSuspendedDoc);
+ topWin->mSuspendedDoc = nullptr;
+ }
+ }
+
+ // Remember the time of the last dialog quit.
+ if (inner) {
+ inner->mLastDialogQuitTime = TimeStamp::Now();
+ }
+
+ if (topWin->mModalStateDepth == 0) {
+ RefPtr<Event> event = NS_NewDOMEvent(inner, nullptr, nullptr);
+ event->InitEvent(NS_LITERAL_STRING("endmodalstate"), true, false);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ bool dummy;
+ topWin->DispatchEvent(event, &dummy);
+ }
+}
+
+bool
+nsGlobalWindow::IsInModalState()
+{
+ nsGlobalWindow *topWin = GetScriptableTopInternal();
+
+ 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;
+}
+
+// static
+void
+nsGlobalWindow::NotifyDOMWindowDestroyed(nsGlobalWindow* aWindow) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ observerService->
+ NotifyObservers(ToSupports(aWindow),
+ DOM_WINDOW_DESTROYED_TOPIC, nullptr);
+ }
+}
+
+// Try to match compartments that are not web content by matching compartments
+// with principals that are either the system principal or an expanded principal.
+// This may not return true for all non-web-content compartments.
+struct BrowserCompartmentMatcher : public js::CompartmentFilter {
+ virtual bool match(JSCompartment* c) const override
+ {
+ nsCOMPtr<nsIPrincipal> pc = nsJSPrincipals::get(JS_GetCompartmentPrincipals(c));
+ return nsContentUtils::IsSystemOrExpandedPrincipal(pc);
+ }
+};
+
+
+class WindowDestroyedEvent : public Runnable
+{
+public:
+ WindowDestroyedEvent(nsIDOMWindow* aWindow, uint64_t aID,
+ const char* aTopic) :
+ mID(aID), mTopic(aTopic)
+ {
+ mWindow = do_GetWeakReference(aWindow);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper =
+ do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
+ if (wrapper) {
+ wrapper->SetData(mID);
+ observerService->NotifyObservers(wrapper, mTopic.get(), nullptr);
+ }
+ }
+
+ bool skipNukeCrossCompartment = false;
+#ifndef DEBUG
+ nsCOMPtr<nsIAppStartup> appStartup =
+ do_GetService(NS_APPSTARTUP_CONTRACTID);
+
+ if (appStartup) {
+ appStartup->GetShuttingDown(&skipNukeCrossCompartment);
+ }
+#endif
+
+ nsCOMPtr<nsISupports> window = do_QueryReferent(mWindow);
+ if (!skipNukeCrossCompartment && window) {
+ nsGlobalWindow* win = nsGlobalWindow::FromSupports(window);
+ nsGlobalWindow* currentInner = win->IsInnerWindow() ? win : win->GetCurrentInnerWindowInternal();
+ NS_ENSURE_TRUE(currentInner, NS_OK);
+
+ AutoSafeJSContext cx;
+ JS::Rooted<JSObject*> obj(cx, currentInner->FastGetGlobalJSObject());
+ // We only want to nuke wrappers for the chrome->content case
+ if (obj && !js::IsSystemCompartment(js::GetObjectCompartment(obj))) {
+ js::NukeCrossCompartmentWrappers(cx,
+ BrowserCompartmentMatcher(),
+ js::SingleCompartment(js::GetObjectCompartment(obj)),
+ win->IsInnerWindow() ? js::DontNukeWindowReferences
+ : js::NukeWindowReferences);
+ }
+ }
+
+ return NS_OK;
+ }
+
+private:
+ uint64_t mID;
+ nsCString mTopic;
+ nsWeakPtr mWindow;
+};
+
+void
+nsGlobalWindow::NotifyWindowIDDestroyed(const char* aTopic)
+{
+ nsCOMPtr<nsIRunnable> runnable = new WindowDestroyedEvent(this, mWindowID, aTopic);
+ nsresult rv = NS_DispatchToCurrentThread(runnable);
+ if (NS_SUCCEEDED(rv)) {
+ mNotifiedIDDestroyed = true;
+ }
+}
+
+// static
+void
+nsGlobalWindow::NotifyDOMWindowFrozen(nsGlobalWindow* aWindow) {
+ if (aWindow && aWindow->IsInnerWindow()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ observerService->
+ NotifyObservers(ToSupports(aWindow),
+ DOM_WINDOW_FROZEN_TOPIC, nullptr);
+ }
+ }
+}
+
+// static
+void
+nsGlobalWindow::NotifyDOMWindowThawed(nsGlobalWindow* aWindow) {
+ if (aWindow && aWindow->IsInnerWindow()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ observerService->
+ NotifyObservers(ToSupports(aWindow),
+ DOM_WINDOW_THAWED_TOPIC, nullptr);
+ }
+ }
+}
+
+JSObject*
+nsGlobalWindow::GetCachedXBLPrototypeHandler(nsXBLPrototypeHandler* aKey)
+{
+ JS::Rooted<JSObject*> handler(RootingCx());
+ if (mCachedXBLPrototypeHandlers) {
+ mCachedXBLPrototypeHandlers->Get(aKey, handler.address());
+ }
+ return handler;
+}
+
+void
+nsGlobalWindow::CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey,
+ JS::Handle<JSObject*> aHandler)
+{
+ if (!mCachedXBLPrototypeHandlers) {
+ mCachedXBLPrototypeHandlers = new XBLPrototypeHandlerTable();
+ PreserveWrapper(ToSupports(this));
+ }
+
+ mCachedXBLPrototypeHandlers->Put(aKey, aHandler);
+}
+
+Element*
+nsGlobalWindow::GetFrameElementOuter(nsIPrincipal& aSubjectPrincipal)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell || mDocShell->GetIsMozBrowserOrApp()) {
+ return nullptr;
+ }
+
+ // Per HTML5, the frameElement getter returns null in cross-origin situations.
+ Element* element = GetRealFrameElementOuter();
+ if (!element) {
+ return nullptr;
+ }
+
+ if (!aSubjectPrincipal.SubsumesConsideringDomain(element->NodePrincipal())) {
+ return nullptr;
+ }
+
+ return element;
+}
+
+Element*
+nsGlobalWindow::GetFrameElement(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetFrameElementOuter, (aSubjectPrincipal), aError,
+ nullptr);
+}
+
+Element*
+nsGlobalWindow::GetRealFrameElementOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> parent;
+ mDocShell->GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent));
+
+ if (!parent || parent == mDocShell) {
+ // We're at a chrome boundary, don't expose the chrome iframe
+ // element to content code.
+ return nullptr;
+ }
+
+ return mFrameElement;
+}
+
+Element*
+nsGlobalWindow::GetRealFrameElement(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetRealFrameElementOuter, (), aError, nullptr);
+}
+
+/**
+ * nsIGlobalWindow::GetFrameElement (when called from C++) is just a wrapper
+ * around GetRealFrameElement.
+ */
+already_AddRefed<nsIDOMElement>
+nsGlobalWindow::GetFrameElement()
+{
+ FORWARD_TO_INNER(GetFrameElement, (), nullptr);
+
+ ErrorResult dummy;
+ nsCOMPtr<nsIDOMElement> frameElement =
+ do_QueryInterface(GetRealFrameElement(dummy));
+ dummy.SuppressException();
+ return frameElement.forget();
+}
+
+/* static */ bool
+nsGlobalWindow::TokenizeDialogOptions(nsAString& aToken,
+ nsAString::const_iterator& aIter,
+ nsAString::const_iterator aEnd)
+{
+ while (aIter != aEnd && nsCRT::IsAsciiSpace(*aIter)) {
+ ++aIter;
+ }
+
+ if (aIter == aEnd) {
+ return false;
+ }
+
+ if (*aIter == ';' || *aIter == ':' || *aIter == '=') {
+ aToken.Assign(*aIter);
+ ++aIter;
+ return true;
+ }
+
+ nsAString::const_iterator start = aIter;
+
+ // Skip characters until we find whitespace, ';', ':', or '='
+ while (aIter != aEnd && !nsCRT::IsAsciiSpace(*aIter) &&
+ *aIter != ';' &&
+ *aIter != ':' &&
+ *aIter != '=') {
+ ++aIter;
+ }
+
+ aToken.Assign(Substring(start, aIter));
+ return true;
+}
+
+// Helper for converting window.showModalDialog() options (list of ';'
+// separated name (:|=) value pairs) to a format that's parsable by
+// our normal window opening code.
+
+/* static */
+void
+nsGlobalWindow::ConvertDialogOptions(const nsAString& aOptions,
+ nsAString& aResult)
+{
+ nsAString::const_iterator end;
+ aOptions.EndReading(end);
+
+ nsAString::const_iterator iter;
+ aOptions.BeginReading(iter);
+
+ nsAutoString token;
+ nsAutoString name;
+ nsAutoString value;
+
+ while (true) {
+ if (!TokenizeDialogOptions(name, iter, end)) {
+ break;
+ }
+
+ // Invalid name.
+ if (name.EqualsLiteral("=") ||
+ name.EqualsLiteral(":") ||
+ name.EqualsLiteral(";")) {
+ break;
+ }
+
+ if (!TokenizeDialogOptions(token, iter, end)) {
+ break;
+ }
+
+ if (!token.EqualsLiteral(":") && !token.EqualsLiteral("=")) {
+ continue;
+ }
+
+ // We found name followed by ':' or '='. Look for a value.
+ if (!TokenizeDialogOptions(value, iter, end)) {
+ break;
+ }
+
+ if (name.LowerCaseEqualsLiteral("center")) {
+ if (value.LowerCaseEqualsLiteral("on") ||
+ value.LowerCaseEqualsLiteral("yes") ||
+ value.LowerCaseEqualsLiteral("1")) {
+ aResult.AppendLiteral(",centerscreen=1");
+ }
+ } else if (name.LowerCaseEqualsLiteral("dialogwidth")) {
+ if (!value.IsEmpty()) {
+ aResult.AppendLiteral(",width=");
+ aResult.Append(value);
+ }
+ } else if (name.LowerCaseEqualsLiteral("dialogheight")) {
+ if (!value.IsEmpty()) {
+ aResult.AppendLiteral(",height=");
+ aResult.Append(value);
+ }
+ } else if (name.LowerCaseEqualsLiteral("dialogtop")) {
+ if (!value.IsEmpty()) {
+ aResult.AppendLiteral(",top=");
+ aResult.Append(value);
+ }
+ } else if (name.LowerCaseEqualsLiteral("dialogleft")) {
+ if (!value.IsEmpty()) {
+ aResult.AppendLiteral(",left=");
+ aResult.Append(value);
+ }
+ } else if (name.LowerCaseEqualsLiteral("resizable")) {
+ if (value.LowerCaseEqualsLiteral("on") ||
+ value.LowerCaseEqualsLiteral("yes") ||
+ value.LowerCaseEqualsLiteral("1")) {
+ aResult.AppendLiteral(",resizable=1");
+ }
+ } else if (name.LowerCaseEqualsLiteral("scroll")) {
+ if (value.LowerCaseEqualsLiteral("off") ||
+ value.LowerCaseEqualsLiteral("no") ||
+ value.LowerCaseEqualsLiteral("0")) {
+ aResult.AppendLiteral(",scrollbars=0");
+ }
+ }
+
+ if (iter == end ||
+ !TokenizeDialogOptions(token, iter, end) ||
+ !token.EqualsLiteral(";")) {
+ break;
+ }
+ }
+}
+
+already_AddRefed<nsIVariant>
+nsGlobalWindow::ShowModalDialogOuter(const nsAString& aUrl,
+ nsIVariant* aArgument,
+ const nsAString& aOptions,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (mDoc) {
+ mDoc->WarnOnceAbout(nsIDocument::eShowModalDialog);
+ }
+
+ if (!IsShowModalDialogEnabled()) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<DialogValueHolder> argHolder =
+ new DialogValueHolder(&aSubjectPrincipal, aArgument);
+
+ // Before bringing up the window/dialog, unsuppress painting and flush
+ // pending reflows.
+ EnsureReflowFlushAndPaint();
+
+ if (!AreDialogsEnabled()) {
+ // We probably want to keep throwing here; silently doing nothing is a bit
+ // weird given the typical use cases of showModalDialog().
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ if (ShouldPromptToBlockDialogs() && !ConfirmDialogIfNeeded()) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> dlgWin;
+ nsAutoString options(NS_LITERAL_STRING("-moz-internal-modal=1,status=1"));
+
+ ConvertDialogOptions(aOptions, options);
+
+ options.AppendLiteral(",scrollbars=1,centerscreen=1,resizable=0");
+
+ EnterModalState();
+ uint32_t oldMicroTaskLevel = nsContentUtils::MicroTaskLevel();
+ nsContentUtils::SetMicroTaskLevel(0);
+ aError = OpenInternal(aUrl, EmptyString(), options,
+ false, // aDialog
+ true, // aContentModal
+ true, // aCalledNoScript
+ true, // aDoJSFixups
+ true, // aNavigate
+ nullptr, argHolder, // args
+ nullptr, // aLoadInfo
+ false, // aForceNoOpener
+ getter_AddRefs(dlgWin));
+ nsContentUtils::SetMicroTaskLevel(oldMicroTaskLevel);
+ LeaveModalState();
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDOMModalContentWindow> dialog = do_QueryInterface(dlgWin);
+ if (!dialog) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIVariant> retVal;
+ aError = dialog->GetReturnValue(getter_AddRefs(retVal));
+ MOZ_ASSERT(!aError.Failed());
+
+ return retVal.forget();
+}
+
+already_AddRefed<nsIVariant>
+nsGlobalWindow::ShowModalDialog(const nsAString& aUrl, nsIVariant* aArgument,
+ const nsAString& aOptions,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(ShowModalDialogOuter,
+ (aUrl, aArgument, aOptions, aSubjectPrincipal,
+ aError), aError, nullptr);
+}
+
+void
+nsGlobalWindow::ShowModalDialog(JSContext* aCx, const nsAString& aUrl,
+ JS::Handle<JS::Value> aArgument,
+ const nsAString& aOptions,
+ JS::MutableHandle<JS::Value> aRetval,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsIVariant> args;
+ aError = nsContentUtils::XPConnect()->JSToVariant(aCx,
+ aArgument,
+ getter_AddRefs(args));
+ if (aError.Failed()) {
+ return;
+ }
+
+ nsCOMPtr<nsIVariant> retVal =
+ ShowModalDialog(aUrl, args, aOptions, aSubjectPrincipal, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> result(aCx);
+ if (retVal) {
+ aError = nsContentUtils::XPConnect()->VariantToJS(aCx,
+ FastGetGlobalJSObject(),
+ retVal, aRetval);
+ } else {
+ aRetval.setNull();
+ }
+}
+
+class ChildCommandDispatcher : public Runnable
+{
+public:
+ ChildCommandDispatcher(nsGlobalWindow* aWindow,
+ nsITabChild* aTabChild,
+ const nsAString& aAction)
+ : mWindow(aWindow), mTabChild(aTabChild), mAction(aAction) {}
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsPIWindowRoot> root = mWindow->GetTopWindowRoot();
+ if (!root) {
+ return NS_OK;
+ }
+
+ nsTArray<nsCString> enabledCommands, disabledCommands;
+ root->GetEnabledDisabledCommands(enabledCommands, disabledCommands);
+ if (enabledCommands.Length() || disabledCommands.Length()) {
+ mTabChild->EnableDisableCommands(mAction, enabledCommands, disabledCommands);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsGlobalWindow> mWindow;
+ nsCOMPtr<nsITabChild> mTabChild;
+ nsString mAction;
+};
+
+class CommandDispatcher : public Runnable
+{
+public:
+ CommandDispatcher(nsIDOMXULCommandDispatcher* aDispatcher,
+ const nsAString& aAction)
+ : mDispatcher(aDispatcher), mAction(aAction) {}
+
+ NS_IMETHOD Run() override
+ {
+ return mDispatcher->UpdateCommands(mAction);
+ }
+
+ nsCOMPtr<nsIDOMXULCommandDispatcher> mDispatcher;
+ nsString mAction;
+};
+
+nsresult
+nsGlobalWindow::UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason)
+{
+ // If this is a child process, redirect to the parent process.
+ if (nsIDocShell* docShell = GetDocShell()) {
+ if (nsCOMPtr<nsITabChild> child = docShell->GetTabChild()) {
+ nsContentUtils::AddScriptRunner(new ChildCommandDispatcher(this, child,
+ anAction));
+ return NS_OK;
+ }
+ }
+
+ nsPIDOMWindowOuter *rootWindow = nsGlobalWindow::GetPrivateRoot();
+ if (!rootWindow)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMXULDocument> xulDoc =
+ do_QueryInterface(rootWindow->GetExtantDoc());
+ // See if we contain a XUL document.
+ // selectionchange action is only used for mozbrowser, not for XUL. So we bypass
+ // XUL command dispatch if anAction is "selectionchange".
+ if (xulDoc && !anAction.EqualsLiteral("selectionchange")) {
+ // Retrieve the command dispatcher and call updateCommands on it.
+ nsCOMPtr<nsIDOMXULCommandDispatcher> xulCommandDispatcher;
+ xulDoc->GetCommandDispatcher(getter_AddRefs(xulCommandDispatcher));
+ if (xulCommandDispatcher) {
+ nsContentUtils::AddScriptRunner(new CommandDispatcher(xulCommandDispatcher,
+ anAction));
+ }
+ }
+
+ return NS_OK;
+}
+
+ThrottledEventQueue*
+nsGlobalWindow::GetThrottledEventQueue()
+{
+ // We must have an outer to access the TabGroup.
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (!outer) {
+ return nullptr;
+ }
+
+ return TabGroup()->GetThrottledEventQueue();
+}
+
+Selection*
+nsGlobalWindow::GetSelectionOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ nsISelection* domSelection =
+ presShell->GetCurrentSelection(SelectionType::eNormal);
+ return domSelection ? domSelection->AsSelection() : nullptr;
+}
+
+Selection*
+nsGlobalWindow::GetSelection(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetSelectionOuter, (), aError, nullptr);
+}
+
+already_AddRefed<nsISelection>
+nsGlobalWindow::GetSelection()
+{
+ nsCOMPtr<nsISelection> selection = GetSelectionOuter();
+ return selection.forget();
+}
+
+bool
+nsGlobalWindow::FindOuter(const nsAString& aString, bool aCaseSensitive,
+ bool aBackwards, bool aWrapAround, bool aWholeWord,
+ bool aSearchInFrames, bool aShowDialog,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (Preferences::GetBool("dom.disable_window_find", false)) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return false;
+ }
+
+ 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(PromiseFlatString(aString).get());
+ 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(AsOuter()); // paranoia
+ framesFinder->SetCurrentSearchFrame(AsOuter());
+ }
+
+ // The Find API does not accept empty strings. Launch the Find Dialog.
+ if (aString.IsEmpty() || aShowDialog) {
+ // See if the find dialog is already up using nsIWindowMediator
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+
+ nsCOMPtr<mozIDOMWindowProxy> findDialog;
+
+ if (windowMediator) {
+ windowMediator->GetMostRecentWindow(u"findInPage",
+ getter_AddRefs(findDialog));
+ }
+
+ if (findDialog) {
+ // The Find dialog is already open, bring it to the top.
+ auto* piFindDialog = nsPIDOMWindowOuter::From(findDialog);
+ aError = piFindDialog->Focus();
+ } else if (finder) {
+ // Open a Find dialog
+ nsCOMPtr<nsPIDOMWindowOuter> dialog;
+ aError = OpenDialog(NS_LITERAL_STRING("chrome://global/content/finddialog.xul"),
+ NS_LITERAL_STRING("_blank"),
+ NS_LITERAL_STRING("chrome, resizable=no, dependent=yes"),
+ finder, getter_AddRefs(dialog));
+ }
+
+ return false;
+ }
+
+ // Launch the search with the passed in search string
+ bool didFind = false;
+ aError = finder->FindNext(&didFind);
+ return didFind;
+}
+
+bool
+nsGlobalWindow::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
+nsGlobalWindow::Atob(const nsAString& aAsciiBase64String,
+ nsAString& aBinaryData, ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ aError = nsContentUtils::Atob(aAsciiBase64String, aBinaryData);
+}
+
+void
+nsGlobalWindow::Btoa(const nsAString& aBinaryData,
+ nsAString& aAsciiBase64String, ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ aError = nsContentUtils::Btoa(aBinaryData, aAsciiBase64String);
+}
+
+//*****************************************************************************
+// nsGlobalWindow::nsIDOMEventTarget
+//*****************************************************************************
+
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetOwnerGlobalForBindings()
+{
+ if (IsOuterWindow()) {
+ return AsOuter();
+ }
+
+ return nsPIDOMWindowOuter::GetFromCurrentInner(AsInner());
+}
+
+NS_IMETHODIMP
+nsGlobalWindow::RemoveEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture)
+{
+ if (RefPtr<EventListenerManager> elm = GetExistingListenerManager()) {
+ elm->RemoveEventListener(aType, aListener, aUseCapture);
+ }
+ return NS_OK;
+}
+
+NS_IMPL_REMOVE_SYSTEM_EVENT_LISTENER(nsGlobalWindow)
+
+NS_IMETHODIMP
+nsGlobalWindow::DispatchEvent(nsIDOMEvent* aEvent, bool* aRetVal)
+{
+ FORWARD_TO_INNER(DispatchEvent, (aEvent, aRetVal), NS_OK);
+
+ if (!AsInner()->IsCurrentInnerWindow()) {
+ NS_WARNING("DispatchEvent called on non-current inner window, dropping. "
+ "Please check the window in the caller instead.");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mDoc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Obtain a presentation shell
+ nsIPresShell *shell = mDoc->GetShell();
+ RefPtr<nsPresContext> presContext;
+ if (shell) {
+ // Retrieve the context
+ presContext = shell->GetPresContext();
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = EventDispatcher::DispatchDOMEvent(AsInner(), nullptr, aEvent,
+ presContext, &status);
+
+ *aRetVal = (status != nsEventStatus_eConsumeNoDefault);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsGlobalWindow::AddEventListener(const nsAString& aType,
+ nsIDOMEventListener *aListener,
+ bool aUseCapture, bool aWantsUntrusted,
+ uint8_t aOptionalArgc)
+{
+ NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1,
+ "Won't check if this is chrome, you want to set "
+ "aWantsUntrusted to false or make the aWantsUntrusted "
+ "explicit by making optional_argc non-zero.");
+
+ if (!aWantsUntrusted &&
+ (aOptionalArgc < 2 && !nsContentUtils::IsChromeDoc(mDoc))) {
+ aWantsUntrusted = true;
+ }
+
+ EventListenerManager* manager = GetOrCreateListenerManager();
+ NS_ENSURE_STATE(manager);
+ manager->AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted);
+ return NS_OK;
+}
+
+void
+nsGlobalWindow::AddEventListener(const nsAString& aType,
+ EventListener* aListener,
+ const AddEventListenerOptionsOrBoolean& aOptions,
+ const Nullable<bool>& aWantsUntrusted,
+ ErrorResult& aRv)
+{
+ if (IsOuterWindow() && mInnerWindow &&
+ !nsContentUtils::CanCallerAccess(mInnerWindow)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ bool wantsUntrusted;
+ if (aWantsUntrusted.IsNull()) {
+ wantsUntrusted = !nsContentUtils::IsChromeDoc(mDoc);
+ } else {
+ wantsUntrusted = aWantsUntrusted.Value();
+ }
+
+ EventListenerManager* manager = GetOrCreateListenerManager();
+ if (!manager) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ manager->AddEventListener(aType, aListener, aOptions, wantsUntrusted);
+}
+
+NS_IMETHODIMP
+nsGlobalWindow::AddSystemEventListener(const nsAString& aType,
+ nsIDOMEventListener *aListener,
+ bool aUseCapture,
+ bool aWantsUntrusted,
+ uint8_t aOptionalArgc)
+{
+ NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1,
+ "Won't check if this is chrome, you want to set "
+ "aWantsUntrusted to false or make the aWantsUntrusted "
+ "explicit by making optional_argc non-zero.");
+
+ if (IsOuterWindow() && mInnerWindow &&
+ !nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(mInnerWindow)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ if (!aWantsUntrusted &&
+ (aOptionalArgc < 2 && !nsContentUtils::IsChromeDoc(mDoc))) {
+ aWantsUntrusted = true;
+ }
+
+ return NS_AddSystemEventListener(this, aType, aListener, aUseCapture,
+ aWantsUntrusted);
+}
+
+EventListenerManager*
+nsGlobalWindow::GetOrCreateListenerManager()
+{
+ FORWARD_TO_INNER_CREATE(GetOrCreateListenerManager, (), nullptr);
+
+ if (!mListenerManager) {
+ mListenerManager =
+ new EventListenerManager(static_cast<EventTarget*>(this));
+ }
+
+ return mListenerManager;
+}
+
+EventListenerManager*
+nsGlobalWindow::GetExistingListenerManager() const
+{
+ FORWARD_TO_INNER(GetExistingListenerManager, (), nullptr);
+
+ return mListenerManager;
+}
+
+nsIScriptContext*
+nsGlobalWindow::GetContextForEventHandlers(nsresult* aRv)
+{
+ *aRv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_TRUE(!IsInnerWindow() || AsInner()->IsCurrentInnerWindow(), nullptr);
+
+ nsIScriptContext* scx;
+ if ((scx = GetContext())) {
+ *aRv = NS_OK;
+ return scx;
+ }
+ return nullptr;
+}
+
+//*****************************************************************************
+// nsGlobalWindow::nsPIDOMWindow
+//*****************************************************************************
+
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetPrivateParent()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
+
+ if (AsOuter() == parent) {
+ nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler));
+ if (!chromeElement)
+ return nullptr; // This is ok, just means a null parent.
+
+ nsIDocument* doc = chromeElement->GetComposedDoc();
+ if (!doc)
+ return nullptr; // This is ok, just means a null parent.
+
+ return doc->GetWindow();
+ }
+
+ return parent;
+}
+
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetPrivateRoot()
+{
+ if (IsInnerWindow()) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (!outer) {
+ NS_WARNING("No outer window available!");
+ return nullptr;
+ }
+ return outer->GetPrivateRoot();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> top = GetTop();
+
+ nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler));
+ if (chromeElement) {
+ nsIDocument* doc = chromeElement->GetComposedDoc();
+ if (doc) {
+ nsCOMPtr<nsPIDOMWindowOuter> parent = doc->GetWindow();
+ if (parent) {
+ top = parent->GetTop();
+ }
+ }
+ }
+
+ return top;
+}
+
+Location*
+nsGlobalWindow::GetLocation(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ nsIDocShell *docShell = GetDocShell();
+ if (!mLocation && docShell) {
+ mLocation = new Location(AsInner(), docShell);
+ }
+ return mLocation;
+}
+
+nsIDOMLocation*
+nsGlobalWindow::GetLocation()
+{
+ FORWARD_TO_INNER(GetLocation, (), nullptr);
+
+ ErrorResult dummy;
+ nsIDOMLocation* location = GetLocation(dummy);
+ dummy.SuppressException();
+ return location;
+}
+
+void
+nsGlobalWindow::ActivateOrDeactivate(bool aActivate)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ if (!mDoc) {
+ return;
+ }
+
+ // Set / unset mIsActive on the top level window, which is used for the
+ // :-moz-window-inactive pseudoclass, and its sheet (if any).
+ nsCOMPtr<nsIWidget> mainWidget = GetMainWidget();
+ nsCOMPtr<nsIWidget> topLevelWidget;
+ if (mainWidget) {
+ // Get the top level widget (if the main widget is a sheet, this will
+ // be the sheet's top (non-sheet) parent).
+ topLevelWidget = mainWidget->GetSheetWindowParent();
+ if (!topLevelWidget) {
+ topLevelWidget = mainWidget;
+ }
+ }
+
+ SetActive(aActivate);
+
+ if (mainWidget != topLevelWidget) {
+ // This is a workaround for the following problem:
+ // When a window with an open sheet gains or loses focus, only the sheet
+ // window receives the NS_ACTIVATE/NS_DEACTIVATE event. However the
+ // styling of the containing top level window also needs to change. We
+ // get around this by calling nsPIDOMWindow::SetActive() on both windows.
+
+ // Get the top level widget's nsGlobalWindow
+ nsCOMPtr<nsPIDOMWindowOuter> topLevelWindow;
+
+ // widgetListener should be a nsXULWindow
+ nsIWidgetListener* listener = topLevelWidget->GetWidgetListener();
+ if (listener) {
+ nsCOMPtr<nsIXULWindow> window = listener->GetXULWindow();
+ nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(window));
+ topLevelWindow = do_GetInterface(req);
+ }
+
+ if (topLevelWindow) {
+ topLevelWindow->SetActive(aActivate);
+ }
+ }
+}
+
+static bool
+NotifyDocumentTree(nsIDocument* aDocument, void* aData)
+{
+ aDocument->EnumerateSubDocuments(NotifyDocumentTree, nullptr);
+ aDocument->DocumentStatesChanged(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+ return true;
+}
+
+void
+nsGlobalWindow::SetActive(bool aActive)
+{
+ nsPIDOMWindow::SetActive(aActive);
+ if (mDoc) {
+ NotifyDocumentTree(mDoc, nullptr);
+ }
+}
+
+bool
+nsGlobalWindow::IsTopLevelWindowActive()
+{
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(GetDocShell());
+ if (!treeItem) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootItem));
+ if (!rootItem) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = rootItem->GetWindow();
+ return domWindow && domWindow->IsActive();
+}
+
+void nsGlobalWindow::SetIsBackground(bool aIsBackground)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ bool resetTimers = (!aIsBackground && AsOuter()->IsBackground());
+ nsPIDOMWindow::SetIsBackground(aIsBackground);
+ if (resetTimers) {
+ ResetTimersForThrottleReduction(gMinBackgroundTimeoutValue);
+ }
+
+ if (!aIsBackground) {
+ nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
+ if (inner) {
+ inner->UnthrottleIdleCallbackRequests();
+ }
+ }
+#ifdef MOZ_GAMEPAD
+ if (!aIsBackground) {
+ nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
+ if (inner) {
+ inner->SyncGamepadState();
+ }
+ }
+#endif
+}
+
+void nsGlobalWindow::MaybeUpdateTouchState()
+{
+ FORWARD_TO_INNER_VOID(MaybeUpdateTouchState, ());
+
+ if (mMayHaveTouchEventListener) {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(static_cast<nsIDOMWindow*>(this),
+ DOM_TOUCH_LISTENER_ADDED,
+ nullptr);
+ }
+ }
+}
+
+void
+nsGlobalWindow::EnableGamepadUpdates()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ if (mHasGamepad) {
+#ifdef MOZ_GAMEPAD
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ if (gamepadManager) {
+ gamepadManager->AddListener(this);
+ }
+#endif
+ }
+}
+
+void
+nsGlobalWindow::DisableGamepadUpdates()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ if (mHasGamepad) {
+#ifdef MOZ_GAMEPAD
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ if (gamepadManager) {
+ gamepadManager->RemoveListener(this);
+ }
+#endif
+ }
+}
+
+void
+nsGlobalWindow::EnableVRUpdates()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ if (mHasVREvents && !mVREventObserver) {
+ mVREventObserver = new VREventObserver(this);
+ }
+}
+
+void
+nsGlobalWindow::DisableVRUpdates()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ mVREventObserver = nullptr;
+}
+
+void
+nsGlobalWindow::SetChromeEventHandler(EventTarget* aChromeEventHandler)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ SetChromeEventHandlerInternal(aChromeEventHandler);
+ // update the chrome event handler on all our inner windows
+ for (nsGlobalWindow *inner = (nsGlobalWindow *)PR_LIST_HEAD(this);
+ inner != this;
+ inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) {
+ NS_ASSERTION(!inner->mOuterWindow || inner->mOuterWindow == AsOuter(),
+ "bad outer window pointer");
+ inner->SetChromeEventHandlerInternal(aChromeEventHandler);
+ }
+}
+
+static bool IsLink(nsIContent* aContent)
+{
+ return aContent && (aContent->IsHTMLElement(nsGkAtoms::a) ||
+ aContent->AttrValueIs(kNameSpaceID_XLink, nsGkAtoms::type,
+ nsGkAtoms::simple, eCaseMatters));
+}
+
+static bool ShouldShowFocusRingIfFocusedByMouse(nsIContent* aNode)
+{
+ if (!aNode) {
+ return true;
+ }
+ return !IsLink(aNode) &&
+ !aNode->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio);
+}
+
+void
+nsGlobalWindow::SetFocusedNode(nsIContent* aNode,
+ uint32_t aFocusMethod,
+ bool aNeedsFocus)
+{
+ FORWARD_TO_INNER_VOID(SetFocusedNode, (aNode, aFocusMethod, aNeedsFocus));
+
+ if (aNode && aNode->GetComposedDoc() != mDoc) {
+ NS_WARNING("Trying to set focus to a node from a wrong document");
+ return;
+ }
+
+ if (mCleanedUp) {
+ NS_ASSERTION(!aNode, "Trying to focus cleaned up window!");
+ aNode = nullptr;
+ aNeedsFocus = false;
+ }
+ if (mFocusedNode != aNode) {
+ UpdateCanvasFocus(false, aNode);
+ mFocusedNode = aNode;
+ mFocusMethod = aFocusMethod & FOCUSMETHOD_MASK;
+ mShowFocusRingForContent = false;
+ }
+
+ if (mFocusedNode) {
+ // if a node was focused by a keypress, turn on focus rings for the
+ // window.
+ if (mFocusMethod & nsIFocusManager::FLAG_BYKEY) {
+ mFocusByKeyOccurred = true;
+ } else if (
+ // otherwise, we set mShowFocusRingForContent, as we don't want this to
+ // be permanent for the window. On Windows, focus rings are only shown
+ // when the FLAG_SHOWRING flag is used. On other platforms, focus rings
+ // are only visible on some elements.
+#ifndef XP_WIN
+ !(mFocusMethod & nsIFocusManager::FLAG_BYMOUSE) ||
+ ShouldShowFocusRingIfFocusedByMouse(aNode) ||
+#endif
+ aFocusMethod & nsIFocusManager::FLAG_SHOWRING) {
+ mShowFocusRingForContent = true;
+ }
+ }
+
+ if (aNeedsFocus)
+ mNeedsFocus = aNeedsFocus;
+}
+
+uint32_t
+nsGlobalWindow::GetFocusMethod()
+{
+ FORWARD_TO_INNER(GetFocusMethod, (), 0);
+
+ return mFocusMethod;
+}
+
+bool
+nsGlobalWindow::ShouldShowFocusRing()
+{
+ FORWARD_TO_INNER(ShouldShowFocusRing, (), false);
+
+ if (mShowFocusRingForContent || mFocusByKeyOccurred) {
+ return true;
+ }
+
+ nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
+ return root ? root->ShowFocusRings() : false;
+}
+
+void
+nsGlobalWindow::SetKeyboardIndicators(UIStateChangeType aShowAccelerators,
+ UIStateChangeType aShowFocusRings)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ nsPIDOMWindowOuter* piWin = GetPrivateRoot();
+ if (!piWin) {
+ return;
+ }
+
+ MOZ_ASSERT(piWin == AsOuter());
+
+ bool oldShouldShowFocusRing = ShouldShowFocusRing();
+
+ // only change the flags that have been modified
+ nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(mChromeEventHandler);
+ if (!windowRoot) {
+ return;
+ }
+
+ if (aShowAccelerators != UIStateChangeType_NoChange) {
+ windowRoot->SetShowAccelerators(aShowAccelerators == UIStateChangeType_Set);
+ }
+ if (aShowFocusRings != UIStateChangeType_NoChange) {
+ windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set);
+ }
+
+ nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(GetOuterWindow(),
+ aShowAccelerators,
+ aShowFocusRings);
+
+ bool newShouldShowFocusRing = ShouldShowFocusRing();
+ if (mHasFocus && mFocusedNode &&
+ oldShouldShowFocusRing != newShouldShowFocusRing &&
+ mFocusedNode->IsElement()) {
+ // Update mFocusedNode's state.
+ if (newShouldShowFocusRing) {
+ mFocusedNode->AsElement()->AddStates(NS_EVENT_STATE_FOCUSRING);
+ } else {
+ mFocusedNode->AsElement()->RemoveStates(NS_EVENT_STATE_FOCUSRING);
+ }
+ }
+}
+
+bool
+nsGlobalWindow::TakeFocus(bool aFocus, uint32_t aFocusMethod)
+{
+ FORWARD_TO_INNER(TakeFocus, (aFocus, aFocusMethod), false);
+
+ if (mCleanedUp) {
+ return false;
+ }
+
+ if (aFocus)
+ mFocusMethod = aFocusMethod & FOCUSMETHOD_MASK;
+
+ if (mHasFocus != aFocus) {
+ mHasFocus = aFocus;
+ UpdateCanvasFocus(true, mFocusedNode);
+ }
+
+ // 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
+nsGlobalWindow::SetReadyForFocus()
+{
+ FORWARD_TO_INNER_VOID(SetReadyForFocus, ());
+
+ bool oldNeedsFocus = mNeedsFocus;
+ mNeedsFocus = false;
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->WindowShown(GetOuterWindow(), oldNeedsFocus);
+ }
+}
+
+void
+nsGlobalWindow::PageHidden()
+{
+ FORWARD_TO_INNER_VOID(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.
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->WindowHidden(GetOuterWindow());
+ }
+
+ mNeedsFocus = true;
+}
+
+class HashchangeCallback : public Runnable
+{
+public:
+ HashchangeCallback(const nsAString &aOldURL,
+ const nsAString &aNewURL,
+ nsGlobalWindow* aWindow)
+ : mWindow(aWindow)
+ {
+ MOZ_ASSERT(mWindow);
+ MOZ_ASSERT(mWindow->IsInnerWindow());
+ mOldURL.Assign(aOldURL);
+ mNewURL.Assign(aNewURL);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ NS_PRECONDITION(NS_IsMainThread(), "Should be called on the main thread.");
+ return mWindow->FireHashchange(mOldURL, mNewURL);
+ }
+
+private:
+ nsString mOldURL;
+ nsString mNewURL;
+ RefPtr<nsGlobalWindow> mWindow;
+};
+
+nsresult
+nsGlobalWindow::DispatchAsyncHashchange(nsIURI *aOldURI, nsIURI *aNewURI)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ // 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 NS_DispatchToMainThread(callback);
+}
+
+nsresult
+nsGlobalWindow::FireHashchange(const nsAString &aOldURL,
+ const nsAString &aNewURL)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ // 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(AsInner()->IsCurrentInnerWindow());
+
+ nsIPresShell *shell = mDoc->GetShell();
+ RefPtr<nsPresContext> presContext;
+ if (shell) {
+ presContext = shell->GetPresContext();
+ }
+
+ HashChangeEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = false;
+ init.mNewURL = aNewURL;
+ init.mOldURL = aOldURL;
+
+ RefPtr<HashChangeEvent> event =
+ HashChangeEvent::Constructor(this, NS_LITERAL_STRING("hashchange"),
+ init);
+
+ event->SetTrusted(true);
+
+ bool dummy;
+ return DispatchEvent(event, &dummy);
+}
+
+nsresult
+nsGlobalWindow::DispatchSyncPopState()
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Must be safe to run script here.");
+
+ nsresult rv = NS_OK;
+
+ // Bail if the window is frozen.
+ if (IsFrozen()) {
+ return NS_OK;
+ }
+
+ // 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.
+ nsCOMPtr<nsIVariant> stateObj;
+ rv = mDoc->GetStateObject(getter_AddRefs(stateObj));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Obtain a presentation shell for use in creating a popstate event.
+ nsIPresShell *shell = mDoc->GetShell();
+ RefPtr<nsPresContext> presContext;
+ if (shell) {
+ presContext = shell->GetPresContext();
+ }
+
+ bool result = true;
+ AutoJSAPI jsapi;
+ result = jsapi.Init(AsInner());
+ NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> stateJSValue(cx, JS::NullValue());
+ result = stateObj ? VariantToJsval(cx, stateObj, &stateJSValue) : true;
+ NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
+
+ RootedDictionary<PopStateEventInit> init(cx);
+ init.mBubbles = true;
+ init.mCancelable = false;
+ init.mState = stateJSValue;
+
+ RefPtr<PopStateEvent> event =
+ PopStateEvent::Constructor(this, NS_LITERAL_STRING("popstate"),
+ init);
+ event->SetTrusted(true);
+ event->SetTarget(this);
+
+ bool dummy; // default action
+ return DispatchEvent(event, &dummy);
+}
+
+// Find an nsICanvasFrame under aFrame. Only search the principal
+// child lists. aFrame must be non-null.
+static nsCanvasFrame* FindCanvasFrame(nsIFrame* aFrame)
+{
+ nsCanvasFrame* canvasFrame = do_QueryFrame(aFrame);
+ if (canvasFrame) {
+ return canvasFrame;
+ }
+
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ canvasFrame = FindCanvasFrame(kid);
+ if (canvasFrame) {
+ return canvasFrame;
+ }
+ }
+
+ return nullptr;
+}
+
+//-------------------------------------------------------
+// Tells the HTMLFrame/CanvasFrame that is now has focus
+void
+nsGlobalWindow::UpdateCanvasFocus(bool aFocusChanged, nsIContent* aNewContent)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ // this is called from the inner window so use GetDocShell
+ nsIDocShell* docShell = GetDocShell();
+ if (!docShell)
+ return;
+
+ bool editable;
+ docShell->GetEditable(&editable);
+ if (editable)
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ if (!presShell || !mDoc)
+ return;
+
+ Element *rootElement = mDoc->GetRootElement();
+ if (rootElement) {
+ if ((mHasFocus || aFocusChanged) &&
+ (mFocusedNode == rootElement || aNewContent == rootElement)) {
+ nsIFrame* frame = rootElement->GetPrimaryFrame();
+ if (frame) {
+ frame = frame->GetParent();
+ nsCanvasFrame* canvasFrame = do_QueryFrame(frame);
+ if (canvasFrame) {
+ canvasFrame->SetHasFocus(mHasFocus && rootElement == aNewContent);
+ }
+ }
+ }
+ } else {
+ // Look for the frame the hard way
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (frame) {
+ nsCanvasFrame* canvasFrame = FindCanvasFrame(frame);
+ if (canvasFrame) {
+ canvasFrame->SetHasFocus(false);
+ }
+ }
+ }
+}
+
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindow::GetComputedStyle(Element& aElt, const nsAString& aPseudoElt,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ return GetComputedStyleHelper(aElt, aPseudoElt, false, aError);
+}
+
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindow::GetDefaultComputedStyle(Element& aElt,
+ const nsAString& aPseudoElt,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ return GetComputedStyleHelper(aElt, aPseudoElt, true, aError);
+}
+
+nsresult
+nsGlobalWindow::GetComputedStyleHelper(nsIDOMElement* aElt,
+ const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly,
+ nsIDOMCSSStyleDeclaration** aReturn)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ NS_ENSURE_ARG_POINTER(aReturn);
+ *aReturn = nullptr;
+
+ nsCOMPtr<dom::Element> element = do_QueryInterface(aElt);
+ if (!element) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ ErrorResult rv;
+ nsCOMPtr<nsIDOMCSSStyleDeclaration> declaration =
+ GetComputedStyleHelper(*element, aPseudoElt, aDefaultStylesOnly, rv);
+ declaration.forget(aReturn);
+
+ return rv.StealNSResult();
+}
+
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindow::GetComputedStyleHelperOuter(Element& aElt,
+ const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+
+ if (!presShell) {
+ // Try flushing frames on our parent in case there's a pending
+ // style change that will create the presshell.
+ auto* parent = nsGlobalWindow::Cast(GetPrivateParent());
+ if (!parent) {
+ return nullptr;
+ }
+
+ parent->FlushPendingNotifications(Flush_Frames);
+
+ // Might have killed mDocShell
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ }
+
+ RefPtr<nsComputedDOMStyle> compStyle =
+ NS_NewComputedDOMStyle(&aElt, aPseudoElt, presShell,
+ aDefaultStylesOnly ? nsComputedDOMStyle::eDefaultOnly :
+ nsComputedDOMStyle::eAll);
+
+ return compStyle.forget();
+}
+
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindow::GetComputedStyleHelper(Element& aElt,
+ const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetComputedStyleHelperOuter,
+ (aElt, aPseudoElt, aDefaultStylesOnly),
+ aError, nullptr);
+}
+
+DOMStorage*
+nsGlobalWindow::GetSessionStorage(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ nsIPrincipal *principal = GetPrincipal();
+ nsIDocShell* docShell = GetDocShell();
+
+ if (!principal || !docShell || !Preferences::GetBool(kStorageEnabled)) {
+ return nullptr;
+ }
+
+ if (mSessionStorage) {
+ if (MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) {
+ PR_LogPrint("nsGlobalWindow %p has %p sessionStorage", this, mSessionStorage.get());
+ }
+ bool canAccess = mSessionStorage->CanAccess(principal);
+ NS_ASSERTION(canAccess,
+ "This window owned sessionStorage "
+ "that could not be accessed!");
+ if (!canAccess) {
+ mSessionStorage = nullptr;
+ }
+ }
+
+ if (!mSessionStorage) {
+ nsString documentURI;
+ if (mDoc) {
+ aError = mDoc->GetDocumentURI(documentURI);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+
+ // If the document has the sandboxed origin flag set
+ // don't allow access to sessionStorage.
+ if (!mDoc) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ if (mDoc->GetSandboxFlags() & SANDBOXED_ORIGIN) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(docShell, &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDOMStorage> storage;
+ aError = storageManager->CreateStorage(AsInner(), principal, documentURI,
+ IsPrivateBrowsing(),
+ getter_AddRefs(storage));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ mSessionStorage = static_cast<DOMStorage*>(storage.get());
+ MOZ_ASSERT(mSessionStorage);
+
+ if (MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) {
+ PR_LogPrint("nsGlobalWindow %p tried to get a new sessionStorage %p", this, mSessionStorage.get());
+ }
+
+ if (!mSessionStorage) {
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+ }
+
+ if (MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) {
+ PR_LogPrint("nsGlobalWindow %p returns %p sessionStorage", this, mSessionStorage.get());
+ }
+
+ return mSessionStorage;
+}
+
+DOMStorage*
+nsGlobalWindow::GetLocalStorage(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!Preferences::GetBool(kStorageEnabled)) {
+ return nullptr;
+ }
+
+ if (!mLocalStorage) {
+ if (nsContentUtils::StorageAllowedForWindow(AsInner()) ==
+ nsContentUtils::StorageAccess::eDeny) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsIPrincipal *principal = GetPrincipal();
+ if (!principal) {
+ return nullptr;
+ }
+
+ 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;
+ }
+ }
+
+ nsCOMPtr<nsIDOMStorage> storage;
+ aError = storageManager->CreateStorage(AsInner(), principal, documentURI,
+ IsPrivateBrowsing(),
+ getter_AddRefs(storage));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ mLocalStorage = static_cast<DOMStorage*>(storage.get());
+ MOZ_ASSERT(mLocalStorage);
+ }
+
+ return mLocalStorage;
+}
+
+IDBFactory*
+nsGlobalWindow::GetIndexedDB(ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+ if (!mIndexedDB) {
+ // This may keep mIndexedDB null without setting an error.
+ aError = IDBFactory::CreateForWindow(AsInner(),
+ getter_AddRefs(mIndexedDB));
+ }
+
+ return mIndexedDB;
+}
+
+//*****************************************************************************
+// nsGlobalWindow::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsGlobalWindow::GetInterface(const nsIID & aIID, void **aSink)
+{
+ NS_ENSURE_ARG_POINTER(aSink);
+ *aSink = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsIDocCharset))) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
+
+ NS_WARNING("Using deprecated nsIDocCharset: use nsIDocShell.GetCharset() instead ");
+ nsCOMPtr<nsIDocCharset> docCharset(do_QueryInterface(outer->mDocShell));
+ docCharset.forget(aSink);
+ }
+ else if (aIID.Equals(NS_GET_IID(nsIWebNavigation))) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(outer->mDocShell));
+ webNav.forget(aSink);
+ }
+ else if (aIID.Equals(NS_GET_IID(nsIDocShell))) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIDocShell> docShell = outer->mDocShell;
+ docShell.forget(aSink);
+ }
+#ifdef NS_PRINTING
+ else if (aIID.Equals(NS_GET_IID(nsIWebBrowserPrint))) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
+
+ if (outer->mDocShell) {
+ nsCOMPtr<nsIContentViewer> viewer;
+ outer->mDocShell->GetContentViewer(getter_AddRefs(viewer));
+ if (viewer) {
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint(do_QueryInterface(viewer));
+ webBrowserPrint.forget(aSink);
+ }
+ }
+ }
+#endif
+ else if (aIID.Equals(NS_GET_IID(nsIDOMWindowUtils))) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
+
+ if (!mWindowUtils) {
+ mWindowUtils = new nsDOMWindowUtils(outer);
+ }
+
+ *aSink = mWindowUtils;
+ NS_ADDREF(((nsISupports *) *aSink));
+ }
+ else if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(outer->mDocShell));
+ loadContext.forget(aSink);
+ }
+ else {
+ return QueryInterface(aIID, aSink);
+ }
+
+ return *aSink ? NS_OK : NS_ERROR_NO_INTERFACE;
+}
+
+void
+nsGlobalWindow::GetInterface(JSContext* aCx, nsIJSID* aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ dom::GetInterface(aCx, this, aIID, aRetval, aError);
+}
+
+already_AddRefed<CacheStorage>
+nsGlobalWindow::GetCaches(ErrorResult& aRv)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ if (!mCacheStorage) {
+ bool forceTrustedOrigin =
+ GetOuterWindow()->GetServiceWorkersTestingEnabled();
+
+ nsContentUtils::StorageAccess access =
+ nsContentUtils::StorageAllowedForWindow(AsInner());
+
+ // We don't block the cache API when being told to only allow storage for the
+ // current session.
+ bool storageBlocked = access <= nsContentUtils::StorageAccess::ePrivateBrowsing;
+
+ mCacheStorage = CacheStorage::CreateOnMainThread(cache::DEFAULT_NAMESPACE,
+ this, GetPrincipal(),
+ storageBlocked,
+ forceTrustedOrigin, aRv);
+ }
+
+ RefPtr<CacheStorage> ref = mCacheStorage;
+ return ref.forget();
+}
+
+already_AddRefed<ServiceWorkerRegistration>
+nsPIDOMWindowInner::GetServiceWorkerRegistration(const nsAString& aScope)
+{
+ RefPtr<ServiceWorkerRegistration> registration;
+ if (!mServiceWorkerRegistrationTable.Get(aScope,
+ getter_AddRefs(registration))) {
+ registration =
+ ServiceWorkerRegistration::CreateForMainThread(this, aScope);
+ mServiceWorkerRegistrationTable.Put(aScope, registration);
+ }
+ return registration.forget();
+}
+
+void
+nsPIDOMWindowInner::InvalidateServiceWorkerRegistration(const nsAString& aScope)
+{
+ mServiceWorkerRegistrationTable.Remove(aScope);
+}
+
+void
+nsGlobalWindow::FireOfflineStatusEventIfChanged()
+{
+ if (!AsInner()->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");
+ }
+ // The event is fired at the body element, or if there is no body element,
+ // at the document.
+ nsCOMPtr<EventTarget> eventTarget = mDoc.get();
+ nsHTMLDocument* htmlDoc = mDoc->AsHTMLDocument();
+ if (htmlDoc) {
+ Element* body = htmlDoc->GetBody();
+ if (body) {
+ eventTarget = body;
+ }
+ } else {
+ Element* documentElement = mDoc->GetDocumentElement();
+ if (documentElement) {
+ eventTarget = documentElement;
+ }
+ }
+ nsContentUtils::DispatchTrustedEvent(mDoc, eventTarget, name, true, false);
+}
+
+class NotifyIdleObserverRunnable : public Runnable
+{
+public:
+ NotifyIdleObserverRunnable(nsIIdleObserver* aIdleObserver,
+ uint32_t aTimeInS,
+ bool aCallOnidle,
+ nsGlobalWindow* aIdleWindow)
+ : mIdleObserver(aIdleObserver), mTimeInS(aTimeInS), mIdleWindow(aIdleWindow),
+ mCallOnidle(aCallOnidle)
+ { }
+
+ NS_IMETHOD Run() override
+ {
+ if (mIdleWindow->ContainsIdleObserver(mIdleObserver, mTimeInS)) {
+ return mCallOnidle ? mIdleObserver->Onidle() : mIdleObserver->Onactive();
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIIdleObserver> mIdleObserver;
+ uint32_t mTimeInS;
+ RefPtr<nsGlobalWindow> mIdleWindow;
+
+ // If false then call on active
+ bool mCallOnidle;
+};
+
+void
+nsGlobalWindow::NotifyIdleObserver(IdleObserverHolder* aIdleObserverHolder,
+ bool aCallOnidle)
+{
+ MOZ_ASSERT(aIdleObserverHolder);
+ aIdleObserverHolder->mPrevNotificationIdle = aCallOnidle;
+
+ nsCOMPtr<nsIRunnable> caller =
+ new NotifyIdleObserverRunnable(aIdleObserverHolder->mIdleObserver,
+ aIdleObserverHolder->mTimeInS,
+ aCallOnidle, this);
+ if (NS_FAILED(NS_DispatchToCurrentThread(caller))) {
+ NS_WARNING("Failed to dispatch thread for idle observer notification.");
+ }
+}
+
+bool
+nsGlobalWindow::ContainsIdleObserver(nsIIdleObserver* aIdleObserver, uint32_t aTimeInS)
+{
+ MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated.");
+ bool found = false;
+ nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers);
+ while (iter.HasMore()) {
+ IdleObserverHolder& idleObserver = iter.GetNext();
+ if (idleObserver.mIdleObserver == aIdleObserver &&
+ idleObserver.mTimeInS == aTimeInS) {
+ found = true;
+ break;
+ }
+ }
+ return found;
+}
+
+void
+IdleActiveTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ RefPtr<nsGlobalWindow> idleWindow = static_cast<nsGlobalWindow*>(aClosure);
+ MOZ_ASSERT(idleWindow, "Idle window has not been instantiated.");
+ idleWindow->HandleIdleActiveEvent();
+}
+
+void
+IdleObserverTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ RefPtr<nsGlobalWindow> idleWindow = static_cast<nsGlobalWindow*>(aClosure);
+ MOZ_ASSERT(idleWindow, "Idle window has not been instantiated.");
+ idleWindow->HandleIdleObserverCallback();
+}
+
+void
+nsGlobalWindow::HandleIdleObserverCallback()
+{
+ MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!");
+ MOZ_ASSERT(static_cast<uint32_t>(mIdleCallbackIndex) < mIdleObservers.Length(),
+ "Idle callback index exceeds array bounds!");
+ IdleObserverHolder& idleObserver = mIdleObservers.ElementAt(mIdleCallbackIndex);
+ NotifyIdleObserver(&idleObserver, true);
+ mIdleCallbackIndex++;
+ if (NS_FAILED(ScheduleNextIdleObserverCallback())) {
+ NS_WARNING("Failed to set next idle observer callback.");
+ }
+}
+
+nsresult
+nsGlobalWindow::ScheduleNextIdleObserverCallback()
+{
+ MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!");
+ MOZ_ASSERT(mIdleService, "No idle service!");
+
+ if (mIdleCallbackIndex < 0 ||
+ static_cast<uint32_t>(mIdleCallbackIndex) >= mIdleObservers.Length()) {
+ return NS_OK;
+ }
+
+ IdleObserverHolder& idleObserver =
+ mIdleObservers.ElementAt(mIdleCallbackIndex);
+
+ uint32_t userIdleTimeMS = 0;
+ nsresult rv = mIdleService->GetIdleTime(&userIdleTimeMS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t callbackTimeMS = 0;
+ if (idleObserver.mTimeInS * 1000 + mIdleFuzzFactor > userIdleTimeMS) {
+ callbackTimeMS = idleObserver.mTimeInS * 1000 - userIdleTimeMS + mIdleFuzzFactor;
+ }
+
+ mIdleTimer->Cancel();
+ rv = mIdleTimer->InitWithFuncCallback(IdleObserverTimerCallback,
+ this,
+ callbackTimeMS,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+uint32_t
+nsGlobalWindow::GetFuzzTimeMS()
+{
+ MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!");
+
+ if (sIdleObserversAPIFuzzTimeDisabled) {
+ return 0;
+ }
+
+ uint32_t randNum = MAX_IDLE_FUZZ_TIME_MS;
+ size_t nbytes = PR_GetRandomNoise(&randNum, sizeof(randNum));
+ if (nbytes != sizeof(randNum)) {
+ NS_WARNING("PR_GetRandomNoise(...) Not implemented or no available noise!");
+ return MAX_IDLE_FUZZ_TIME_MS;
+ }
+
+ if (randNum > MAX_IDLE_FUZZ_TIME_MS) {
+ randNum %= MAX_IDLE_FUZZ_TIME_MS;
+ }
+
+ return randNum;
+}
+
+nsresult
+nsGlobalWindow::ScheduleActiveTimerCallback()
+{
+ MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!");
+
+ if (!mAddActiveEventFuzzTime) {
+ return HandleIdleActiveEvent();
+ }
+
+ MOZ_ASSERT(mIdleTimer);
+ mIdleTimer->Cancel();
+
+ uint32_t fuzzFactorInMS = GetFuzzTimeMS();
+ nsresult rv = mIdleTimer->InitWithFuncCallback(IdleActiveTimerCallback,
+ this,
+ fuzzFactorInMS,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+nsGlobalWindow::HandleIdleActiveEvent()
+{
+ MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!");
+
+ if (mCurrentlyIdle) {
+ mIdleCallbackIndex = 0;
+ mIdleFuzzFactor = GetFuzzTimeMS();
+ nsresult rv = ScheduleNextIdleObserverCallback();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ mIdleCallbackIndex = -1;
+ MOZ_ASSERT(mIdleTimer);
+ mIdleTimer->Cancel();
+ nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers);
+ while (iter.HasMore()) {
+ IdleObserverHolder& idleObserver = iter.GetNext();
+ if (idleObserver.mPrevNotificationIdle) {
+ NotifyIdleObserver(&idleObserver, false);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsGlobalWindow::SlowScriptResponse
+nsGlobalWindow::ShowSlowScriptDialog()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsresult rv;
+ AutoJSContext cx;
+
+ 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_ReportWarningASCII(cx, "A long running script was terminated");
+ return KillSlowScript;
+ }
+
+ // If our document is not active, just kill the script: we've been unloaded
+ if (!AsInner()->HasActiveDocument()) {
+ return KillSlowScript;
+ }
+
+ // Check if we should offer the option to debug
+ JS::AutoFilename filename;
+ unsigned lineno;
+ bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, &lineno);
+
+ // 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;
+
+ if (XRE_IsContentProcess() &&
+ ProcessHangMonitor::Get()) {
+ ProcessHangMonitor::SlowScriptAction action;
+ RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
+ nsIDocShell* docShell = GetDocShell();
+ nsCOMPtr<nsITabChild> child = docShell ? docShell->GetTabChild() : nullptr;
+ action = monitor->NotifySlowScript(child,
+ filename.get(),
+ lineno);
+ 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<nsGlobalWindow> outer = GetOuterWindowInternal();
+ outer->EnterModalState();
+ while (!monitor->IsDebuggerStartupComplete()) {
+ NS_ProcessNextEvent(nullptr, true);
+ }
+ 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 showDebugButton = !!debugCallback;
+
+ // Get localizable strings
+ nsXPIDLString title, msg, stopButton, waitButton, debugButton, neverShowDlg;
+
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "KillScriptTitle",
+ title);
+
+ nsresult tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "StopScriptButton",
+ stopButton);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "WaitForScriptButton",
+ waitButton);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "DontAskAgain",
+ neverShowDlg);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+
+ if (showDebugButton) {
+ tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "DebugScriptButton",
+ debugButton);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "KillScriptWithDebugMessage",
+ msg);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+ else {
+ tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "KillScriptMessage",
+ msg);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ // GetStringFromName can return NS_OK and still give nullptr string
+ if (NS_FAILED(rv) || !title || !msg || !stopButton || !waitButton ||
+ (!debugButton && showDebugButton) || !neverShowDlg) {
+ NS_ERROR("Failed to get localized strings.");
+ return ContinueSlowScript;
+ }
+
+ // Append file and line number information, if available
+ if (filename.get()) {
+ nsXPIDLString 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.Replace(cutStart, cutLength, NS_LITERAL_STRING(u"\x2026"));
+ }
+ const char16_t *formatParams[] = { filenameUTF16.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "KillScriptLocation",
+ formatParams,
+ scriptLocation);
+
+ if (NS_SUCCEEDED(rv) && scriptLocation) {
+ msg.AppendLiteral("\n\n");
+ msg.Append(scriptLocation);
+ msg.Append(':');
+ msg.AppendInt(lineno);
+ }
+ }
+
+ int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
+ bool neverShowDlgChk = false;
+ 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;
+
+ // Null out the operation callback while we're re-entering JS here.
+ bool old = JS_DisableInterruptCallback(cx);
+
+ // Open the dialog.
+ rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
+ debugButton, neverShowDlg, &neverShowDlgChk,
+ &buttonPressed);
+
+ JS_ResetInterruptCallback(cx, old);
+
+ if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
+ return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
+ }
+ if (buttonPressed == 2) {
+ if (debugCallback) {
+ rv = debugCallback->HandleSlowScriptDebug(this);
+ return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
+ }
+ }
+ JS_ClearPendingException(cx);
+ return KillSlowScript;
+}
+
+uint32_t
+nsGlobalWindow::FindInsertionIndex(IdleObserverHolder* aIdleObserver)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated.");
+
+ uint32_t i = 0;
+ nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers);
+ while (iter.HasMore()) {
+ IdleObserverHolder& idleObserver = iter.GetNext();
+ if (idleObserver.mTimeInS > aIdleObserver->mTimeInS) {
+ break;
+ }
+ i++;
+ MOZ_ASSERT(i <= mIdleObservers.Length(), "Array index out of bounds error.");
+ }
+
+ return i;
+}
+
+nsresult
+nsGlobalWindow::RegisterIdleObserver(nsIIdleObserver* aIdleObserver)
+{
+ MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!");
+
+ nsresult rv;
+ if (mIdleObservers.IsEmpty()) {
+ mIdleService = do_GetService("@mozilla.org/widget/idleservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mIdleService->AddIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mIdleTimer) {
+ mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mIdleTimer->Cancel();
+ }
+ }
+
+ MOZ_ASSERT(mIdleService);
+ MOZ_ASSERT(mIdleTimer);
+
+ IdleObserverHolder tmpIdleObserver;
+ tmpIdleObserver.mIdleObserver = aIdleObserver;
+ rv = aIdleObserver->GetTime(&tmpIdleObserver.mTimeInS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG_MAX(tmpIdleObserver.mTimeInS, UINT32_MAX / 1000);
+ NS_ENSURE_ARG_MIN(tmpIdleObserver.mTimeInS, MIN_IDLE_NOTIFICATION_TIME_S);
+
+ uint32_t insertAtIndex = FindInsertionIndex(&tmpIdleObserver);
+ if (insertAtIndex == mIdleObservers.Length()) {
+ mIdleObservers.AppendElement(tmpIdleObserver);
+ }
+ else {
+ mIdleObservers.InsertElementAt(insertAtIndex, tmpIdleObserver);
+ }
+
+ bool userIsIdle = false;
+ rv = nsContentUtils::IsUserIdle(MIN_IDLE_NOTIFICATION_TIME_S, &userIsIdle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Special case. First idle observer added to empty list while the user is idle.
+ // Haven't received 'idle' topic notification from slow idle service yet.
+ // Need to wait for the idle notification and then notify idle observers in the list.
+ if (userIsIdle && mIdleCallbackIndex == -1) {
+ return NS_OK;
+ }
+
+ if (!mCurrentlyIdle) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mIdleCallbackIndex >= 0);
+
+ if (static_cast<int32_t>(insertAtIndex) < mIdleCallbackIndex) {
+ IdleObserverHolder& idleObserver = mIdleObservers.ElementAt(insertAtIndex);
+ NotifyIdleObserver(&idleObserver, true);
+ mIdleCallbackIndex++;
+ return NS_OK;
+ }
+
+ if (static_cast<int32_t>(insertAtIndex) == mIdleCallbackIndex) {
+ mIdleTimer->Cancel();
+ rv = ScheduleNextIdleObserverCallback();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsGlobalWindow::FindIndexOfElementToRemove(nsIIdleObserver* aIdleObserver,
+ int32_t* aRemoveElementIndex)
+{
+ MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!");
+ MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated.");
+
+ *aRemoveElementIndex = 0;
+ if (mIdleObservers.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t aIdleObserverTimeInS;
+ nsresult rv = aIdleObserver->GetTime(&aIdleObserverTimeInS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG_MIN(aIdleObserverTimeInS, MIN_IDLE_NOTIFICATION_TIME_S);
+
+ nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers);
+ while (iter.HasMore()) {
+ IdleObserverHolder& idleObserver = iter.GetNext();
+ if (idleObserver.mTimeInS == aIdleObserverTimeInS &&
+ idleObserver.mIdleObserver == aIdleObserver ) {
+ break;
+ }
+ (*aRemoveElementIndex)++;
+ }
+ return static_cast<uint32_t>(*aRemoveElementIndex) >= mIdleObservers.Length() ?
+ NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult
+nsGlobalWindow::UnregisterIdleObserver(nsIIdleObserver* aIdleObserver)
+{
+ MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!");
+
+ int32_t removeElementIndex;
+ nsresult rv = FindIndexOfElementToRemove(aIdleObserver, &removeElementIndex);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Idle observer not found in list of idle observers. No idle observer removed.");
+ return NS_OK;
+ }
+ mIdleObservers.RemoveElementAt(removeElementIndex);
+
+ MOZ_ASSERT(mIdleTimer);
+ if (mIdleObservers.IsEmpty() && mIdleService) {
+ rv = mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mIdleService = nullptr;
+
+ mIdleTimer->Cancel();
+ mIdleCallbackIndex = -1;
+ return NS_OK;
+ }
+
+ if (!mCurrentlyIdle) {
+ return NS_OK;
+ }
+
+ if (removeElementIndex < mIdleCallbackIndex) {
+ mIdleCallbackIndex--;
+ return NS_OK;
+ }
+
+ if (removeElementIndex != mIdleCallbackIndex) {
+ return NS_OK;
+ }
+
+ mIdleTimer->Cancel();
+
+ // If the last element in the array had been notified then decrement
+ // mIdleCallbackIndex because an idle was removed from the list of
+ // idle observers.
+ // Example: add idle observer with time 1, 2, 3,
+ // Idle notifications for idle observers with time 1, 2, 3 are complete
+ // Remove idle observer with time 3 while the user is still idle.
+ // The user never transitioned to active state.
+ // Add an idle observer with idle time 4
+ if (static_cast<uint32_t>(mIdleCallbackIndex) == mIdleObservers.Length()) {
+ mIdleCallbackIndex--;
+ }
+ rv = ScheduleNextIdleObserverCallback();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsGlobalWindow::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, OBSERVER_TOPIC_IDLE)) {
+ mCurrentlyIdle = true;
+ if (IsFrozen()) {
+ // need to fire only one idle event while the window is frozen.
+ mNotifyIdleObserversIdleOnThaw = true;
+ mNotifyIdleObserversActiveOnThaw = false;
+ } else if (AsInner()->IsCurrentInnerWindow()) {
+ HandleIdleActiveEvent();
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_ACTIVE)) {
+ mCurrentlyIdle = false;
+ if (IsFrozen()) {
+ mNotifyIdleObserversActiveOnThaw = true;
+ mNotifyIdleObserversIdleOnThaw = false;
+ } else if (AsInner()->IsCurrentInnerWindow()) {
+ MOZ_ASSERT(IsInnerWindow());
+ ScheduleActiveTimerCallback();
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "dom-storage2-changed")) {
+ if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow()) {
+ return NS_OK;
+ }
+
+ nsIPrincipal *principal;
+ nsresult rv;
+
+ RefPtr<StorageEvent> event = static_cast<StorageEvent*>(aSubject);
+ if (!event) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DOMStorage> changingStorage = event->GetStorageArea();
+ if (!changingStorage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDOMStorage> istorage = changingStorage.get();
+
+ bool fireMozStorageChanged = false;
+ nsAutoString eventType;
+ eventType.AssignLiteral("storage");
+ principal = GetPrincipal();
+ if (!principal) {
+ return NS_OK;
+ }
+
+ if (changingStorage->IsPrivate() != IsPrivateBrowsing()) {
+ return NS_OK;
+ }
+
+ switch (changingStorage->GetType())
+ {
+ case DOMStorage::SessionStorage:
+ {
+ bool check = false;
+
+ nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell());
+ if (storageManager) {
+ rv = storageManager->CheckStorage(principal, istorage, &check);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ 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 NS_OK;
+ }
+
+ if (MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) {
+ PR_LogPrint("nsGlobalWindow %p with sessionStorage %p passing event from %p",
+ this, mSessionStorage.get(), changingStorage.get());
+ }
+
+ fireMozStorageChanged = mSessionStorage == changingStorage;
+ if (fireMozStorageChanged) {
+ eventType.AssignLiteral("MozSessionStorageChanged");
+ }
+ break;
+ }
+
+ case DOMStorage::LocalStorage:
+ {
+ // Allow event fire only for the same principal storages
+ // XXX We have to use EqualsIgnoreDomain after bug 495337 lands
+ nsIPrincipal* storagePrincipal = changingStorage->GetPrincipal();
+
+ bool equals = false;
+ rv = storagePrincipal->Equals(principal, &equals);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!equals)
+ return NS_OK;
+
+ fireMozStorageChanged = mLocalStorage == changingStorage;
+ if (fireMozStorageChanged) {
+ eventType.AssignLiteral("MozLocalStorageChanged");
+ }
+ break;
+ }
+ default:
+ return NS_OK;
+ }
+
+ // Clone the storage event included in the observer notification. We want
+ // to dispatch clones rather than the original event.
+ ErrorResult error;
+ RefPtr<StorageEvent> newEvent = CloneStorageEvent(eventType, event, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ newEvent->SetTrusted(true);
+
+ if (fireMozStorageChanged) {
+ WidgetEvent* internalEvent = newEvent->WidgetEventPtr();
+ internalEvent->mFlags.mOnlyChromeDispatch = true;
+ }
+
+ if (IsFrozen()) {
+ // This window is frozen, rather than firing the events here,
+ // store the domain in which the change happened and fire the
+ // events if we're ever thawed.
+
+ mPendingStorageEvents.AppendElement(newEvent);
+ return NS_OK;
+ }
+
+ bool defaultActionEnabled;
+ DispatchEvent(newEvent, &defaultActionEnabled);
+
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "offline-cache-update-added")) {
+ if (mApplicationCache)
+ return NS_OK;
+
+ // Instantiate the application object now. It observes update belonging to
+ // this window's document and correctly updates the applicationCache object
+ // state.
+ nsCOMPtr<nsIDOMOfflineResourceList> applicationCache = GetApplicationCache();
+ nsCOMPtr<nsIObserver> observer = do_QueryInterface(applicationCache);
+ if (observer)
+ observer->Observe(aSubject, aTopic, aData);
+
+ return NS_OK;
+ }
+
+#ifdef MOZ_B2G
+ if (!nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC) ||
+ !nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC)) {
+ MOZ_ASSERT(IsInnerWindow());
+ if (!AsInner()->IsCurrentInnerWindow()) {
+ return NS_OK;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ event->InitEvent(
+ !nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC)
+ ? NETWORK_UPLOAD_EVENT_NAME
+ : NETWORK_DOWNLOAD_EVENT_NAME,
+ false, false);
+ event->SetTrusted(true);
+
+ bool dummy;
+ return DispatchEvent(event, &dummy);
+ }
+#endif // MOZ_B2G
+
+ if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ MOZ_ASSERT(!NS_strcmp(aData, u"intl.accept_languages"));
+ MOZ_ASSERT(IsInnerWindow());
+
+ // 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) {
+ NavigatorBinding::ClearCachedLanguageValue(mNavigator);
+ NavigatorBinding::ClearCachedLanguagesValue(mNavigator);
+ }
+
+ // The event has to be dispatched only to the current inner window.
+ if (!AsInner()->IsCurrentInnerWindow()) {
+ return NS_OK;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+ event->InitEvent(NS_LITERAL_STRING("languagechange"), false, false);
+ event->SetTrusted(true);
+
+ bool dummy;
+ return DispatchEvent(event, &dummy);
+ }
+
+ NS_WARNING("unrecognized topic in nsGlobalWindow::Observe");
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<StorageEvent>
+nsGlobalWindow::CloneStorageEvent(const nsAString& aType,
+ const RefPtr<StorageEvent>& aEvent,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ 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<DOMStorage> storageArea = aEvent->GetStorageArea();
+ MOZ_ASSERT(storageArea);
+
+ RefPtr<DOMStorage> storage;
+ if (storageArea->GetType() == DOMStorage::LocalStorage) {
+ storage = GetLocalStorage(aRv);
+ } else {
+ MOZ_ASSERT(storageArea->GetType() == DOMStorage::SessionStorage);
+ storage = GetSessionStorage(aRv);
+ }
+
+ if (aRv.Failed() || !storage) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(storage);
+ MOZ_ASSERT(storage->IsForkOf(storageArea));
+
+ dict.mStorageArea = storage;
+
+ RefPtr<StorageEvent> event = StorageEvent::Constructor(this, aType, dict);
+ return event.forget();
+}
+
+void
+nsGlobalWindow::Suspend()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(IsInnerWindow());
+
+ // 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 (!AsInner()->IsCurrentInnerWindow()) {
+ return;
+ }
+
+ // All children are also suspended. This ensure mSuspendDepth is
+ // set properly and the timers are properly canceled for each child.
+ CallOnChildren(&nsGlobalWindow::Suspend);
+
+ mSuspendDepth += 1;
+ if (mSuspendDepth != 1) {
+ return;
+ }
+
+ 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();
+
+ mozilla::dom::workers::SuspendWorkersForWindow(AsInner());
+
+ for (Timeout* t = mTimeouts.getFirst(); t; t = t->getNext()) {
+ // Leave the timers with the current time remaining. This will
+ // cause the timers to potentially fire when the window is
+ // Resume()'d. Time effectively passes while suspended.
+
+ // Drop the XPCOM timer; we'll reschedule when restoring the state.
+ if (t->mTimer) {
+ t->mTimer->Cancel();
+ t->mTimer = nullptr;
+
+ // Drop the reference that the timer's closure had on this timeout, we'll
+ // add it back in Resume().
+ t->Release();
+ }
+ }
+
+ // Suspend all of the AudioContexts for this window
+ for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
+ ErrorResult dummy;
+ RefPtr<Promise> d = mAudioContexts[i]->Suspend(dummy);
+ }
+}
+
+void
+nsGlobalWindow::Resume()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(IsInnerWindow());
+
+ // 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 (!AsInner()->IsCurrentInnerWindow()) {
+ return;
+ }
+
+ // Resume all children. This restores timers recursively canceled
+ // in Suspend() and ensures all children have the correct mSuspendDepth.
+ CallOnChildren(&nsGlobalWindow::Resume);
+
+ MOZ_ASSERT(mSuspendDepth != 0);
+ 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) {
+ ErrorResult dummy;
+ RefPtr<Promise> d = mAudioContexts[i]->Resume(dummy);
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ DebugOnly<bool> _seenDummyTimeout = false;
+
+ for (Timeout* t = mTimeouts.getFirst(); t; t = t->getNext()) {
+ // There's a chance we're being called with RunTimeout on the stack in which
+ // case we have a dummy timeout in the list that *must not* be resumed. It
+ // can be identified by a null mWindow.
+ if (!t->mWindow) {
+ NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
+ _seenDummyTimeout = true;
+ continue;
+ }
+
+ MOZ_ASSERT(!t->mTimer);
+
+ // The timeout mWhen is set to the absolute time when the timer should
+ // fire. Recalculate the delay from now until that deadline. If the
+ // the deadline has already passed or falls within our minimum delay
+ // deadline, then clamp the resulting value to the minimum delay. The
+ // mWhen will remain at its absolute time, but we won't fire the OS
+ // timer until our calculated delay has passed.
+ int32_t remaining = 0;
+ if (t->mWhen > now) {
+ remaining = static_cast<int32_t>((t->mWhen - now).ToMilliseconds());
+ }
+ uint32_t delay = std::max(remaining, DOMMinTimeoutValue());
+
+ t->mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!t->mTimer) {
+ t->remove();
+ continue;
+ }
+
+ nsresult rv = t->InitTimer(GetThrottledEventQueue(), delay);
+ if (NS_FAILED(rv)) {
+ t->mTimer = nullptr;
+ t->remove();
+ continue;
+ }
+
+ // Add a reference for the new timer's closure.
+ t->AddRef();
+ }
+
+ // 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().
+ mozilla::dom::workers::ResumeWorkersForWindow(AsInner());
+}
+
+bool
+nsGlobalWindow::IsSuspended() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // No inner means we are effectively suspended
+ if (IsOuterWindow()) {
+ if (!mInnerWindow) {
+ return true;
+ }
+ return mInnerWindow->IsSuspended();
+ }
+ return mSuspendDepth != 0;
+}
+
+void
+nsGlobalWindow::Freeze()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ Suspend();
+ FreezeInternal();
+}
+
+void
+nsGlobalWindow::FreezeInternal()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(IsInnerWindow());
+ MOZ_DIAGNOSTIC_ASSERT(AsInner()->IsCurrentInnerWindow());
+ MOZ_DIAGNOSTIC_ASSERT(IsSuspended());
+
+ CallOnChildren(&nsGlobalWindow::FreezeInternal);
+
+ mFreezeDepth += 1;
+ MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
+ if (mFreezeDepth != 1) {
+ return;
+ }
+
+ mozilla::dom::workers::FreezeWorkersForWindow(AsInner());
+
+ TimeStamp now = TimeStamp::Now();
+ for (Timeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
+ // 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.
+ if (t->mWhen > now) {
+ t->mTimeRemaining = t->mWhen - now;
+ } else {
+ t->mTimeRemaining = TimeDuration(0);
+ }
+
+ // Since we are suspended there should be no OS timer set for
+ // this timeout entry.
+ MOZ_ASSERT(!t->mTimer);
+ }
+
+ NotifyDOMWindowFrozen(this);
+}
+
+void
+nsGlobalWindow::Thaw()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ ThawInternal();
+ Resume();
+}
+
+void
+nsGlobalWindow::ThawInternal()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(IsInnerWindow());
+ MOZ_DIAGNOSTIC_ASSERT(AsInner()->IsCurrentInnerWindow());
+ MOZ_DIAGNOSTIC_ASSERT(IsSuspended());
+
+ CallOnChildren(&nsGlobalWindow::ThawInternal);
+
+ MOZ_ASSERT(mFreezeDepth != 0);
+ mFreezeDepth -= 1;
+ MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
+ if (mFreezeDepth != 0) {
+ return;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ DebugOnly<bool> _seenDummyTimeout = false;
+
+ for (Timeout *t = mTimeouts.getFirst(); t; t = t->getNext()) {
+ // There's a chance we're being called with RunTimeout on the stack in which
+ // case we have a dummy timeout in the list that *must not* be resumed. It
+ // can be identified by a null mWindow.
+ if (!t->mWindow) {
+ NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
+ _seenDummyTimeout = true;
+ continue;
+ }
+
+ // Set mWhen back to the time when the timer is supposed to fire.
+ t->mWhen = now + t->mTimeRemaining;
+
+ MOZ_ASSERT(!t->mTimer);
+ }
+
+ mozilla::dom::workers::ThawWorkersForWindow(AsInner());
+
+ NotifyDOMWindowThawed(this);
+}
+
+bool
+nsGlobalWindow::IsFrozen() const
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // No inner means we are effectively frozen
+ if (IsOuterWindow()) {
+ if (!mInnerWindow) {
+ return true;
+ }
+ return mInnerWindow->IsFrozen();
+ }
+ bool frozen = mFreezeDepth != 0;
+ MOZ_ASSERT_IF(frozen, IsSuspended());
+ return frozen;
+}
+
+void
+nsGlobalWindow::SyncStateFromParentWindow()
+{
+ // This method should only be called on an inner window that has been
+ // assigned to an outer window already.
+ MOZ_ASSERT(IsInnerWindow());
+ MOZ_ASSERT(AsInner()->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;
+ nsGlobalWindow* parentInner =
+ parentOuter ? nsGlobalWindow::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();
+ }
+}
+
+template<typename Method>
+void
+nsGlobalWindow::CallOnChildren(Method aMethod)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsInnerWindow());
+ MOZ_ASSERT(AsInner()->IsCurrentInnerWindow());
+
+ nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ int32_t childCount = 0;
+ docShell->GetChildCount(&childCount);
+
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> childShell;
+ docShell->GetChildAt(i, getter_AddRefs(childShell));
+ NS_ASSERTION(childShell, "null child shell");
+
+ nsCOMPtr<nsPIDOMWindowOuter> pWin = childShell->GetWindow();
+ if (!pWin) {
+ continue;
+ }
+
+ auto* win = nsGlobalWindow::Cast(pWin);
+ nsGlobalWindow* inner = win->GetCurrentInnerWindowInternal();
+
+ // This is a bit hackish. Only freeze/suspend windows which are truly our
+ // subwindows.
+ nsCOMPtr<Element> frame = pWin->GetFrameElementInternal();
+ if (!mDoc || !frame || mDoc != frame->OwnerDoc() || !inner) {
+ continue;
+ }
+
+ (inner->*aMethod)();
+ }
+}
+
+nsresult
+nsGlobalWindow::FireDelayedDOMEvents()
+{
+ FORWARD_TO_INNER(FireDelayedDOMEvents, (), NS_ERROR_UNEXPECTED);
+
+ for (uint32_t i = 0, len = mPendingStorageEvents.Length(); i < len; ++i) {
+ Observe(mPendingStorageEvents[i], "dom-storage2-changed", nullptr);
+ }
+
+ if (mApplicationCache) {
+ static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents();
+ }
+
+ // Fires an offline status event if the offline status has changed
+ FireOfflineStatusEventIfChanged();
+
+ if (mNotifyIdleObserversIdleOnThaw) {
+ mNotifyIdleObserversIdleOnThaw = false;
+ HandleIdleActiveEvent();
+ }
+
+ if (mNotifyIdleObserversActiveOnThaw) {
+ mNotifyIdleObserversActiveOnThaw = false;
+ ScheduleActiveTimerCallback();
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+ if (docShell) {
+ int32_t childCount = 0;
+ docShell->GetChildCount(&childCount);
+
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> childShell;
+ docShell->GetChildAt(i, getter_AddRefs(childShell));
+ NS_ASSERTION(childShell, "null child shell");
+
+ if (nsCOMPtr<nsPIDOMWindowOuter> pWin = childShell->GetWindow()) {
+ auto* win = nsGlobalWindow::Cast(pWin);
+ win->FireDelayedDOMEvents();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsGlobalWindow: Window Control Functions
+//*****************************************************************************
+
+nsPIDOMWindowOuter*
+nsGlobalWindow::GetParentInternal()
+{
+ if (IsInnerWindow()) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (!outer) {
+ NS_WARNING("No outer window available!");
+ return nullptr;
+ }
+ return outer->GetParentInternal();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent();
+
+ if (parent && parent != AsOuter()) {
+ return parent;
+ }
+
+ return nullptr;
+}
+
+void
+nsGlobalWindow::UnblockScriptedClosing()
+{
+ MOZ_ASSERT(IsOuterWindow());
+ mBlockScriptedClosingFlag = false;
+}
+
+class AutoUnblockScriptClosing
+{
+private:
+ RefPtr<nsGlobalWindow> mWin;
+public:
+ explicit AutoUnblockScriptClosing(nsGlobalWindow* aWin)
+ : mWin(aWin)
+ {
+ MOZ_ASSERT(mWin);
+ MOZ_ASSERT(mWin->IsOuterWindow());
+ }
+ ~AutoUnblockScriptClosing()
+ {
+ void (nsGlobalWindow::*run)() = &nsGlobalWindow::UnblockScriptedClosing;
+ NS_DispatchToCurrentThread(NewRunnableMethod(mWin, run));
+ }
+};
+
+nsresult
+nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, bool aDialog,
+ bool aContentModal, bool aCalledNoScript,
+ bool aDoJSFixups, bool aNavigate,
+ nsIArray *argv,
+ nsISupports *aExtraArgument,
+ nsIDocShellLoadInfo* aLoadInfo,
+ bool aForceNoOpener,
+ nsPIDOMWindowOuter **aReturn)
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+#ifdef DEBUG
+ uint32_t argc = 0;
+ if (argv)
+ argv->GetLength(&argc);
+#endif
+ NS_PRECONDITION(!aExtraArgument || (!argv && argc == 0),
+ "Can't pass in arguments both ways");
+ NS_PRECONDITION(!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");
+
+ // Popups from apps are never blocked.
+ bool isApp = false;
+ if (mDoc) {
+ isApp = mDoc->NodePrincipal()->GetAppStatus() >=
+ nsIPrincipal::APP_STATUS_INSTALLED;
+ }
+
+ bool forceNoOpener = aForceNoOpener;
+ if (!forceNoOpener) {
+ // Unlike other window flags, "noopener" comes from splitting on commas with
+ // HTML whitespace trimming...
+ nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(
+ aOptions, ',');
+ while (tok.hasMoreTokens()) {
+ if (tok.nextToken().EqualsLiteral("noopener")) {
+ forceNoOpener = true;
+ break;
+ }
+ }
+ }
+
+ // 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() &&
+ !isApp && !aDialog && !WindowExists(aName, forceNoOpener, !aCalledNoScript);
+
+ // Note: it's very important that this be an nsXPIDLCString, since we want
+ // .get() on it to return nullptr until we write stuff to it. The window
+ // watcher expects a null URL string if there is no URL to load.
+ nsXPIDLCString url;
+ nsresult rv = NS_OK;
+
+ // 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.get() && !aDialog && aNavigate)
+ rv = SecurityCheckURL(url.get());
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ PopupControlState abuseLevel = gPopupControlState;
+ if (checkForPopup) {
+ abuseLevel = RevisePopupAbuseLevel(abuseLevel);
+ if (abuseLevel >= openAbused) {
+ 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->AsOuter()) {
+ mBlockScriptedClosingFlag = true;
+ closeUnblocker.emplace(this);
+ }
+ }
+
+ FireAbuseEvents(aUrl, aName, aOptions);
+ return aDoJSFixups ? NS_OK : NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domReturn;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(wwatch, rv);
+
+ NS_ConvertUTF16toUTF8 options(aOptions);
+ NS_ConvertUTF16toUTF8 name(aName);
+
+ const char *options_ptr = aOptions.IsEmpty() ? nullptr : options.get();
+ const char *name_ptr = aName.IsEmpty() ? nullptr : name.get();
+
+ nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
+ NS_ENSURE_STATE(pwwatch);
+
+ MOZ_ASSERT_IF(checkForPopup, abuseLevel < openAbused);
+ // At this point we should know for a fact that if checkForPopup then
+ // abuseLevel < openAbused, so we could just check for abuseLevel ==
+ // 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 >= openControlled);
+
+ {
+ // Reset popup state while opening a window to prevent the
+ // current state from being active the whole time a modal
+ // dialog is open.
+ nsAutoPopupStatePusher popupStatePusher(openAbused, true);
+
+ if (!aCalledNoScript) {
+ // We asserted at the top of this function that aNavigate is true for
+ // !aCalledNoScript.
+ rv = pwwatch->OpenWindow2(AsOuter(), url.get(), name_ptr,
+ options_ptr, /* aCalledFromScript = */ true,
+ aDialog, aNavigate, argv,
+ isPopupSpamWindow,
+ forceNoOpener,
+ aLoadInfo,
+ 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(AsOuter(), url.get(), name_ptr,
+ options_ptr, /* aCalledFromScript = */ false,
+ aDialog, aNavigate, aExtraArgument,
+ isPopupSpamWindow,
+ forceNoOpener,
+ aLoadInfo,
+ getter_AddRefs(domReturn));
+
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // success!
+
+ NS_ENSURE_TRUE(domReturn, NS_OK);
+ nsCOMPtr<nsPIDOMWindowOuter> outerReturn =
+ nsPIDOMWindowOuter::From(domReturn);
+ outerReturn.swap(*aReturn);
+
+ if (aDoJSFixups) {
+ nsCOMPtr<nsIDOMChromeWindow> chrome_win(do_QueryInterface(*aReturn));
+ 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.
+ nsCOMPtr<nsIDocument> doc = (*aReturn)->GetDoc();
+ Unused << doc;
+ }
+ }
+
+ return rv;
+}
+
+//*****************************************************************************
+// nsGlobalWindow: Timeout Functions
+//*****************************************************************************
+
+uint32_t sNestingLevel;
+
+uint32_t
+nsGlobalWindow::GetTimeoutId(Timeout::Reason aReason)
+{
+ switch (aReason) {
+ case Timeout::Reason::eIdleCallbackTimeout:
+ return ++mIdleCallbackTimeoutCounter;
+ case Timeout::Reason::eTimeoutOrInterval:
+ default:
+ return ++mTimeoutIdCounter;
+ }
+}
+
+nsGlobalWindow*
+nsGlobalWindow::InnerForSetTimeoutOrInterval(ErrorResult& aError)
+{
+ nsGlobalWindow* currentInner;
+ nsGlobalWindow* forwardTo;
+ if (IsInnerWindow()) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ currentInner = outer ? outer->GetCurrentInnerWindowInternal() : this;
+
+ forwardTo = this;
+ } else {
+ currentInner = GetCurrentInnerWindowInternal();
+
+ // This needs to forward to the inner window, but since the current
+ // inner may not be the inner in the calling scope, we need to treat
+ // this specially here as we don't want timeouts registered in a
+ // dying inner window to get registered and run on the current inner
+ // window. To get this right, we need to forward this call to the
+ // inner window that's calling window.setTimeout().
+
+ forwardTo = CallerInnerWindow();
+ if (!forwardTo && nsContentUtils::IsCallerChrome()) {
+ forwardTo = currentInner;
+ }
+ if (!forwardTo) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ // If the caller and the callee share the same outer window, forward to the
+ // caller inner. Else, we forward to the current inner (e.g. someone is
+ // calling setTimeout() on a reference to some other window).
+ if (forwardTo->GetOuterWindow() != AsOuter() ||
+ !forwardTo->IsInnerWindow()) {
+ if (!currentInner) {
+ NS_WARNING("No inner window available!");
+ aError.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+
+ return currentInner;
+ }
+ }
+
+ // 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 forwardTo->AsInner()->HasActiveDocument() ? currentInner : nullptr;
+}
+
+int32_t
+nsGlobalWindow::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
+nsGlobalWindow::SetTimeout(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout,
+ const Sequence<JS::Value>& /* unused */,
+ ErrorResult& aError)
+{
+ return SetTimeoutOrInterval(aCx, aHandler, aTimeout, false, aError);
+}
+
+static bool
+IsInterval(const Optional<int32_t>& aTimeout, int32_t& aResultTimeout)
+{
+ if (aTimeout.WasPassed()) {
+ aResultTimeout = aTimeout.Value();
+ return true;
+ }
+
+ // If no interval was specified, treat this like a timeout, to avoid setting
+ // an interval of 0 milliseconds.
+ aResultTimeout = 0;
+ return false;
+}
+
+int32_t
+nsGlobalWindow::SetInterval(JSContext* aCx, Function& aFunction,
+ const Optional<int32_t>& aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aError)
+{
+ int32_t timeout;
+ bool isInterval = IsInterval(aTimeout, timeout);
+ return SetTimeoutOrInterval(aCx, aFunction, timeout, aArguments, isInterval,
+ aError);
+}
+
+int32_t
+nsGlobalWindow::SetInterval(JSContext* aCx, const nsAString& aHandler,
+ const Optional<int32_t>& aTimeout,
+ const Sequence<JS::Value>& /* unused */,
+ ErrorResult& aError)
+{
+ int32_t timeout;
+ bool isInterval = IsInterval(aTimeout, timeout);
+ return SetTimeoutOrInterval(aCx, aHandler, timeout, isInterval, aError);
+}
+
+nsresult
+nsGlobalWindow::SetTimeoutOrInterval(nsITimeoutHandler* aHandler,
+ int32_t interval, bool aIsInterval,
+ Timeout::Reason aReason, int32_t* aReturn)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ // If we don't have a document (we could have been unloaded since
+ // the call to setTimeout was made), do nothing.
+ if (!mDoc) {
+ return NS_OK;
+ }
+
+ // Disallow negative intervals. If aIsInterval also disallow 0,
+ // because we use that as a "don't repeat" flag.
+ interval = std::max(aIsInterval ? 1 : 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();
+ timeout->mIsInterval = aIsInterval;
+ timeout->mInterval = interval;
+ timeout->mScriptHandler = aHandler;
+ timeout->mReason = aReason;
+
+ // Now clamp the actual interval we will use for the timer based on
+ uint32_t nestingLevel = sNestingLevel + 1;
+ uint32_t realInterval = interval;
+ if (aIsInterval || nestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL ||
+ mBackPressureDelayMS > 0) {
+ // Don't allow timeouts less than DOMMinTimeoutValue() from
+ // now...
+ realInterval = std::max(realInterval, uint32_t(DOMMinTimeoutValue()));
+ }
+
+ TimeDuration delta = TimeDuration::FromMilliseconds(realInterval);
+
+ if (IsFrozen()) {
+ // If we are frozen simply set timeout->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.
+ timeout->mTimeRemaining = delta;
+ } else {
+ // 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.
+ timeout->mWhen = TimeStamp::Now() + delta;
+ }
+
+ // If we're not suspended, then set the timer.
+ if (!IsSuspended()) {
+ MOZ_ASSERT(!timeout->mWhen.IsNull());
+
+ nsresult rv;
+ timeout->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<Timeout> copy = timeout;
+
+ rv = timeout->InitTimer(GetThrottledEventQueue(), realInterval);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The timeout is now also held in the timer's closure.
+ Unused << copy.forget();
+ }
+
+ timeout->mWindow = this;
+
+ if (!aIsInterval) {
+ timeout->mNestingLevel = nestingLevel;
+ }
+
+ // No popups from timeouts by default
+ timeout->mPopupState = openAbused;
+
+ if (gRunningTimeoutDepth == 0 && gPopupControlState < openAbused) {
+ // 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).
+
+ int32_t delay =
+ Preferences::GetInt("dom.disable_open_click_delay");
+
+ // This is checking |interval|, not realInterval, on purpose,
+ // because our lower bound for |realInterval| could be pretty high
+ // in some cases.
+ if (interval <= delay) {
+ timeout->mPopupState = gPopupControlState;
+ }
+ }
+
+ InsertTimeoutIntoList(timeout);
+
+ timeout->mTimeoutId = GetTimeoutId(aReason);
+ *aReturn = timeout->mTimeoutId;
+
+ return NS_OK;
+}
+
+int32_t
+nsGlobalWindow::SetTimeoutOrInterval(JSContext *aCx, Function& aFunction,
+ int32_t aTimeout,
+ const Sequence<JS::Value>& aArguments,
+ bool aIsInterval, ErrorResult& aError)
+{
+ nsGlobalWindow* inner = InnerForSetTimeoutOrInterval(aError);
+ if (!inner) {
+ return -1;
+ }
+
+ if (inner != this) {
+ return inner->SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments,
+ aIsInterval, aError);
+ }
+
+ nsCOMPtr<nsIScriptTimeoutHandler> handler =
+ NS_CreateJSTimeoutHandler(aCx, this, aFunction, aArguments, aError);
+ if (!handler) {
+ return 0;
+ }
+
+ int32_t result;
+ aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval,
+ Timeout::Reason::eTimeoutOrInterval, &result);
+ return result;
+}
+
+int32_t
+nsGlobalWindow::SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout, bool aIsInterval,
+ ErrorResult& aError)
+{
+ nsGlobalWindow* inner = InnerForSetTimeoutOrInterval(aError);
+ if (!inner) {
+ return -1;
+ }
+
+ if (inner != this) {
+ return inner->SetTimeoutOrInterval(aCx, aHandler, aTimeout, aIsInterval,
+ aError);
+ }
+
+ nsCOMPtr<nsIScriptTimeoutHandler> handler =
+ NS_CreateJSTimeoutHandler(aCx, this, aHandler, aError);
+ if (!handler) {
+ return 0;
+ }
+
+ int32_t result;
+ aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval,
+ Timeout::Reason::eTimeoutOrInterval, &result);
+ return result;
+}
+
+bool
+nsGlobalWindow::RunTimeoutHandler(Timeout* aTimeout,
+ nsIScriptContext* aScx)
+{
+ // Hold on to the timeout in case mExpr or mFunObj releases its
+ // doc.
+ RefPtr<Timeout> timeout = aTimeout;
+ Timeout* last_running_timeout = mRunningTimeout;
+ mRunningTimeout = timeout;
+ timeout->mRunning = true;
+
+ // Push this timeout's popup control state, which should only be
+ // eabled the first time a timeout fires that was created while
+ // popups were enabled and with a delay less than
+ // "dom.disable_open_click_delay".
+ nsAutoPopupStatePusher popupStatePusher(timeout->mPopupState);
+
+ // Clear the timeout's popup state, if any, to prevent interval
+ // timeouts from repeatedly opening poups.
+ timeout->mPopupState = openAbused;
+
+ ++gRunningTimeoutDepth;
+ ++mTimeoutFiringDepth;
+
+ bool trackNestingLevel = !timeout->mIsInterval;
+ uint32_t nestingLevel;
+ if (trackNestingLevel) {
+ nestingLevel = sNestingLevel;
+ sNestingLevel = timeout->mNestingLevel;
+ }
+
+ const char *reason;
+ if (timeout->mIsInterval) {
+ reason = "setInterval handler";
+ } else {
+ reason = "setTimeout handler";
+ }
+
+ bool abortIntervalHandler = false;
+ nsCOMPtr<nsIScriptTimeoutHandler> handler(do_QueryInterface(timeout->mScriptHandler));
+ if (handler) {
+ RefPtr<Function> callback = handler->GetCallback();
+
+ if (!callback) {
+ // Evaluate the timeout expression.
+ const nsAString& script = handler->GetHandlerText();
+
+ const char* filename = nullptr;
+ uint32_t lineNo = 0, dummyColumn = 0;
+ handler->GetLocation(&filename, &lineNo, &dummyColumn);
+
+ // 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(this, reason, true);
+ JS::CompileOptions options(aes.cx());
+ options.setFileAndLine(filename, lineNo).setVersion(JSVERSION_DEFAULT);
+ JS::Rooted<JSObject*> global(aes.cx(), FastGetGlobalJSObject());
+ nsresult rv =
+ nsJSUtils::EvaluateString(aes.cx(), script, global, options);
+ if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
+ abortIntervalHandler = true;
+ }
+ } else {
+ // Hold strong ref to ourselves while we call the callback.
+ nsCOMPtr<nsISupports> me(static_cast<nsIDOMWindow*>(this));
+ ErrorResult rv;
+ JS::Rooted<JS::Value> ignoredVal(RootingCx());
+ callback->Call(me, handler->GetArgs(), &ignoredVal, rv, reason);
+ if (rv.IsUncatchableException()) {
+ abortIntervalHandler = true;
+ }
+
+ rv.SuppressException();
+ }
+ } else {
+ nsCOMPtr<nsITimeoutHandler> basicHandler(timeout->mScriptHandler);
+ nsCOMPtr<nsISupports> kungFuDeathGrip(static_cast<nsIDOMWindow*>(this));
+ mozilla::Unused << kungFuDeathGrip;
+ basicHandler->Call();
+ }
+
+ // 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.
+
+ // Since we might be processing more timeouts, go ahead and flush the promise
+ // queue now before we do that. We need to do that while we're still in our
+ // "running JS is safe" state (e.g. mRunningTimeout is set, timeout->mRunning
+ // is false).
+ Promise::PerformMicroTaskCheckpoint();
+
+ if (trackNestingLevel) {
+ sNestingLevel = nestingLevel;
+ }
+
+ --mTimeoutFiringDepth;
+ --gRunningTimeoutDepth;
+
+ mRunningTimeout = last_running_timeout;
+ timeout->mRunning = false;
+
+ return timeout->mCleared;
+}
+
+bool
+nsGlobalWindow::RescheduleTimeout(Timeout* aTimeout, const TimeStamp& now,
+ bool aRunningPendingTimeouts)
+{
+ if (!aTimeout->mIsInterval) {
+ if (aTimeout->mTimer) {
+ // The timeout still has an OS timer, and it's not an interval,
+ // that means that the OS timer could still fire; cancel the OS
+ // timer and release its reference to the timeout.
+ aTimeout->mTimer->Cancel();
+ aTimeout->mTimer = nullptr;
+ aTimeout->Release();
+ }
+ return false;
+ }
+
+ // Compute time to next timeout for interval timer.
+ // Make sure nextInterval is at least DOMMinTimeoutValue().
+ TimeDuration nextInterval =
+ TimeDuration::FromMilliseconds(std::max(aTimeout->mInterval,
+ uint32_t(DOMMinTimeoutValue())));
+
+ // If we're running pending timeouts, set the next interval to be
+ // relative to "now", and not to when the timeout that was pending
+ // should have fired.
+ TimeStamp firingTime;
+ if (aRunningPendingTimeouts) {
+ firingTime = now + nextInterval;
+ } else {
+ firingTime = aTimeout->mWhen + nextInterval;
+ }
+
+ TimeStamp currentNow = TimeStamp::Now();
+ TimeDuration delay = firingTime - currentNow;
+
+ // 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);
+ }
+
+ if (!aTimeout->mTimer) {
+ if (IsFrozen()) {
+ aTimeout->mTimeRemaining = delay;
+ } else if (IsSuspended()) {
+ aTimeout->mWhen = currentNow + delay;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Window should be frozen or suspended.");
+ }
+ return true;
+ }
+
+ aTimeout->mWhen = currentNow + delay;
+
+ // Reschedule the OS timer. Don't bother returning any error codes if
+ // this fails since the callers of this method don't care about them.
+ nsresult rv = aTimeout->InitTimer(GetThrottledEventQueue(),
+ delay.ToMilliseconds());
+
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Error initializing timer for DOM timeout!");
+
+ // We failed to initialize the new OS timer, this timer does
+ // us no good here so we just cancel it (just in case) and
+ // null out the pointer to the OS timer, this will release the
+ // OS timer. As we continue executing the code below we'll end
+ // up deleting the timeout since it's not an interval timeout
+ // any more (since timeout->mTimer == nullptr).
+ aTimeout->mTimer->Cancel();
+ aTimeout->mTimer = nullptr;
+
+ // Now that the OS timer no longer has a reference to the
+ // timeout we need to drop that reference.
+ aTimeout->Release();
+
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsGlobalWindow::RunTimeout(Timeout* aTimeout)
+{
+ if (IsSuspended()) {
+ return;
+ }
+
+ NS_ASSERTION(IsInnerWindow(), "Timeout running on outer window!");
+ NS_ASSERTION(!IsFrozen(), "Timeout running on a window in the bfcache!");
+
+ Timeout* nextTimeout;
+ Timeout* last_expired_timeout;
+ Timeout* last_insertion_point;
+ uint32_t firingDepth = mTimeoutFiringDepth + 1;
+
+ // Make sure that the window and the script context don't go away as
+ // a result of running timeouts
+ nsCOMPtr<nsIScriptGlobalObject> windowKungFuDeathGrip(this);
+
+ // A native timer has gone off. See which of our timeouts need
+ // servicing
+ TimeStamp now = TimeStamp::Now();
+ TimeStamp deadline;
+
+ if (aTimeout && aTimeout->mWhen > 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 before aTimeout *will* be fired
+ // now.
+
+ deadline = aTimeout->mWhen;
+ } else {
+ deadline = now;
+ }
+
+ uint32_t numTimersToRun = 0;
+ bool targetTimerSeen = false;
+
+
+ // 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 aTimeout->mWhen
+ // if the timer fired early. So we can stop walking if we get to timeouts
+ // whose mWhen is greater than deadline, since once that happens we know
+ // nothing past that point is expired.
+ last_expired_timeout = nullptr;
+ for (Timeout* timeout = mTimeouts.getFirst();
+ timeout && timeout->mWhen <= deadline;
+ timeout = timeout->getNext()) {
+ if (timeout->mFiringDepth == 0) {
+ // Mark any timeouts that are on the list to be fired with the
+ // firing depth so that we can reentrantly run timeouts
+ timeout->mFiringDepth = firingDepth;
+ last_expired_timeout = timeout;
+
+ // Note that we have seen our target timer. This means we can now
+ // stop processing timers once we hit our threshold below.
+ if (timeout == aTimeout) {
+ targetTimerSeen = true;
+ }
+
+ // Run only a limited number of timers based on the configured
+ // maximum. Note, we must always run our target timer however.
+ // Further timers that are ready will get picked up by their own
+ // nsITimer runnables when they execute.
+ //
+ // For chrome windows, however, we do coalesce all timers and
+ // do not yield the main thread. This is partly because we
+ // trust chrome windows not to misbehave and partly because a
+ // number of browser chrome tests have races that depend on this
+ // coalescing.
+ if (targetTimerSeen &&
+ numTimersToRun >= gTargetMaxConsecutiveCallbacks &&
+ !IsChromeWindow()) {
+ break;
+ }
+
+ numTimersToRun += 1;
+ }
+ }
+
+ // 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 (!last_expired_timeout) {
+ return;
+ }
+
+ // Record telemetry information about timers set recently.
+ TimeDuration recordingInterval = TimeDuration::FromMilliseconds(STATISTICS_INTERVAL);
+ if (gLastRecordedRecentTimeouts.IsNull() ||
+ now - gLastRecordedRecentTimeouts > recordingInterval) {
+ gLastRecordedRecentTimeouts = now;
+ }
+
+ // Insert a dummy timeout into the list of timeouts between the
+ // portion of the list that we are about to process now and those
+ // timeouts that will be processed in a future call to
+ // win_run_timeout(). This dummy timeout serves as the head of the
+ // list for any timeouts inserted as a result of running a timeout.
+ RefPtr<Timeout> dummy_timeout = new Timeout();
+ dummy_timeout->mFiringDepth = firingDepth;
+ dummy_timeout->mWhen = now;
+ last_expired_timeout->setNext(dummy_timeout);
+ RefPtr<Timeout> timeoutExtraRef(dummy_timeout);
+
+ last_insertion_point = mTimeoutInsertionPoint;
+ // If we ever start setting mTimeoutInsertionPoint to a non-dummy timeout,
+ // the logic in ResetTimersForThrottleReduction will need to change.
+ mTimeoutInsertionPoint = dummy_timeout;
+
+ for (Timeout* timeout = mTimeouts.getFirst();
+ timeout != dummy_timeout && !IsFrozen();
+ timeout = nextTimeout) {
+ nextTimeout = timeout->getNext();
+
+ if (timeout->mFiringDepth != firingDepth) {
+ // We skip the timeout since it's on the list to run at another
+ // depth.
+
+ continue;
+ }
+
+ if (IsSuspended()) {
+ // Some timer did suspend us. Make sure the
+ // rest of the timers get executed later.
+ timeout->mFiringDepth = 0;
+ continue;
+ }
+
+ // The timeout is on the list to run at this depth, go ahead and
+ // process it.
+
+ // 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 = GetContextInternal();
+
+ if (!scx) {
+ // No context means this window was closed or never properly
+ // initialized for this language.
+ continue;
+ }
+
+ // This timeout is good to run
+ bool timeout_was_cleared = RunTimeoutHandler(timeout, scx);
+
+ if (timeout_was_cleared) {
+ // The running timeout's window was cleared, this means that
+ // ClearAllTimeouts() was called from a *nested* call, possibly
+ // through a timeout that fired while a modal (to this window)
+ // dialog was open or through other non-obvious paths.
+ MOZ_ASSERT(dummy_timeout->HasRefCntOne(), "dummy_timeout may leak");
+ Unused << timeoutExtraRef.forget().take();
+
+ mTimeoutInsertionPoint = last_insertion_point;
+
+ return;
+ }
+
+ // If we have a regular interval timer, we re-schedule the
+ // timeout, accounting for clock drift.
+ bool needsReinsertion = RescheduleTimeout(timeout, now, !aTimeout);
+
+ // Running a timeout can cause another timeout to be deleted, so
+ // we need to reset the pointer to the following timeout.
+ nextTimeout = timeout->getNext();
+
+ timeout->remove();
+
+ if (needsReinsertion) {
+ // Insert interval timeout onto list sorted in deadline order.
+ // AddRefs timeout.
+ InsertTimeoutIntoList(timeout);
+ }
+
+ // Release the timeout struct since it's possibly out of the list
+ timeout->Release();
+ }
+
+ // Take the dummy timeout off the head of the list
+ dummy_timeout->remove();
+ timeoutExtraRef = nullptr;
+ MOZ_ASSERT(dummy_timeout->HasRefCntOne(), "dummy_timeout may leak");
+
+ mTimeoutInsertionPoint = last_insertion_point;
+
+ MaybeApplyBackPressure();
+}
+
+void
+nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerId, Timeout::Reason aReason)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ uint32_t timerId = (uint32_t)aTimerId;
+ Timeout* timeout;
+
+ for (timeout = mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) {
+ if (timeout->mTimeoutId == timerId && timeout->mReason == aReason) {
+ if (timeout->mRunning) {
+ /* We're running from inside the timeout. Mark this
+ timeout for deferred deletion by the code in
+ RunTimeout() */
+ timeout->mIsInterval = false;
+ }
+ else {
+ /* Delete the timeout from the pending timeout list */
+ timeout->remove();
+
+ if (timeout->mTimer) {
+ timeout->mTimer->Cancel();
+ timeout->mTimer = nullptr;
+ timeout->Release();
+ }
+ timeout->Release();
+ }
+ break;
+ }
+ }
+}
+
+nsresult nsGlobalWindow::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS)
+{
+ FORWARD_TO_INNER(ResetTimersForThrottleReduction, (aPreviousThrottleDelayMS),
+ NS_ERROR_NOT_INITIALIZED);
+ MOZ_ASSERT(aPreviousThrottleDelayMS > 0);
+
+ if (IsFrozen() || IsSuspended()) {
+ return NS_OK;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+
+ // If mTimeoutInsertionPoint is non-null, we're in the middle of firing
+ // timers and the timers we're planning to fire all come before
+ // mTimeoutInsertionPoint; mTimeoutInsertionPoint itself is a dummy timeout
+ // with an mWhen that may be semi-bogus. In that case, we don't need to do
+ // anything with mTimeoutInsertionPoint or anything before it, so should
+ // start at the timer after mTimeoutInsertionPoint, if there is one.
+ // Otherwise, start at the beginning of the list.
+ for (Timeout* timeout = mTimeoutInsertionPoint ?
+ mTimeoutInsertionPoint->getNext() : mTimeouts.getFirst();
+ timeout; ) {
+ // It's important that this check be <= so that we guarantee that
+ // taking std::max with |now| won't make a quantity equal to
+ // timeout->mWhen below.
+ if (timeout->mWhen <= now) {
+ timeout = timeout->getNext();
+ continue;
+ }
+
+ if (timeout->mWhen - now >
+ TimeDuration::FromMilliseconds(aPreviousThrottleDelayMS)) {
+ // No need to loop further. Timeouts are sorted in mWhen order
+ // and the ones after this point were all set up for at least
+ // gMinBackgroundTimeoutValue ms and hence were not clamped.
+ break;
+ }
+
+ // We reduced our throttled delay. Re-init the timer appropriately.
+ // Compute the interval the timer should have had if it had not been set in a
+ // background window
+ TimeDuration interval =
+ TimeDuration::FromMilliseconds(std::max(timeout->mInterval,
+ uint32_t(DOMMinTimeoutValue())));
+ uint32_t oldIntervalMillisecs = 0;
+ timeout->mTimer->GetDelay(&oldIntervalMillisecs);
+ TimeDuration oldInterval = TimeDuration::FromMilliseconds(oldIntervalMillisecs);
+ if (oldInterval > interval) {
+ // unclamp
+ TimeStamp firingTime =
+ std::max(timeout->mWhen - oldInterval + interval, now);
+
+ NS_ASSERTION(firingTime < timeout->mWhen,
+ "Our firing time should strictly decrease!");
+
+ TimeDuration delay = firingTime - now;
+ timeout->mWhen = firingTime;
+
+ // Since we reset mWhen we need to move |timeout| to the right
+ // place in the list so that it remains sorted by mWhen.
+
+ // Get the pointer to the next timeout now, before we move the
+ // current timeout in the list.
+ Timeout* nextTimeout = timeout->getNext();
+
+ // Since we are only reducing intervals in this method we can
+ // make an optimization here. If the reduction does not cause us
+ // to fall before our previous timeout then we do not have to
+ // remove and re-insert the current timeout. This is important
+ // because re-insertion makes this algorithm O(n^2). Since we
+ // will typically be shifting a lot of timers at once this
+ // optimization saves us a lot of work.
+ Timeout* prevTimeout = timeout->getPrevious();
+ if (prevTimeout && prevTimeout->mWhen > timeout->mWhen) {
+ // It is safe to remove and re-insert because mWhen is now
+ // strictly smaller than it used to be, so we know we'll insert
+ // |timeout| before nextTimeout.
+ NS_ASSERTION(!nextTimeout ||
+ timeout->mWhen < nextTimeout->mWhen, "How did that happen?");
+ timeout->remove();
+ // InsertTimeoutIntoList will addref |timeout| and reset
+ // mFiringDepth. Make sure to undo that after calling it.
+ uint32_t firingDepth = timeout->mFiringDepth;
+ InsertTimeoutIntoList(timeout);
+ timeout->mFiringDepth = firingDepth;
+ timeout->Release();
+ }
+
+ nsresult rv = timeout->InitTimer(GetThrottledEventQueue(),
+ delay.ToMilliseconds());
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error resetting non background timer for DOM timeout!");
+ return rv;
+ }
+
+ timeout = nextTimeout;
+ } else {
+ timeout = timeout->getNext();
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsGlobalWindow::ClearAllTimeouts()
+{
+ Timeout* timeout;
+ Timeout* nextTimeout;
+
+ for (timeout = mTimeouts.getFirst(); timeout; timeout = nextTimeout) {
+ /* 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 == timeout)
+ mTimeoutInsertionPoint = nullptr;
+
+ nextTimeout = timeout->getNext();
+
+ if (timeout->mTimer) {
+ timeout->mTimer->Cancel();
+ timeout->mTimer = nullptr;
+
+ // Drop the count since the timer isn't going to hold on
+ // anymore.
+ timeout->Release();
+ }
+
+ // Set timeout->mCleared to true to indicate that the timeout was
+ // cleared and taken out of the list of timeouts
+ timeout->mCleared = true;
+
+ // Drop the count since we're removing it from the list.
+ timeout->Release();
+ }
+
+ // Clear out our list
+ mTimeouts.clear();
+}
+
+void
+nsGlobalWindow::InsertTimeoutIntoList(Timeout* aTimeout)
+{
+ NS_ASSERTION(IsInnerWindow(),
+ "InsertTimeoutIntoList() called on outer window!");
+
+ // Start at mLastTimeout and go backwards. Don't go further than
+ // mTimeoutInsertionPoint, though. This optimizes for the common case of
+ // insertion at the end.
+ Timeout* prevSibling;
+ for (prevSibling = mTimeouts.getLast();
+ prevSibling && prevSibling != mTimeoutInsertionPoint &&
+ // This condition needs to match the one in SetTimeoutOrInterval that
+ // determines whether to set mWhen or mTimeRemaining.
+ (IsFrozen() ?
+ prevSibling->mTimeRemaining > aTimeout->mTimeRemaining :
+ prevSibling->mWhen > aTimeout->mWhen);
+ prevSibling = prevSibling->getPrevious()) {
+ /* Do nothing; just searching */
+ }
+
+ // Now link in aTimeout after prevSibling.
+ if (prevSibling) {
+ prevSibling->setNext(aTimeout);
+ } else {
+ mTimeouts.insertFront(aTimeout);
+ }
+
+ aTimeout->mFiringDepth = 0;
+
+ // Increment the timeout's reference count since it's now held on to
+ // by the list
+ aTimeout->AddRef();
+}
+
+//*****************************************************************************
+// nsGlobalWindow: Helper Functions
+//*****************************************************************************
+
+already_AddRefed<nsIDocShellTreeOwner>
+nsGlobalWindow::GetTreeOwner()
+{
+ FORWARD_TO_OUTER(GetTreeOwner, (), nullptr);
+
+ // 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>
+nsGlobalWindow::GetTreeOwnerWindow()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ 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>
+nsGlobalWindow::GetWebBrowserChrome()
+{
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner);
+ return browserChrome.forget();
+}
+
+nsIScrollableFrame *
+nsGlobalWindow::GetScrollFrame()
+{
+ FORWARD_TO_OUTER(GetScrollFrame, (), nullptr);
+
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+ if (presShell) {
+ return presShell->GetRootScrollFrameAsScrollable();
+ }
+ return nullptr;
+}
+
+nsresult
+nsGlobalWindow::SecurityCheckURL(const char *aURL)
+{
+ nsCOMPtr<nsPIDOMWindowInner> sourceWindow = do_QueryInterface(GetEntryGlobal());
+ if (!sourceWindow) {
+ sourceWindow = AsOuter()->GetCurrentInnerWindow();
+ }
+ AutoJSContext cx;
+ nsGlobalWindow* sourceWin = nsGlobalWindow::Cast(sourceWindow);
+ JSAutoCompartment ac(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<nsIDocument> doc = sourceWindow->GetDoc();
+ nsIURI* baseURI = nullptr;
+ nsAutoCString charset(NS_LITERAL_CSTRING("UTF-8")); // default to utf-8
+ if (doc) {
+ baseURI = doc->GetDocBaseURI();
+ charset = doc->GetDocumentCharacterSet();
+ }
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aURL),
+ charset.get(), baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_FAILED(nsContentUtils::GetSecurityManager()->
+ CheckLoadURIFromScript(cx, uri))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsGlobalWindow::IsPrivateBrowsing()
+{
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(GetDocShell());
+ return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+void
+nsGlobalWindow::FlushPendingNotifications(mozFlushType aType)
+{
+ if (mDoc) {
+ mDoc->FlushPendingNotifications(aType);
+ }
+}
+
+void
+nsGlobalWindow::EnsureSizeUpToDate()
+{
+ MOZ_ASSERT(IsOuterWindow());
+
+ // If we're a subframe, make sure our size is up to date. It's OK that this
+ // crosses the content/chrome boundary, since chrome can have pending reflows
+ // too.
+ nsGlobalWindow *parent = nsGlobalWindow::Cast(GetPrivateParent());
+ if (parent) {
+ parent->FlushPendingNotifications(Flush_Layout);
+ }
+}
+
+already_AddRefed<nsISupports>
+nsGlobalWindow::SaveWindowState()
+{
+ NS_PRECONDITION(IsOuterWindow(), "Can't save the inner window's state");
+
+ if (!mContext || !GetWrapperPreserveColor()) {
+ // The window may be getting torn down; don't bother saving state.
+ return nullptr;
+ }
+
+ nsGlobalWindow *inner = GetCurrentInnerWindowInternal();
+ NS_ASSERTION(inner, "No inner window to save");
+
+ // 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);
+
+#ifdef DEBUG_PAGE_CACHE
+ printf("saving window state, state = %p\n", (void*)state);
+#endif
+
+ return state.forget();
+}
+
+nsresult
+nsGlobalWindow::RestoreWindowState(nsISupports *aState)
+{
+ NS_ASSERTION(IsOuterWindow(), "Cannot restore an inner window");
+
+ 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);
+
+#ifdef DEBUG_PAGE_CACHE
+ printf("restoring window state, state = %p\n", (void*)holder);
+#endif
+
+ // And we're ready to go!
+ nsGlobalWindow *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.
+ nsIContent* focusedNode = inner->GetFocusedNode();
+ if (IsLink(focusedNode)) {
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<nsIDOMElement> focusedElement(do_QueryInterface(focusedNode));
+ fm->SetFocus(focusedElement, nsIFocusManager::FLAG_NOSCROLL |
+ nsIFocusManager::FLAG_SHOWRING);
+ }
+ }
+
+ inner->Thaw();
+
+ holder->DidRestoreWindow();
+
+ return NS_OK;
+}
+
+void
+nsGlobalWindow::EnableDeviceSensor(uint32_t aType)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ 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
+nsGlobalWindow::DisableDeviceSensor(uint32_t aType)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ 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) || defined(MOZ_WIDGET_GONK)
+void
+nsGlobalWindow::EnableOrientationChangeListener()
+{
+ MOZ_ASSERT(IsInnerWindow());
+ if (!nsContentUtils::ShouldResistFingerprinting(mDocShell) &&
+ !mOrientationChangeObserver) {
+ mOrientationChangeObserver =
+ new WindowOrientationObserver(this);
+ }
+}
+
+void
+nsGlobalWindow::DisableOrientationChangeListener()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ mOrientationChangeObserver = nullptr;
+}
+#endif
+
+void
+nsGlobalWindow::SetHasGamepadEventListener(bool aHasGamepad/* = true*/)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ mHasGamepad = aHasGamepad;
+ if (aHasGamepad) {
+ EnableGamepadUpdates();
+ }
+}
+
+
+void
+nsGlobalWindow::EventListenerAdded(nsIAtom* aType)
+{
+ if (aType == nsGkAtoms::onvrdisplayconnect ||
+ aType == nsGkAtoms::onvrdisplaydisconnect ||
+ aType == nsGkAtoms::onvrdisplaypresentchange) {
+ NotifyVREventListenerAdded();
+ }
+}
+
+void
+nsGlobalWindow::NotifyVREventListenerAdded()
+{
+ MOZ_ASSERT(IsInnerWindow());
+ mHasVREvents = true;
+ EnableVRUpdates();
+}
+
+void
+nsGlobalWindow::EnableTimeChangeNotifications()
+{
+ mozilla::time::AddWindowListener(AsInner());
+}
+
+void
+nsGlobalWindow::DisableTimeChangeNotifications()
+{
+ mozilla::time::RemoveWindowListener(AsInner());
+}
+
+void
+nsGlobalWindow::AddSizeOfIncludingThis(nsWindowSizes* aWindowSizes) const
+{
+ aWindowSizes->mDOMOtherSize += aWindowSizes->mMallocSizeOf(this);
+
+ if (IsInnerWindow()) {
+ EventListenerManager* elm = GetExistingListenerManager();
+ if (elm) {
+ aWindowSizes->mDOMOtherSize +=
+ elm->SizeOfIncludingThis(aWindowSizes->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() == AsInner()) {
+ mDoc->DocAddSizeOfIncludingThis(aWindowSizes);
+ }
+ }
+ }
+
+ if (mNavigator) {
+ aWindowSizes->mDOMOtherSize +=
+ mNavigator->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
+ }
+
+ aWindowSizes->mDOMEventTargetsSize +=
+ mEventTargetObjects.ShallowSizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
+
+ for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done(); iter.Next()) {
+ DOMEventTargetHelper* et = iter.Get()->GetKey();
+ if (nsCOMPtr<nsISizeOfEventTarget> iSizeOf = do_QueryObject(et)) {
+ aWindowSizes->mDOMEventTargetsSize +=
+ iSizeOf->SizeOfEventTargetIncludingThis(aWindowSizes->mMallocSizeOf);
+ }
+ if (EventListenerManager* elm = et->GetExistingListenerManager()) {
+ aWindowSizes->mDOMEventListenersCount += elm->ListenerCount();
+ }
+ ++aWindowSizes->mDOMEventTargetsCount;
+ }
+}
+
+
+#ifdef MOZ_GAMEPAD
+void
+nsGlobalWindow::AddGamepad(uint32_t aIndex, Gamepad* aGamepad)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ // 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.Put(aIndex, aGamepad);
+}
+
+void
+nsGlobalWindow::RemoveGamepad(uint32_t aIndex)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ RefPtr<Gamepad> gamepad;
+ if (!mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
+ return;
+ }
+ // Free up the index we were using so it can be reused
+ mGamepadIndexSet.Remove(gamepad->Index());
+ mGamepads.Remove(aIndex);
+}
+
+void
+nsGlobalWindow::GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ aGamepads.Clear();
+ // mGamepads.Count() may not be sufficient, but it's not harmful.
+ aGamepads.SetCapacity(mGamepads.Count());
+ for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
+ Gamepad* gamepad = iter.UserData();
+ aGamepads.EnsureLengthAtLeast(gamepad->Index() + 1);
+ aGamepads[gamepad->Index()] = gamepad;
+ }
+}
+
+already_AddRefed<Gamepad>
+nsGlobalWindow::GetGamepad(uint32_t aIndex)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ RefPtr<Gamepad> gamepad;
+
+ if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
+ return gamepad.forget();
+ }
+
+ return nullptr;
+}
+
+void
+nsGlobalWindow::SetHasSeenGamepadInput(bool aHasSeen)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ mHasSeenGamepadInput = aHasSeen;
+}
+
+bool
+nsGlobalWindow::HasSeenGamepadInput()
+{
+ MOZ_ASSERT(IsInnerWindow());
+ return mHasSeenGamepadInput;
+}
+
+void
+nsGlobalWindow::SyncGamepadState()
+{
+ MOZ_ASSERT(IsInnerWindow());
+ if (mHasSeenGamepadInput) {
+ RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+ for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
+ gamepadManager->SyncGamepadState(iter.Key(), iter.UserData());
+ }
+ }
+}
+#endif // MOZ_GAMEPAD
+
+bool
+nsGlobalWindow::UpdateVRDisplays(nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDevices)
+{
+ FORWARD_TO_INNER(UpdateVRDisplays, (aDevices), false);
+
+ VRDisplay::UpdateVRDisplays(mVRDisplays, AsInner());
+ aDevices = mVRDisplays;
+ return true;
+}
+
+void
+nsGlobalWindow::NotifyActiveVRDisplaysChanged()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ if (mNavigator) {
+ mNavigator->NotifyActiveVRDisplaysChanged();
+ }
+}
+
+
+// nsGlobalChromeWindow implementation
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalChromeWindow)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGlobalChromeWindow,
+ nsGlobalWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserDOMWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGroupMessageManagers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenerForInitialContentBrowser)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGlobalChromeWindow,
+ nsGlobalWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserDOMWindow)
+ if (tmp->mMessageManager) {
+ static_cast<nsFrameMessageManager*>(
+ tmp->mMessageManager.get())->Disconnect();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
+ }
+ tmp->DisconnectAndClearGroupMessageManagers();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGroupMessageManagers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenerForInitialContentBrowser)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+// QueryInterface implementation for nsGlobalChromeWindow
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsGlobalChromeWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMChromeWindow)
+NS_INTERFACE_MAP_END_INHERITING(nsGlobalWindow)
+
+NS_IMPL_ADDREF_INHERITED(nsGlobalChromeWindow, nsGlobalWindow)
+NS_IMPL_RELEASE_INHERITED(nsGlobalChromeWindow, nsGlobalWindow)
+
+/* static */ already_AddRefed<nsGlobalChromeWindow>
+nsGlobalChromeWindow::Create(nsGlobalWindow *aOuterWindow)
+{
+ RefPtr<nsGlobalChromeWindow> window = new nsGlobalChromeWindow(aOuterWindow);
+ window->InitWasOffline();
+ return window.forget();
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::GetWindowState(uint16_t* aWindowState)
+{
+ FORWARD_TO_INNER_CHROME(GetWindowState, (aWindowState), NS_ERROR_UNEXPECTED);
+
+ *aWindowState = WindowState();
+ return NS_OK;
+}
+
+uint16_t
+nsGlobalWindow::WindowState()
+{
+ MOZ_ASSERT(IsInnerWindow());
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+
+ int32_t mode = widget ? widget->SizeMode() : 0;
+
+ switch (mode) {
+ case nsSizeMode_Minimized:
+ return nsIDOMChromeWindow::STATE_MINIMIZED;
+ case nsSizeMode_Maximized:
+ return nsIDOMChromeWindow::STATE_MAXIMIZED;
+ case nsSizeMode_Fullscreen:
+ return nsIDOMChromeWindow::STATE_FULLSCREEN;
+ case nsSizeMode_Normal:
+ return nsIDOMChromeWindow::STATE_NORMAL;
+ default:
+ NS_WARNING("Illegal window state for this chrome window");
+ break;
+ }
+
+ return nsIDOMChromeWindow::STATE_NORMAL;
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::Maximize()
+{
+ FORWARD_TO_INNER_CHROME(Maximize, (), NS_ERROR_UNEXPECTED);
+
+ nsGlobalWindow::Maximize();
+ return NS_OK;
+}
+
+void
+nsGlobalWindow::Maximize()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+
+ if (widget) {
+ widget->SetSizeMode(nsSizeMode_Maximized);
+ }
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::Minimize()
+{
+ FORWARD_TO_INNER_CHROME(Minimize, (), NS_ERROR_UNEXPECTED);
+
+ nsGlobalWindow::Minimize();
+ return NS_OK;
+}
+
+void
+nsGlobalWindow::Minimize()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+
+ if (widget) {
+ widget->SetSizeMode(nsSizeMode_Minimized);
+ }
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::Restore()
+{
+ FORWARD_TO_INNER_CHROME(Restore, (), NS_ERROR_UNEXPECTED);
+
+ nsGlobalWindow::Restore();
+ return NS_OK;
+}
+
+void
+nsGlobalWindow::Restore()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+
+ if (widget) {
+ widget->SetSizeMode(nsSizeMode_Normal);
+ }
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::GetAttention()
+{
+ FORWARD_TO_INNER_CHROME(GetAttention, (), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ GetAttention(rv);
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::GetAttention(ErrorResult& aResult)
+{
+ MOZ_ASSERT(IsInnerWindow());
+ return GetAttentionWithCycleCount(-1, aResult);
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::GetAttentionWithCycleCount(int32_t aCycleCount)
+{
+ FORWARD_TO_INNER_CHROME(GetAttentionWithCycleCount, (aCycleCount), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ GetAttentionWithCycleCount(aCycleCount, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::GetAttentionWithCycleCount(int32_t aCycleCount,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+
+ if (widget) {
+ aError = widget->GetAttention(aCycleCount);
+ }
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::BeginWindowMove(nsIDOMEvent *aMouseDownEvent, nsIDOMElement* aPanel)
+{
+ FORWARD_TO_INNER_CHROME(BeginWindowMove, (aMouseDownEvent, aPanel), NS_ERROR_UNEXPECTED);
+
+ NS_ENSURE_TRUE(aMouseDownEvent, NS_ERROR_FAILURE);
+ Event* mouseDownEvent = aMouseDownEvent->InternalDOMEvent();
+ NS_ENSURE_TRUE(mouseDownEvent, NS_ERROR_FAILURE);
+
+ nsCOMPtr<Element> panel = do_QueryInterface(aPanel);
+ NS_ENSURE_TRUE(panel || !aPanel, NS_ERROR_FAILURE);
+
+ ErrorResult rv;
+ BeginWindowMove(*mouseDownEvent, panel, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::BeginWindowMove(Event& aMouseDownEvent, Element* aPanel,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsIWidget> widget;
+
+ // if a panel was supplied, use its widget instead.
+#ifdef MOZ_XUL
+ if (aPanel) {
+ nsIFrame* frame = aPanel->GetPrimaryFrame();
+ if (!frame || frame->GetType() != nsGkAtoms::menuPopupFrame) {
+ return;
+ }
+
+ widget = (static_cast<nsMenuPopupFrame*>(frame))->GetWidget();
+ }
+ else {
+#endif
+ widget = GetMainWidget();
+#ifdef MOZ_XUL
+ }
+#endif
+
+ if (!widget) {
+ return;
+ }
+
+ WidgetMouseEvent* mouseEvent =
+ aMouseDownEvent.WidgetEventPtr()->AsMouseEvent();
+ if (!mouseEvent || mouseEvent->mClass != eMouseEventClass) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aError = widget->BeginMoveDrag(mouseEvent);
+}
+
+already_AddRefed<nsWindowRoot>
+nsGlobalWindow::GetWindowRootOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
+ return root.forget().downcast<nsWindowRoot>();
+}
+
+already_AddRefed<nsWindowRoot>
+nsGlobalWindow::GetWindowRoot(mozilla::ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetWindowRootOuter, (), aError, nullptr);
+}
+
+//Note: This call will lock the cursor, it will not change as it moves.
+//To unlock, the cursor must be set back to CURSOR_AUTO.
+NS_IMETHODIMP
+nsGlobalChromeWindow::SetCursor(const nsAString& aCursor)
+{
+ FORWARD_TO_INNER_CHROME(SetCursor, (aCursor), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ SetCursor(aCursor, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::SetCursorOuter(const nsAString& aCursor, ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ int32_t cursor;
+
+ if (aCursor.EqualsLiteral("auto"))
+ cursor = NS_STYLE_CURSOR_AUTO;
+ else {
+ nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aCursor);
+ if (eCSSKeyword_UNKNOWN == keyword ||
+ !nsCSSProps::FindKeyword(keyword, nsCSSProps::kCursorKTable, cursor)) {
+ return;
+ }
+ }
+
+ RefPtr<nsPresContext> presContext;
+ if (mDocShell) {
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ }
+
+ if (presContext) {
+ // Need root widget.
+ nsCOMPtr<nsIPresShell> 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,
+ false, 0.0f, 0.0f,
+ widget, true);
+ }
+}
+
+void
+nsGlobalWindow::SetCursor(const nsAString& aCursor, ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetCursorOuter, (aCursor, aError), aError, );
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::GetBrowserDOMWindow(nsIBrowserDOMWindow **aBrowserWindow)
+{
+ FORWARD_TO_INNER_CHROME(GetBrowserDOMWindow, (aBrowserWindow), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ NS_IF_ADDREF(*aBrowserWindow = GetBrowserDOMWindow(rv));
+ return rv.StealNSResult();
+}
+
+nsIBrowserDOMWindow*
+nsGlobalWindow::GetBrowserDOMWindowOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(IsChromeWindow());
+ return static_cast<nsGlobalChromeWindow*>(this)->mBrowserDOMWindow;
+}
+
+nsIBrowserDOMWindow*
+nsGlobalWindow::GetBrowserDOMWindow(ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetBrowserDOMWindowOuter, (), aError, nullptr);
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::SetBrowserDOMWindow(nsIBrowserDOMWindow *aBrowserWindow)
+{
+ FORWARD_TO_INNER_CHROME(SetBrowserDOMWindow, (aBrowserWindow), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ SetBrowserDOMWindow(aBrowserWindow, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::SetBrowserDOMWindowOuter(nsIBrowserDOMWindow* aBrowserWindow)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(IsChromeWindow());
+ static_cast<nsGlobalChromeWindow*>(this)->mBrowserDOMWindow = aBrowserWindow;
+}
+
+void
+nsGlobalWindow::SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserWindow,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetBrowserDOMWindowOuter, (aBrowserWindow), aError, );
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::NotifyDefaultButtonLoaded(nsIDOMElement* aDefaultButton)
+{
+ FORWARD_TO_INNER_CHROME(NotifyDefaultButtonLoaded,
+ (aDefaultButton), NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<Element> defaultButton = do_QueryInterface(aDefaultButton);
+ NS_ENSURE_ARG(defaultButton);
+
+ ErrorResult rv;
+ NotifyDefaultButtonLoaded(*defaultButton, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsGlobalWindow::NotifyDefaultButtonLoaded(Element& aDefaultButton,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+#ifdef MOZ_XUL
+ // Don't snap to a disabled button.
+ nsCOMPtr<nsIDOMXULControlElement> xulControl =
+ do_QueryInterface(&aDefaultButton);
+ 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::FromUnknownRect(frame->GetScreenRect());
+
+ // 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);
+ }
+#else
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+#endif
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::GetMessageManager(nsIMessageBroadcaster** aManager)
+{
+ FORWARD_TO_INNER_CHROME(GetMessageManager, (aManager), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ NS_IF_ADDREF(*aManager = GetMessageManager(rv));
+ return rv.StealNSResult();
+}
+
+nsIMessageBroadcaster*
+nsGlobalWindow::GetMessageManager(ErrorResult& aError)
+{
+ MOZ_ASSERT(IsChromeWindow());
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+ nsGlobalChromeWindow* myself = static_cast<nsGlobalChromeWindow*>(this);
+ if (!myself->mMessageManager) {
+ nsCOMPtr<nsIMessageBroadcaster> globalMM =
+ do_GetService("@mozilla.org/globalmessagemanager;1");
+ myself->mMessageManager =
+ new nsFrameMessageManager(nullptr,
+ static_cast<nsFrameMessageManager*>(globalMM.get()),
+ MM_CHROME | MM_BROADCASTER);
+ }
+ return myself->mMessageManager;
+}
+
+NS_IMETHODIMP
+nsGlobalChromeWindow::GetGroupMessageManager(const nsAString& aGroup,
+ nsIMessageBroadcaster** aManager)
+{
+ FORWARD_TO_INNER_CHROME(GetGroupMessageManager, (aGroup, aManager), NS_ERROR_UNEXPECTED);
+
+ ErrorResult rv;
+ NS_IF_ADDREF(*aManager = GetGroupMessageManager(aGroup, rv));
+ return rv.StealNSResult();
+}
+
+nsIMessageBroadcaster*
+nsGlobalWindow::GetGroupMessageManager(const nsAString& aGroup,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsChromeWindow());
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ nsGlobalChromeWindow* myself = static_cast<nsGlobalChromeWindow*>(this);
+ nsCOMPtr<nsIMessageBroadcaster> messageManager =
+ myself->mGroupMessageManagers.Get(aGroup);
+
+ if (!messageManager) {
+ nsFrameMessageManager* parent =
+ static_cast<nsFrameMessageManager*>(GetMessageManager(aError));
+
+ messageManager = new nsFrameMessageManager(nullptr,
+ parent,
+ MM_CHROME | MM_BROADCASTER);
+ myself->mGroupMessageManagers.Put(aGroup, messageManager);
+ }
+
+ return messageManager;
+}
+
+nsresult
+nsGlobalChromeWindow::SetOpenerForInitialContentBrowser(mozIDOMWindowProxy* aOpenerWindow)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(!mOpenerForInitialContentBrowser);
+ mOpenerForInitialContentBrowser = aOpenerWindow;
+ return NS_OK;
+}
+
+nsresult
+nsGlobalChromeWindow::TakeOpenerForInitialContentBrowser(mozIDOMWindowProxy** aOpenerWindow)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ // Intentionally forget our own member
+ mOpenerForInitialContentBrowser.forget(aOpenerWindow);
+ return NS_OK;
+}
+
+// nsGlobalModalWindow implementation
+
+// QueryInterface implementation for nsGlobalModalWindow
+NS_INTERFACE_MAP_BEGIN(nsGlobalModalWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMModalContentWindow)
+NS_INTERFACE_MAP_END_INHERITING(nsGlobalWindow)
+
+NS_IMPL_ADDREF_INHERITED(nsGlobalModalWindow, nsGlobalWindow)
+NS_IMPL_RELEASE_INHERITED(nsGlobalModalWindow, nsGlobalWindow)
+
+
+void
+nsGlobalWindow::GetDialogArgumentsOuter(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRetval,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(IsModalContentWindow(),
+ "This should only be called on modal windows!");
+
+ if (!mDialogArguments) {
+ MOZ_ASSERT(mIsClosed, "This window should be closed!");
+ aRetval.setUndefined();
+ return;
+ }
+
+ // This does an internal origin check, and returns undefined if the subject
+ // does not subsumes the origin of the arguments.
+ JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
+ JSAutoCompartment ac(aCx, wrapper);
+ mDialogArguments->Get(aCx, wrapper, &aSubjectPrincipal, aRetval, aError);
+}
+
+void
+nsGlobalWindow::GetDialogArguments(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRetval,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetDialogArgumentsOuter,
+ (aCx, aRetval, aSubjectPrincipal, aError),
+ aError, );
+}
+
+/* static */ already_AddRefed<nsGlobalModalWindow>
+nsGlobalModalWindow::Create(nsGlobalWindow *aOuterWindow)
+{
+ RefPtr<nsGlobalModalWindow> window = new nsGlobalModalWindow(aOuterWindow);
+ window->InitWasOffline();
+ return window.forget();
+}
+
+NS_IMETHODIMP
+nsGlobalModalWindow::GetDialogArguments(nsIVariant **aArguments)
+{
+ FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetDialogArguments, (aArguments),
+ NS_ERROR_NOT_INITIALIZED);
+
+ // This does an internal origin check, and returns undefined if the subject
+ // does not subsumes the origin of the arguments.
+ return mDialogArguments->Get(nsContentUtils::SubjectPrincipal(), aArguments);
+}
+
+/* static */ already_AddRefed<nsGlobalWindow>
+nsGlobalWindow::Create(nsGlobalWindow *aOuterWindow)
+{
+ RefPtr<nsGlobalWindow> window = new nsGlobalWindow(aOuterWindow);
+ window->InitWasOffline();
+ return window.forget();
+}
+
+void
+nsGlobalWindow::InitWasOffline()
+{
+ mWasOffline = NS_IsOffline();
+}
+
+void
+nsGlobalWindow::GetReturnValueOuter(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(IsModalContentWindow(),
+ "This should only be called on modal windows!");
+
+ if (mReturnValue) {
+ JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
+ JSAutoCompartment ac(aCx, wrapper);
+ mReturnValue->Get(aCx, wrapper, &aSubjectPrincipal, aReturnValue, aError);
+ } else {
+ aReturnValue.setUndefined();
+ }
+}
+
+void
+nsGlobalWindow::GetReturnValue(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(GetReturnValueOuter,
+ (aCx, aReturnValue, aSubjectPrincipal, aError),
+ aError, );
+}
+
+NS_IMETHODIMP
+nsGlobalModalWindow::GetReturnValue(nsIVariant **aRetVal)
+{
+ FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetReturnValue, (aRetVal), NS_OK);
+
+ if (!mReturnValue) {
+ nsCOMPtr<nsIVariant> variant = CreateVoidVariant();
+ variant.forget(aRetVal);
+ return NS_OK;
+ }
+ return mReturnValue->Get(nsContentUtils::SubjectPrincipal(), aRetVal);
+}
+
+void
+nsGlobalWindow::SetReturnValueOuter(JSContext* aCx,
+ JS::Handle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+ MOZ_ASSERT(IsModalContentWindow(),
+ "This should only be called on modal windows!");
+
+ nsCOMPtr<nsIVariant> returnValue;
+ aError =
+ nsContentUtils::XPConnect()->JSToVariant(aCx, aReturnValue,
+ getter_AddRefs(returnValue));
+ if (!aError.Failed()) {
+ mReturnValue = new DialogValueHolder(&aSubjectPrincipal, returnValue);
+ }
+}
+
+void
+nsGlobalWindow::SetReturnValue(JSContext* aCx,
+ JS::Handle<JS::Value> aReturnValue,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError)
+{
+ FORWARD_TO_OUTER_OR_THROW(SetReturnValueOuter,
+ (aCx, aReturnValue, aSubjectPrincipal, aError),
+ aError, );
+}
+
+NS_IMETHODIMP
+nsGlobalModalWindow::SetReturnValue(nsIVariant *aRetVal)
+{
+ FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(SetReturnValue, (aRetVal), NS_OK);
+
+ mReturnValue = new DialogValueHolder(nsContentUtils::SubjectPrincipal(),
+ aRetVal);
+ return NS_OK;
+}
+
+/* static */
+bool
+nsGlobalWindow::IsModalContentWindow(JSContext* aCx, JSObject* aGlobal)
+{
+ return xpc::WindowOrNull(aGlobal)->IsModalContentWindow();
+}
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+int16_t
+nsGlobalWindow::Orientation(CallerType aCallerType) const
+{
+ return nsContentUtils::ResistFingerprinting(aCallerType) ?
+ 0 : WindowOrientationObserver::OrientationAngle();
+}
+#endif
+
+Console*
+nsGlobalWindow::GetConsole(ErrorResult& aRv)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mConsole) {
+ mConsole = Console::Create(AsInner(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return mConsole;
+}
+
+bool
+nsGlobalWindow::IsSecureContext() const
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ return JS_GetIsSecureContext(js::GetObjectCompartment(GetWrapperPreserveColor()));
+}
+
+bool
+nsGlobalWindow::IsSecureContextIfOpenerIgnored() const
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ return mIsSecureContextIfOpenerIgnored;
+}
+
+already_AddRefed<External>
+nsGlobalWindow::GetExternal(ErrorResult& aRv)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+#ifdef HAVE_SIDEBAR
+ if (!mExternal) {
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> jsImplObj(cx);
+ ConstructJSImplementation("@mozilla.org/sidebar;1", this, &jsImplObj, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ mExternal = new External(jsImplObj, this);
+ }
+
+ RefPtr<External> external = static_cast<External*>(mExternal.get());
+ return external.forget();
+#else
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+#endif
+}
+
+void
+nsGlobalWindow::GetSidebar(OwningExternalOrWindowProxy& aResult,
+ ErrorResult& aRv)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+#ifdef HAVE_SIDEBAR
+ // First check for a named frame named "sidebar"
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetChildWindow(NS_LITERAL_STRING("sidebar"));
+ if (domWindow) {
+ aResult.SetAsWindowProxy() = domWindow.forget();
+ return;
+ }
+
+ RefPtr<External> external = GetExternal(aRv);
+ if (external) {
+ aResult.SetAsExternal() = external;
+ }
+#else
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+#endif
+}
+
+void
+nsGlobalWindow::ClearDocumentDependentSlots(JSContext* aCx)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ // If JSAPI OOMs here, there is basically nothing we can do to recover safely.
+ if (!WindowBinding::ClearCachedDocumentValue(aCx, this) ||
+ !WindowBinding::ClearCachedPerformanceValue(aCx, this)) {
+ MOZ_CRASH("Unhandlable OOM while clearing document dependent slots.");
+ }
+}
+
+/* static */
+JSObject*
+nsGlobalWindow::CreateNamedPropertiesObject(JSContext *aCx,
+ JS::Handle<JSObject*> aProto)
+{
+ return WindowNamedPropertiesHandler::Create(aCx, aProto);
+}
+
+bool
+nsGlobalWindow::GetIsPrerendered()
+{
+ nsIDocShell* docShell = GetDocShell();
+ return docShell && docShell->GetIsPrerendered();
+}
+
+#ifdef MOZ_B2G
+void
+nsGlobalWindow::EnableNetworkEvent(EventMessage aEventMessage)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsIPermissionManager> permMgr =
+ services::GetPermissionManager();
+ if (!permMgr) {
+ NS_ERROR("No PermissionManager available!");
+ return;
+ }
+
+ uint32_t permission = nsIPermissionManager::DENY_ACTION;
+ permMgr->TestExactPermissionFromPrincipal(GetPrincipal(), "network-events",
+ &permission);
+
+ if (permission != nsIPermissionManager::ALLOW_ACTION) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os) {
+ NS_ERROR("ObserverService should be available!");
+ return;
+ }
+
+ switch (aEventMessage) {
+ case eNetworkUpload:
+ if (!mNetworkUploadObserverEnabled) {
+ mNetworkUploadObserverEnabled = true;
+ os->AddObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC, false);
+ }
+ break;
+ case eNetworkDownload:
+ if (!mNetworkDownloadObserverEnabled) {
+ mNetworkDownloadObserverEnabled = true;
+ os->AddObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC, false);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void
+nsGlobalWindow::DisableNetworkEvent(EventMessage aEventMessage)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os) {
+ return;
+ }
+
+ switch (aEventMessage) {
+ case eNetworkUpload:
+ if (mNetworkUploadObserverEnabled) {
+ mNetworkUploadObserverEnabled = false;
+ os->RemoveObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC);
+ }
+ break;
+ case eNetworkDownload:
+ if (mNetworkDownloadObserverEnabled) {
+ mNetworkDownloadObserverEnabled = false;
+ os->RemoveObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC);
+ }
+ break;
+ default:
+ break;
+ }
+}
+#endif // MOZ_B2G
+
+void
+nsGlobalWindow::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,
+ JS_STUBGETTER, JS_STUBSETTER)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+void
+nsGlobalWindow::GetReplaceableWindowCoord(JSContext* aCx,
+ nsGlobalWindow::WindowCoordGetter aGetter,
+ JS::MutableHandle<JS::Value> aRetval,
+ CallerType aCallerType,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ int32_t coord = (this->*aGetter)(aCallerType, aError);
+ if (!aError.Failed() &&
+ !ToJSValue(aCx, coord, aRetval)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+void
+nsGlobalWindow::SetReplaceableWindowCoord(JSContext* aCx,
+ nsGlobalWindow::WindowCoordSetter aSetter,
+ JS::Handle<JS::Value> aValue,
+ const char* aPropName,
+ ErrorResult& aError)
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ /*
+ * 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.
+ */
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (!outer || !outer->CanMoveResizeWindows(nsContentUtils::IsCallerChrome()) || outer->IsFrame()) {
+ RedefineProperty(aCx, aPropName, aValue, aError);
+ return;
+ }
+
+ int32_t value;
+ if (!ValueToPrimitive<int32_t, eDefault>(aCx, aValue, &value)) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ (this->*aSetter)(value, aError);
+}
+
+void
+nsGlobalWindow::FireOnNewGlobalObject()
+{
+ MOZ_ASSERT(IsInnerWindow());
+
+ // AutoEntryScript required to invoke debugger hook, which is a
+ // Gecko-specific concept at present.
+ AutoEntryScript aes(this, "nsGlobalWindow report new global");
+ JS::Rooted<JSObject*> global(aes.cx(), GetWrapper());
+ JS_FireOnNewGlobalObject(aes.cx(), global);
+}
+
+#ifdef _WINDOWS_
+#error "Never include windows.h in this file!"
+#endif
+
+already_AddRefed<Promise>
+nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage,
+ ErrorResult& aRv)
+{
+ if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+ }
+
+ return ImageBitmap::Create(this, aImage, Nothing(), aRv);
+}
+
+already_AddRefed<Promise>
+nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage,
+ int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh,
+ ErrorResult& aRv)
+{
+ if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return nullptr;
+ }
+
+ return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv);
+}
+
+already_AddRefed<mozilla::dom::Promise>
+nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage,
+ int32_t aOffset, int32_t aLength,
+ ImageBitmapFormat aFormat,
+ const Sequence<ChannelPixelLayout>& aLayout,
+ ErrorResult& aRv)
+{
+ if (!ImageBitmap::ExtensionsEnabled(nullptr, nullptr)) {
+ aRv.Throw(NS_ERROR_TYPE_ERR);
+ return nullptr;
+ }
+ if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
+ return ImageBitmap::Create(this, aImage, aOffset, aLength, aFormat, aLayout,
+ aRv);
+ } else {
+ aRv.Throw(NS_ERROR_TYPE_ERR);
+ return nullptr;
+ }
+}
+
+// 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
+nsGlobalWindow::CheckForDPIChange()
+{
+ if (mDocShell) {
+ RefPtr<nsPresContext> presContext;
+ mDocShell->GetPresContext(getter_AddRefs(presContext));
+ if (presContext) {
+ if (presContext->DeviceContext()->CheckDPIChange()) {
+ presContext->UIResolutionChanged();
+ }
+ }
+ }
+}
+
+mozilla::dom::TabGroup*
+nsGlobalWindow::TabGroupOuter()
+{
+ MOZ_RELEASE_ASSERT(IsOuterWindow());
+
+ // This method is valid both on inner and outer windows, which is a
+ // Outer windows lazily join TabGroups when requested. This is usually done
+ // because a document is getting its NodePrincipal, and asking for the
+ // TabGroup to determine its DocGroup.
+ if (!mTabGroup) {
+ // Get mOpener ourselves, instead of relying on GetOpenerWindowOuter,
+ // because that way we dodge the LegacyIsCallerChromeOrNativeCode() call
+ // which we want to return false.
+ nsCOMPtr<nsPIDOMWindowOuter> piOpener = do_QueryReferent(mOpener);
+ nsPIDOMWindowOuter* opener = GetSanitizedOpener(piOpener);
+ nsPIDOMWindowOuter* parent = GetScriptableParentOrNull();
+ MOZ_ASSERT(!parent || !opener, "Only one of parent and opener may be provided");
+
+ mozilla::dom::TabGroup* toJoin = nullptr;
+ if (GetDocShell()->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ toJoin = TabGroup::GetChromeTabGroup();
+ } else if (opener) {
+ toJoin = opener->TabGroup();
+ } else if (parent) {
+ toJoin = parent->TabGroup();
+ }
+ mTabGroup = mozilla::dom::TabGroup::Join(AsOuter(), toJoin);
+ }
+ MOZ_ASSERT(mTabGroup);
+
+#ifdef DEBUG
+ // Ensure that we don't recurse forever
+ if (!mIsValidatingTabGroup) {
+ mIsValidatingTabGroup = true;
+ // We only need to do this check if we aren't in the chrome tab group
+ if (mIsChrome) {
+ MOZ_ASSERT(mTabGroup == TabGroup::GetChromeTabGroup());
+ } else {
+ // Sanity check that our tabgroup matches our opener or parent.
+ RefPtr<nsPIDOMWindowOuter> parent = GetScriptableParentOrNull();
+ MOZ_ASSERT_IF(parent, parent->TabGroup() == mTabGroup);
+ nsCOMPtr<nsPIDOMWindowOuter> piOpener = do_QueryReferent(mOpener);
+ nsPIDOMWindowOuter* opener = GetSanitizedOpener(piOpener);
+ MOZ_ASSERT_IF(opener && Cast(opener) != this, opener->TabGroup() == mTabGroup);
+ }
+ mIsValidatingTabGroup = false;
+ }
+#endif
+
+ return mTabGroup;
+}
+
+mozilla::dom::TabGroup*
+nsGlobalWindow::TabGroupInner()
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ // If we don't have a TabGroup yet, try to get it from the outer window and
+ // cache it.
+ if (!mTabGroup) {
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ // This will never be called without either an outer window, or a cached tab group.
+ // This is because of the following:
+ // * This method is only called on inner windows
+ // * This method is called as a document is attached to it's script global
+ // by the document
+ // * Inner windows are created in nsGlobalWindow::SetNewDocument, which
+ // immediately sets a document, which will call this method, causing
+ // the TabGroup to be cached.
+ MOZ_RELEASE_ASSERT(outer, "Inner window without outer window has no cached tab group!");
+ mTabGroup = outer->TabGroup();
+ }
+ MOZ_ASSERT(mTabGroup);
+
+#ifdef DEBUG
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ MOZ_ASSERT_IF(outer, outer->TabGroup() == mTabGroup);
+#endif
+
+ return mTabGroup;
+}
+
+template<typename T>
+mozilla::dom::TabGroup*
+nsPIDOMWindow<T>::TabGroup()
+{
+ nsGlobalWindow* globalWindow =
+ static_cast<nsGlobalWindow*>(
+ reinterpret_cast<nsPIDOMWindow<nsISupports>*>(this));
+
+ if (IsInnerWindow()) {
+ return globalWindow->TabGroupInner();
+ }
+ return globalWindow->TabGroupOuter();
+}
+
+template<typename T>
+mozilla::dom::DocGroup*
+nsPIDOMWindow<T>::GetDocGroup()
+{
+ nsIDocument* doc = GetExtantDoc();
+ if (doc) {
+ return doc->GetDocGroup();
+ }
+ return nullptr;
+}
+
+nsGlobalWindow::TemporarilyDisableDialogs::TemporarilyDisableDialogs(
+ nsGlobalWindow* aWindow MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+{
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+ MOZ_ASSERT(aWindow);
+ nsGlobalWindow* topWindow = aWindow->GetScriptableTopInternal();
+ if (!topWindow) {
+ NS_ERROR("nsGlobalWindow::TemporarilyDisableDialogs used without a top "
+ "window?");
+ return;
+ }
+
+ // TODO: Warn if no top window?
+ topWindow = topWindow->GetCurrentInnerWindowInternal();
+ if (topWindow) {
+ mTopWindow = topWindow;
+ mSavedDialogsEnabled = mTopWindow->mAreDialogsEnabled;
+ mTopWindow->mAreDialogsEnabled = false;
+ }
+}
+
+nsGlobalWindow::TemporarilyDisableDialogs::~TemporarilyDisableDialogs()
+{
+ if (mTopWindow) {
+ mTopWindow->mAreDialogsEnabled = mSavedDialogsEnabled;
+ }
+}
+
+already_AddRefed<Worklet>
+nsGlobalWindow::CreateWorklet(ErrorResult& aRv)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ if (!mDoc) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<Worklet> worklet = new Worklet(AsInner(), mDoc->NodePrincipal());
+ return worklet.forget();
+}
+
+template class nsPIDOMWindow<mozIDOMWindowProxy>;
+template class nsPIDOMWindow<mozIDOMWindow>;
+template class nsPIDOMWindow<nsISupports>;
diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
new file mode 100644
index 000000000..eab91c2e4
--- /dev/null
+++ b/dom/base/nsGlobalWindow.h
@@ -0,0 +1,2141 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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___
+
+#include "nsPIDOMWindow.h"
+
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsRefPtrHashtable.h"
+#include "nsInterfaceHashtable.h"
+
+// Local Includes
+// Helper Classes
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsWeakReference.h"
+#include "nsDataHashtable.h"
+#include "nsJSThingHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+
+// Interfaces Needed
+#include "nsIBrowserDOMWindow.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsITimer.h"
+#include "nsIDOMModalContentWindow.h"
+#include "mozilla/EventListenerManager.h"
+#include "nsIPrincipal.h"
+#include "nsSize.h"
+#include "mozFlushType.h"
+#include "prclist.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsFrameMessageManager.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/GuardObjects.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/TimeStamp.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsIIdleObserver.h"
+#include "nsIDocument.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "Units.h"
+#include "nsComponentManagerUtils.h"
+#include "nsSize.h"
+#include "nsCheapSets.h"
+#include "mozilla/dom/ImageBitmapSource.h"
+#include "mozilla/dom/Timeout.h"
+
+#define DEFAULT_HOME_PAGE "www.mozilla.org"
+#define PREF_BROWSER_STARTUP_HOMEPAGE "browser.startup.homepage"
+
+// Amount of time allowed between alert/prompt/confirm before enabling
+// the stop dialog checkbox.
+#define DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT 3 // 3 sec
+
+// Maximum number of successive dialogs before we prompt users to disable
+// dialogs for this window.
+#define MAX_SUCCESSIVE_DIALOG_COUNT 5
+
+// Idle fuzz time upper limit
+#define MAX_IDLE_FUZZ_TIME_MS 90000
+
+// Min idle notification time in seconds.
+#define MIN_IDLE_NOTIFICATION_TIME_S 1
+
+class nsIArray;
+class nsIBaseWindow;
+class nsIContent;
+class nsICSSDeclaration;
+class nsIDocShellTreeOwner;
+class nsIDOMOfflineResourceList;
+class nsIScrollableFrame;
+class nsIControllers;
+class nsIJSID;
+class nsIScriptContext;
+class nsIScriptTimeoutHandler;
+class nsITimeoutHandler;
+class nsIWebBrowserChrome;
+class mozIDOMWindowProxy;
+
+class nsDOMWindowList;
+class nsScreen;
+class nsHistory;
+class nsGlobalWindowObserver;
+class nsGlobalWindow;
+class nsDOMWindowUtils;
+class nsIIdleService;
+struct nsRect;
+
+class nsWindowSizes;
+
+namespace mozilla {
+class DOMEventTargetHelper;
+class ThrottledEventQueue;
+namespace dom {
+class BarProp;
+struct ChannelPixelLayout;
+class Console;
+class Crypto;
+class CustomElementRegistry;
+class DocGroup;
+class External;
+class Function;
+class Gamepad;
+enum class ImageBitmapFormat : uint32_t;
+class IdleRequest;
+class IdleRequestCallback;
+class Location;
+class MediaQueryList;
+class MozSelfSupport;
+class Navigator;
+class OwningExternalOrWindowProxy;
+class Promise;
+class PostMessageEvent;
+struct RequestInit;
+class RequestOrUSVString;
+class Selection;
+class SpeechSynthesis;
+class TabGroup;
+class Timeout;
+class U2F;
+class VRDisplay;
+class VREventObserver;
+class WakeLock;
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+class WindowOrientationObserver;
+#endif
+class Worklet;
+namespace cache {
+class CacheStorage;
+} // namespace cache
+class IDBFactory;
+} // namespace dom
+} // namespace mozilla
+
+extern already_AddRefed<nsIScriptTimeoutHandler>
+NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
+ mozilla::dom::Function& aFunction,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ mozilla::ErrorResult& aError);
+
+extern already_AddRefed<nsIScriptTimeoutHandler>
+NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
+ const nsAString& aExpression,
+ mozilla::ErrorResult& aError);
+
+extern const js::Class OuterWindowProxyClass;
+
+struct IdleObserverHolder
+{
+ nsCOMPtr<nsIIdleObserver> mIdleObserver;
+ uint32_t mTimeInS;
+ bool mPrevNotificationIdle;
+
+ IdleObserverHolder()
+ : mTimeInS(0), mPrevNotificationIdle(false)
+ {
+ MOZ_COUNT_CTOR(IdleObserverHolder);
+ }
+
+ IdleObserverHolder(const IdleObserverHolder& aOther)
+ : mIdleObserver(aOther.mIdleObserver), mTimeInS(aOther.mTimeInS),
+ mPrevNotificationIdle(aOther.mPrevNotificationIdle)
+ {
+ MOZ_COUNT_CTOR(IdleObserverHolder);
+ }
+
+ bool operator==(const IdleObserverHolder& aOther) const {
+ return
+ mIdleObserver == aOther.mIdleObserver &&
+ mTimeInS == aOther.mTimeInS;
+ }
+
+ ~IdleObserverHolder()
+ {
+ MOZ_COUNT_DTOR(IdleObserverHolder);
+ }
+};
+
+// Helper class to manage modal dialog arguments and all their quirks.
+//
+// Given our clunky embedding APIs, modal dialog arguments need to be passed
+// as an nsISupports parameter to WindowWatcher, get stuck inside an array of
+// length 1, and then passed back to the newly-created dialog.
+//
+// However, we need to track both the caller-passed value as well as the
+// caller's, so that we can do an origin check (even for primitives) when the
+// value is accessed. This class encapsulates that magic.
+//
+// We also use the same machinery for |returnValue|, which needs similar origin
+// checks.
+class DialogValueHolder final : public nsISupports
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(DialogValueHolder)
+
+ DialogValueHolder(nsIPrincipal* aSubject, nsIVariant* aValue)
+ : mOrigin(aSubject)
+ , mValue(aValue) {}
+ nsresult Get(nsIPrincipal* aSubject, nsIVariant** aResult);
+ void Get(JSContext* aCx, JS::Handle<JSObject*> aScope, nsIPrincipal* aSubject,
+ JS::MutableHandle<JS::Value> aResult, mozilla::ErrorResult& aError);
+private:
+ virtual ~DialogValueHolder() {}
+
+ nsCOMPtr<nsIPrincipal> mOrigin;
+ nsCOMPtr<nsIVariant> mValue;
+};
+
+//*****************************************************************************
+// nsGlobalWindow: Global Object for Scripting
+//*****************************************************************************
+// Beware that all scriptable interfaces implemented by
+// nsGlobalWindow will be reachable from JS, if you make this class
+// implement new interfaces you better know what you're
+// doing. Security wise this is very sensitive code. --
+// jst@netscape.com
+
+// nsGlobalWindow 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).
+
+// NB: Currently nsPIDOMWindowInner and nsPIDOMWindowOuter are identical classes
+// with identical member variables and identical vtables, that only differ in
+// type name. nsGlobalWindow doesn't want to doubly inherit (and have two
+// copies of everything), and it also doesn't want to privilege one over
+// the other by making it possible to convert types through the C++ type system
+// instead of our accessor methods (AsInner and AsOuter) that do dynamic
+// checking. So we inherit from nsPIDOMWindow<nsISupports>, which is also
+// identical to both nsPIDOMWindowInner and nsPIDOMWindowOuter, but not
+// convertible to either.
+
+class nsGlobalWindow : public mozilla::dom::EventTarget,
+ public nsPIDOMWindow<nsISupports>,
+ private nsIDOMWindowInternal,
+ public nsIScriptGlobalObject,
+ public nsIScriptObjectPrincipal,
+ public nsSupportsWeakReference,
+ public nsIInterfaceRequestor,
+ public PRCListStr
+{
+public:
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::TimeDuration TimeDuration;
+ typedef nsDataHashtable<nsUint64HashKey, nsGlobalWindow*> WindowByIdTable;
+
+ static void
+ AssertIsOnMainThread()
+#ifdef DEBUG
+ ;
+#else
+ { }
+#endif
+
+ static nsGlobalWindow* Cast(nsPIDOMWindowInner* aPIWin) {
+ return static_cast<nsGlobalWindow*>(
+ reinterpret_cast<nsPIDOMWindow<nsISupports>*>(aPIWin));
+ }
+ static const nsGlobalWindow* Cast(const nsPIDOMWindowInner* aPIWin) {
+ return static_cast<const nsGlobalWindow*>(
+ reinterpret_cast<const nsPIDOMWindow<nsISupports>*>(aPIWin));
+ }
+ static nsGlobalWindow* Cast(mozIDOMWindow* aWin) {
+ return Cast(nsPIDOMWindowInner::From(aWin));
+ }
+ static nsGlobalWindow* Cast(nsPIDOMWindowOuter* aPIWin) {
+ return static_cast<nsGlobalWindow*>(
+ reinterpret_cast<nsPIDOMWindow<nsISupports>*>(aPIWin));
+ }
+ static nsGlobalWindow* Cast(mozIDOMWindowProxy* aWin) {
+ return Cast(nsPIDOMWindowOuter::From(aWin));
+ }
+
+ // 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 IsInnerWindow() || AsOuter()->EnsureInnerWindow() ? GetWrapper() : nullptr;
+ }
+
+ // nsIGlobalJSObjectHolder
+ virtual JSObject* GetGlobalJSObject() override;
+
+ // nsIScriptGlobalObject
+ JSObject *FastGetGlobalJSObject() const
+ {
+ return GetWrapperPreserveColor();
+ }
+
+ void TraceGlobalJSObject(JSTracer* aTrc);
+
+ virtual nsresult EnsureScriptEnvironment() override;
+
+ virtual nsIScriptContext *GetScriptContext() override;
+
+ void PoisonOuterWindowProxy(JSObject *aObject);
+
+ virtual bool IsBlackForCC(bool aTracingNeeded = true) override;
+
+ // nsIScriptObjectPrincipal
+ virtual nsIPrincipal* GetPrincipal() override;
+
+ // nsIDOMWindow
+ NS_DECL_NSIDOMWINDOW
+
+ nsresult
+ OpenJS(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, nsPIDOMWindowOuter **_retval);
+ void CaptureEvents();
+ void ReleaseEvents();
+ void Dump(const nsAString& aStr);
+ void SetResizable(bool aResizable) const;
+ nsresult GetScriptableContent(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal);
+
+ // nsIDOMEventTarget
+ NS_DECL_NSIDOMEVENTTARGET
+
+ virtual mozilla::EventListenerManager*
+ GetExistingListenerManager() const override;
+
+ virtual mozilla::EventListenerManager*
+ GetOrCreateListenerManager() override;
+
+ using mozilla::dom::EventTarget::RemoveEventListener;
+ virtual void AddEventListener(const nsAString& aType,
+ mozilla::dom::EventListener* aListener,
+ const mozilla::dom::AddEventListenerOptionsOrBoolean& aOptions,
+ const mozilla::dom::Nullable<bool>& aWantsUntrusted,
+ mozilla::ErrorResult& aRv) override;
+ virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
+
+ virtual nsIGlobalObject* GetOwnerGlobal() const override
+ {
+ if (IsOuterWindow()) {
+ return GetCurrentInnerWindowInternal();
+ }
+
+ return const_cast<nsGlobalWindow*>(this);
+ }
+
+ // nsPIDOMWindow
+ virtual nsPIDOMWindowOuter* GetPrivateRoot() override;
+
+ // Outer windows only.
+ virtual void ActivateOrDeactivate(bool aActivate) override;
+ virtual void SetActive(bool aActive) override;
+ virtual bool IsTopLevelWindowActive() override;
+ virtual void SetIsBackground(bool aIsBackground) override;
+ virtual void SetChromeEventHandler(mozilla::dom::EventTarget* aChromeEventHandler) override;
+
+ // Outer windows only.
+ virtual void SetInitialPrincipalToSubject() override;
+
+ virtual PopupControlState PushPopupControlState(PopupControlState state, bool aForce) const override;
+ virtual void PopPopupControlState(PopupControlState state) const override;
+ virtual PopupControlState GetPopupControlState() const override;
+
+ virtual already_AddRefed<nsISupports> SaveWindowState() override;
+ virtual nsresult RestoreWindowState(nsISupports *aState) override;
+
+ virtual void Suspend();
+ virtual void Resume();
+ virtual bool IsSuspended() const override;
+ virtual void Freeze();
+ virtual void Thaw();
+ virtual bool IsFrozen() const override;
+ virtual void SyncStateFromParentWindow();
+
+ virtual nsresult FireDelayedDOMEvents() override;
+ virtual bool IsRunningTimeout() override { return mTimeoutFiringDepth > 0; }
+
+ // Outer windows only.
+ virtual bool WouldReuseInnerWindow(nsIDocument* aNewDocument) override;
+
+ virtual void SetDocShell(nsIDocShell* aDocShell) override;
+ virtual void DetachFromDocShell() override;
+ virtual nsresult SetNewDocument(nsIDocument *aDocument,
+ nsISupports *aState,
+ bool aForceReuseInnerWindow) override;
+
+ // Outer windows only.
+ void DispatchDOMWindowCreated();
+
+ virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
+ bool aOriginalOpener) override;
+
+ // Outer windows only.
+ virtual void EnsureSizeUpToDate() override;
+
+ virtual void EnterModalState() override;
+ virtual void LeaveModalState() override;
+
+ // Outer windows only.
+ virtual bool CanClose() override;
+ virtual void ForceClose() override;
+
+ virtual void MaybeUpdateTouchState() override;
+
+ // Outer windows only.
+ virtual bool DispatchCustomEvent(const nsAString& aEventName) override;
+ bool DispatchResizeEvent(const mozilla::CSSIntSize& aSize);
+
+ // Inner windows only.
+ void RefreshCompartmentPrincipal();
+
+ // For accessing protected field mFullScreen
+ friend class FullscreenTransitionTask;
+
+ // Outer windows only.
+ virtual nsresult SetFullscreenInternal(
+ FullscreenReason aReason, bool aIsFullscreen) override final;
+ virtual void FinishFullscreenChange(bool aIsFullscreen) override final;
+ bool SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
+ nsIWidget* aWidget, nsIScreen* aScreen);
+ bool FullScreen() const;
+
+ // Inner windows only.
+ virtual void SetHasGamepadEventListener(bool aHasGamepad = true) override;
+ void NotifyVREventListenerAdded();
+ virtual void EventListenerAdded(nsIAtom* aType) override;
+
+ // nsIInterfaceRequestor
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // WebIDL interface.
+ already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
+ already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
+
+ static bool IsPrivilegedChromeWindow(JSContext* /* unused */, JSObject* aObj);
+
+ static bool IsShowModalDialogEnabled(JSContext* /* unused */ = nullptr,
+ JSObject* /* unused */ = nullptr);
+
+ bool DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::Handle<jsid> aId,
+ JS::MutableHandle<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, nsTArray<nsString>& aNames,
+ mozilla::ErrorResult& aRv);
+
+ // Object Management
+ static already_AddRefed<nsGlobalWindow> Create(nsGlobalWindow *aOuterWindow);
+
+ static nsGlobalWindow *FromSupports(nsISupports *supports)
+ {
+ // Make sure this matches the casts we do in QueryInterface().
+ return (nsGlobalWindow *)(mozilla::dom::EventTarget *)supports;
+ }
+ static nsGlobalWindow *FromWrapper(nsIXPConnectWrappedNative *wrapper)
+ {
+ return FromSupports(wrapper->Native());
+ }
+ already_AddRefed<nsPIDOMWindowOuter> GetTop() override;
+ nsPIDOMWindowOuter* GetScriptableTop() override;
+ inline nsGlobalWindow *GetTopInternal()
+ {
+ nsGlobalWindow* outer = IsOuterWindow() ? this : GetOuterWindowInternal();
+ nsCOMPtr<nsPIDOMWindowOuter> top = outer ? outer->GetTop() : nullptr;
+ if (top) {
+ return nsGlobalWindow::Cast(top);
+ }
+ return nullptr;
+ }
+
+ inline nsGlobalWindow* GetScriptableTopInternal()
+ {
+ nsPIDOMWindowOuter* top = GetScriptableTop();
+ return nsGlobalWindow::Cast(top);
+ }
+
+ nsPIDOMWindowOuter* GetChildWindow(const nsAString& aName);
+
+ // These return true if we've reached the state in this top level window
+ // where we ask the user if further dialogs should be blocked.
+ //
+ // DialogsAreBeingAbused must be called on the scriptable top inner window.
+ //
+ // ShouldPromptToBlockDialogs is implemented in terms of
+ // DialogsAreBeingAbused, and will get the scriptable top inner window
+ // automatically.
+ // Outer windows only.
+ bool ShouldPromptToBlockDialogs();
+ // Inner windows only.
+ bool DialogsAreBeingAbused();
+
+ // These functions are used for controlling and determining whether dialogs
+ // (alert, prompt, confirm) are currently allowed in this window. 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:
+ // Takes an inner _or_ outer window.
+ explicit TemporarilyDisableDialogs(nsGlobalWindow* aWindow
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+ ~TemporarilyDisableDialogs();
+
+ private:
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+ // Always an inner window; this is the window 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<nsGlobalWindow> mTopWindow;
+ // 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 mTopWindow.
+ bool mSavedDialogsEnabled;
+ };
+ friend class TemporarilyDisableDialogs;
+
+ nsIScriptContext *GetContextInternal()
+ {
+ if (mOuterWindow) {
+ return GetOuterWindowInternal()->mContext;
+ }
+
+ return mContext;
+ }
+
+ nsGlobalWindow *GetOuterWindowInternal()
+ {
+ return nsGlobalWindow::Cast(GetOuterWindow());
+ }
+
+ nsGlobalWindow* GetCurrentInnerWindowInternal() const
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ return nsGlobalWindow::Cast(mInnerWindow);
+ }
+
+ nsGlobalWindow* EnsureInnerWindowInternal()
+ {
+ return nsGlobalWindow::Cast(AsOuter()->EnsureInnerWindow());
+ }
+
+ bool IsCreatingInnerWindow() const
+ {
+ return mCreatingInnerWindow;
+ }
+
+ bool IsChromeWindow() const
+ {
+ return mIsChrome;
+ }
+
+ using nsPIDOMWindow::IsModalContentWindow;
+ static bool IsModalContentWindow(JSContext* aCx, JSObject* aGlobal);
+
+ // 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);
+
+ // Outer windows only.
+ void UnblockScriptedClosing();
+
+ static void Init();
+ static void ShutDown();
+ static void CleanupCachedXBLHandlers(nsGlobalWindow* aWindow);
+ static bool IsCallerChrome();
+
+ friend class WindowStateHolder;
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsGlobalWindow,
+ nsIDOMEventTarget)
+
+#ifdef DEBUG
+ // Call Unlink on this window. This may cause bad things to happen, so use
+ // with caution.
+ void RiskyUnlink();
+#endif
+
+ virtual JSObject*
+ GetCachedXBLPrototypeHandler(nsXBLPrototypeHandler* aKey) override;
+
+ virtual void
+ CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey,
+ JS::Handle<JSObject*> aHandler) override;
+
+ virtual bool TakeFocus(bool aFocus, uint32_t aFocusMethod) override;
+ virtual void SetReadyForFocus() override;
+ 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) || defined(MOZ_WIDGET_GONK)
+ virtual void EnableOrientationChangeListener() override;
+ virtual void DisableOrientationChangeListener() override;
+#endif
+
+ virtual void EnableTimeChangeNotifications() override;
+ virtual void DisableTimeChangeNotifications() override;
+
+#ifdef MOZ_B2G
+ // Inner windows only.
+ virtual void EnableNetworkEvent(mozilla::EventMessage aEventMessage) override;
+ virtual void DisableNetworkEvent(
+ mozilla::EventMessage aEventMessage) override;
+#endif // MOZ_B2G
+
+ virtual nsresult SetArguments(nsIArray* aArguments) override;
+
+ void MaybeForgiveSpamCount();
+ bool IsClosedOrClosing() {
+ return (mIsClosed ||
+ mInClose ||
+ mHavePendingClose ||
+ mCleanedUp);
+ }
+
+ bool
+ HadOriginalOpener() const
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ return mHadOriginalOpener;
+ }
+
+ bool
+ IsTopLevelWindow()
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ nsPIDOMWindowOuter* parentWindow = GetScriptableTop();
+ return parentWindow == this->AsOuter();
+ }
+
+ virtual void
+ FirePopupBlockedEvent(nsIDocument* aDoc,
+ nsIURI* aPopupURI,
+ const nsAString& aPopupWindowName,
+ const nsAString& aPopupWindowFeatures) override;
+
+ virtual uint32_t GetSerial() override {
+ return mSerial;
+ }
+
+ static nsGlobalWindow* GetOuterWindowWithId(uint64_t aWindowID) {
+ AssertIsOnMainThread();
+
+ if (!sWindowsById) {
+ return nullptr;
+ }
+
+ nsGlobalWindow* outerWindow = sWindowsById->Get(aWindowID);
+ return outerWindow && !outerWindow->IsInnerWindow() ? outerWindow : nullptr;
+ }
+
+ static nsGlobalWindow* GetInnerWindowWithId(uint64_t aInnerWindowID) {
+ AssertIsOnMainThread();
+
+ if (!sWindowsById) {
+ return nullptr;
+ }
+
+ nsGlobalWindow* innerWindow = sWindowsById->Get(aInnerWindowID);
+ return innerWindow && innerWindow->IsInnerWindow() ? innerWindow : nullptr;
+ }
+
+ static WindowByIdTable* GetWindowsTable() {
+ AssertIsOnMainThread();
+
+ return sWindowsById;
+ }
+
+ void AddSizeOfIncludingThis(nsWindowSizes* aWindowSizes) const;
+
+ void UnmarkGrayTimers();
+
+ // Inner windows only.
+ void AddEventTargetObject(mozilla::DOMEventTargetHelper* aObject);
+ void RemoveEventTargetObject(mozilla::DOMEventTargetHelper* aObject);
+
+ void NotifyIdleObserver(IdleObserverHolder* aIdleObserverHolder,
+ bool aCallOnidle);
+ nsresult HandleIdleActiveEvent();
+ bool ContainsIdleObserver(nsIIdleObserver* aIdleObserver, uint32_t timeInS);
+ void HandleIdleObserverCallback();
+
+ void AllowScriptsToClose()
+ {
+ mAllowScriptsToClose = true;
+ }
+
+ enum SlowScriptResponse {
+ ContinueSlowScript = 0,
+ ContinueSlowScriptAndKeepNotifying,
+ AlwaysContinueSlowScript,
+ KillSlowScript
+ };
+ SlowScriptResponse ShowSlowScriptDialog();
+
+#ifdef MOZ_GAMEPAD
+ // Inner windows only.
+ void AddGamepad(uint32_t aIndex, mozilla::dom::Gamepad* aGamepad);
+ void RemoveGamepad(uint32_t aIndex);
+ void GetGamepads(nsTArray<RefPtr<mozilla::dom::Gamepad> >& aGamepads);
+ already_AddRefed<mozilla::dom::Gamepad> GetGamepad(uint32_t aIndex);
+ void SetHasSeenGamepadInput(bool aHasSeen);
+ bool HasSeenGamepadInput();
+ void SyncGamepadState();
+#endif
+
+ // 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();
+
+ // 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();
+
+#define EVENT(name_, id_, type_, struct_) \
+ mozilla::dom::EventHandlerNonNull* GetOn##name_() \
+ { \
+ mozilla::EventListenerManager* elm = GetExistingListenerManager(); \
+ return elm ? elm->GetEventHandler(nsGkAtoms::on##name_, EmptyString()) \
+ : nullptr; \
+ } \
+ void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler) \
+ { \
+ mozilla::EventListenerManager* elm = GetOrCreateListenerManager(); \
+ if (elm) { \
+ elm->SetEventHandler(nsGkAtoms::on##name_, EmptyString(), 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);
+
+ nsGlobalWindow* Window();
+ nsGlobalWindow* Self();
+ nsIDocument* GetDocument()
+ {
+ return GetDoc();
+ }
+ void GetNameOuter(nsAString& aName);
+ void GetName(nsAString& aName, mozilla::ErrorResult& aError);
+ void SetNameOuter(const nsAString& aName, mozilla::ErrorResult& aError);
+ void SetName(const nsAString& aName, mozilla::ErrorResult& aError);
+ mozilla::dom::Location* GetLocation(mozilla::ErrorResult& aError);
+ nsIDOMLocation* GetLocation() override;
+ nsHistory* GetHistory(mozilla::ErrorResult& aError);
+ mozilla::dom::CustomElementRegistry* CustomElements() override;
+ 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 GetStatusOuter(nsAString& aStatus);
+ void GetStatus(nsAString& aStatus, mozilla::ErrorResult& aError);
+ void SetStatusOuter(const nsAString& aStatus);
+ void SetStatus(const nsAString& aStatus, mozilla::ErrorResult& aError);
+ void CloseOuter(bool aTrustedCaller);
+ void Close(mozilla::ErrorResult& aError);
+ nsresult Close() override;
+ bool GetClosedOuter();
+ bool GetClosed(mozilla::ErrorResult& aError);
+ bool Closed() override;
+ void StopOuter(mozilla::ErrorResult& aError);
+ void Stop(mozilla::ErrorResult& aError);
+ void FocusOuter(mozilla::ErrorResult& aError);
+ void Focus(mozilla::ErrorResult& aError);
+ nsresult Focus() override;
+ void BlurOuter();
+ void Blur(mozilla::ErrorResult& aError);
+ already_AddRefed<nsPIDOMWindowOuter> GetFramesOuter();
+ already_AddRefed<nsIDOMWindowCollection> GetFrames() override;
+ already_AddRefed<nsPIDOMWindowOuter> GetFrames(mozilla::ErrorResult& aError);
+ uint32_t Length();
+ already_AddRefed<nsPIDOMWindowOuter> GetTopOuter();
+ already_AddRefed<nsPIDOMWindowOuter> GetTop(mozilla::ErrorResult& aError);
+
+ nsresult GetPrompter(nsIPrompt** aPrompt) override;
+protected:
+ explicit nsGlobalWindow(nsGlobalWindow *aOuterWindow);
+ nsPIDOMWindowOuter* GetOpenerWindowOuter();
+ // Initializes the mWasOffline member variable
+ void InitWasOffline();
+public:
+ nsPIDOMWindowOuter*
+ GetSanitizedOpener(nsPIDOMWindowOuter* aOpener);
+
+ nsPIDOMWindowOuter* GetOpenerWindow(mozilla::ErrorResult& aError);
+ void GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aError);
+ already_AddRefed<nsPIDOMWindowOuter> GetOpener() override;
+ void SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener,
+ mozilla::ErrorResult& aError);
+ already_AddRefed<nsPIDOMWindowOuter> GetParentOuter();
+ already_AddRefed<nsPIDOMWindowOuter> GetParent(mozilla::ErrorResult& aError);
+ already_AddRefed<nsPIDOMWindowOuter> GetParent() override;
+ nsPIDOMWindowOuter* GetScriptableParent() override;
+ nsPIDOMWindowOuter* GetScriptableParentOrNull() override;
+ mozilla::dom::Element*
+ GetFrameElementOuter(nsIPrincipal& aSubjectPrincipal);
+ mozilla::dom::Element*
+ GetFrameElement(nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ already_AddRefed<nsIDOMElement> GetFrameElement() override;
+ already_AddRefed<nsPIDOMWindowOuter>
+ OpenOuter(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ mozilla::ErrorResult& aError);
+ already_AddRefed<nsPIDOMWindowOuter>
+ Open(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ mozilla::ErrorResult& aError);
+ nsresult Open(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ nsIDocShellLoadInfo* aLoadInfo,
+ bool aForceNoOpener,
+ nsPIDOMWindowOuter **_retval) override;
+ mozilla::dom::Navigator* GetNavigator(mozilla::ErrorResult& aError);
+ nsIDOMNavigator* GetNavigator() override;
+ nsIDOMOfflineResourceList* GetApplicationCache(mozilla::ErrorResult& aError);
+ already_AddRefed<nsIDOMOfflineResourceList> GetApplicationCache() override;
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+ int16_t Orientation(mozilla::dom::CallerType aCallerType) const;
+#endif
+
+ mozilla::dom::Console* GetConsole(mozilla::ErrorResult& aRv);
+
+ // https://w3c.github.io/webappsec-secure-contexts/#dom-window-issecurecontext
+ bool IsSecureContext() const;
+ bool IsSecureContextIfOpenerIgnored() const;
+
+ void GetSidebar(mozilla::dom::OwningExternalOrWindowProxy& aResult,
+ mozilla::ErrorResult& aRv);
+ already_AddRefed<mozilla::dom::External> GetExternal(mozilla::ErrorResult& aRv);
+
+ // Exposed only for testing
+ static bool
+ TokenizeDialogOptions(nsAString& aToken, nsAString::const_iterator& aIter,
+ nsAString::const_iterator aEnd);
+ static void
+ ConvertDialogOptions(const nsAString& aOptions, nsAString& aResult);
+
+ // Exposed only for testing
+ already_AddRefed<mozilla::dom::Worklet>
+ CreateWorklet(mozilla::ErrorResult& aRv);
+
+protected:
+ bool AlertOrConfirm(bool aAlert, const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+public:
+ void Alert(nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void AlertOuter(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void Alert(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ bool ConfirmOuter(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ bool Confirm(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void PromptOuter(const nsAString& aMessage, const nsAString& aInitial,
+ nsAString& aReturn,
+ 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::ErrorResult& aRv);
+ void PrintOuter(mozilla::ErrorResult& aError);
+ void Print(mozilla::ErrorResult& aError);
+ void ShowModalDialog(JSContext* aCx, const nsAString& aUrl,
+ JS::Handle<JS::Value> aArgument,
+ const nsAString& aOptions,
+ JS::MutableHandle<JS::Value> aRetval,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const mozilla::dom::Optional<mozilla::dom::Sequence<JS::Value > >& aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ int32_t SetTimeout(JSContext* aCx, mozilla::dom::Function& aFunction,
+ int32_t aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ mozilla::ErrorResult& aError);
+ int32_t SetTimeout(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& /* unused */,
+ mozilla::ErrorResult& aError);
+ void ClearTimeout(int32_t aHandle);
+ int32_t SetInterval(JSContext* aCx, mozilla::dom::Function& aFunction,
+ const mozilla::dom::Optional<int32_t>& aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ mozilla::ErrorResult& aError);
+ int32_t SetInterval(JSContext* aCx, const nsAString& aHandler,
+ const mozilla::dom::Optional<int32_t>& aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& /* unused */,
+ mozilla::ErrorResult& aError);
+ void ClearInterval(int32_t aHandle);
+ void Atob(const nsAString& aAsciiBase64String, nsAString& aBinaryData,
+ mozilla::ErrorResult& aError);
+ void Btoa(const nsAString& aBinaryData, nsAString& aAsciiBase64String,
+ mozilla::ErrorResult& aError);
+ mozilla::dom::DOMStorage* GetSessionStorage(mozilla::ErrorResult& aError);
+ mozilla::dom::DOMStorage*
+ GetLocalStorage(mozilla::ErrorResult& aError);
+ mozilla::dom::Selection* GetSelectionOuter();
+ mozilla::dom::Selection* GetSelection(mozilla::ErrorResult& aError);
+ already_AddRefed<nsISelection> GetSelection() override;
+ mozilla::dom::IDBFactory* GetIndexedDB(mozilla::ErrorResult& aError);
+ already_AddRefed<nsICSSDeclaration>
+ GetComputedStyle(mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
+ mozilla::ErrorResult& aError) override;
+ already_AddRefed<mozilla::dom::MediaQueryList> MatchMediaOuter(const nsAString& aQuery);
+ already_AddRefed<mozilla::dom::MediaQueryList> MatchMedia(const nsAString& aQuery,
+ mozilla::ErrorResult& aError);
+ nsScreen* GetScreen(mozilla::ErrorResult& aError);
+ nsIDOMScreen* GetScreen() override;
+ void MoveToOuter(int32_t aXPos, int32_t aYPos, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void MoveTo(int32_t aXPos, int32_t aYPos, mozilla::ErrorResult& aError);
+ void MoveByOuter(int32_t aXDif, int32_t aYDif, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void MoveBy(int32_t aXDif, int32_t aYDif, mozilla::ErrorResult& aError);
+ nsresult MoveBy(int32_t aXDif, int32_t aYDif) override;
+ void ResizeToOuter(int32_t aWidth, int32_t aHeight, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void ResizeTo(int32_t aWidth, int32_t aHeight,
+ mozilla::ErrorResult& aError);
+ void ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void ResizeBy(int32_t aWidthDif, int32_t aHeightDif,
+ 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);
+ int32_t GetScrollXOuter();
+ int32_t GetScrollX(mozilla::ErrorResult& aError);
+ int32_t GetPageXOffset(mozilla::ErrorResult& aError)
+ {
+ return GetScrollX(aError);
+ }
+ int32_t GetScrollYOuter();
+ int32_t GetScrollY(mozilla::ErrorResult& aError);
+ int32_t GetPageYOffset(mozilla::ErrorResult& aError)
+ {
+ return GetScrollY(aError);
+ }
+ void MozRequestOverfill(mozilla::dom::OverfillCallback& aCallback, mozilla::ErrorResult& 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);
+ int32_t RequestAnimationFrame(mozilla::dom::FrameRequestCallback& aCallback,
+ mozilla::ErrorResult& aError);
+ 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
+ already_AddRefed<nsICSSDeclaration>
+ GetDefaultComputedStyle(mozilla::dom::Element& aElt,
+ const nsAString& aPseudoElt,
+ mozilla::ErrorResult& aError);
+ void SizeToContentOuter(mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void SizeToContent(mozilla::ErrorResult& aError);
+ mozilla::dom::Crypto* GetCrypto(mozilla::ErrorResult& aError);
+ mozilla::dom::U2F* GetU2f(mozilla::ErrorResult& aError);
+ nsIControllers* GetControllersOuter(mozilla::ErrorResult& aError);
+ nsIControllers* GetControllers(mozilla::ErrorResult& aError);
+ nsresult GetControllers(nsIControllers** aControllers) override;
+ mozilla::dom::Element* GetRealFrameElementOuter();
+ mozilla::dom::Element* GetRealFrameElement(mozilla::ErrorResult& aError);
+ float GetMozInnerScreenXOuter(mozilla::dom::CallerType aCallerType);
+ float GetMozInnerScreenX(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ float GetMozInnerScreenYOuter(mozilla::dom::CallerType aCallerType);
+ float GetMozInnerScreenY(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ float GetDevicePixelRatioOuter(mozilla::dom::CallerType aCallerType);
+ float GetDevicePixelRatio(mozilla::dom::CallerType aCallerType,
+ 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 GetFullScreenOuter();
+ bool GetFullScreen(mozilla::ErrorResult& aError);
+ bool GetFullScreen() override;
+ void SetFullScreenOuter(bool aFullScreen, mozilla::ErrorResult& aError);
+ void SetFullScreen(bool aFullScreen, mozilla::ErrorResult& aError);
+ nsresult SetFullScreen(bool aFullScreen) override;
+ void BackOuter(mozilla::ErrorResult& aError);
+ void Back(mozilla::ErrorResult& aError);
+ void ForwardOuter(mozilla::ErrorResult& aError);
+ void Forward(mozilla::ErrorResult& aError);
+ void HomeOuter(mozilla::ErrorResult& aError);
+ void Home(mozilla::ErrorResult& aError);
+ bool FindOuter(const nsAString& aString, bool aCaseSensitive, bool aBackwards,
+ bool aWrapAround, bool aWholeWord, bool aSearchInFrames,
+ bool aShowDialog, mozilla::ErrorResult& aError);
+ bool Find(const nsAString& aString, bool aCaseSensitive, bool aBackwards,
+ bool aWrapAround, bool aWholeWord, bool aSearchInFrames,
+ bool aShowDialog, mozilla::ErrorResult& aError);
+ uint64_t GetMozPaintCountOuter();
+ uint64_t GetMozPaintCount(mozilla::ErrorResult& aError);
+
+ bool ShouldResistFingerprinting();
+
+ mozilla::dom::MozSelfSupport* GetMozSelfSupport(mozilla::ErrorResult& aError);
+
+ already_AddRefed<nsPIDOMWindowOuter>
+ OpenDialogOuter(JSContext* aCx,
+ const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ const mozilla::dom::Sequence<JS::Value>& aExtraArgument,
+ mozilla::ErrorResult& aError);
+ already_AddRefed<nsPIDOMWindowOuter>
+ OpenDialog(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,
+ nsPIDOMWindowOuter** _retval) override;
+ nsresult UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason) override;
+
+ mozilla::ThrottledEventQueue* GetThrottledEventQueue() override;
+
+ already_AddRefed<nsPIDOMWindowOuter>
+ GetContentInternal(mozilla::ErrorResult& aError, bool aUnprivilegedCaller);
+ void GetContentOuter(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ mozilla::ErrorResult& aError);
+ void GetContent(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ mozilla::ErrorResult& aError);
+ already_AddRefed<nsPIDOMWindowOuter> GetContent()
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ mozilla::ErrorResult ignored;
+ nsCOMPtr<nsPIDOMWindowOuter> win =
+ GetContentInternal(ignored, /* aUnprivilegedCaller = */ false);
+ ignored.SuppressException();
+ return win.forget();
+ }
+
+ void Get_content(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ mozilla::ErrorResult& aError)
+ {
+ if (mDoc) {
+ mDoc->WarnOnceAbout(nsIDocument::eWindow_Content);
+ }
+ GetContent(aCx, aRetval, aError);
+ }
+
+ already_AddRefed<mozilla::dom::Promise>
+ CreateImageBitmap(const mozilla::dom::ImageBitmapSource& aImage,
+ 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,
+ mozilla::ErrorResult& aRv);
+
+ already_AddRefed<mozilla::dom::Promise>
+ CreateImageBitmap(const mozilla::dom::ImageBitmapSource& aImage,
+ int32_t aOffset, int32_t aLength,
+ mozilla::dom::ImageBitmapFormat aFormat,
+ const mozilla::dom::Sequence<mozilla::dom::ChannelPixelLayout>& aLayout,
+ mozilla::ErrorResult& aRv);
+
+
+ // ChromeWindow bits. Do NOT call these unless your window is in
+ // fact an nsGlobalChromeWindow.
+ uint16_t WindowState();
+ nsIBrowserDOMWindow* GetBrowserDOMWindowOuter();
+ nsIBrowserDOMWindow* GetBrowserDOMWindow(mozilla::ErrorResult& aError);
+ void SetBrowserDOMWindowOuter(nsIBrowserDOMWindow* aBrowserWindow);
+ void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserWindow,
+ mozilla::ErrorResult& aError);
+ void GetAttention(mozilla::ErrorResult& aError);
+ void GetAttentionWithCycleCount(int32_t aCycleCount,
+ mozilla::ErrorResult& aError);
+ void SetCursorOuter(const nsAString& aCursor, mozilla::ErrorResult& aError);
+ void SetCursor(const nsAString& aCursor, mozilla::ErrorResult& aError);
+ void Maximize();
+ void Minimize();
+ void Restore();
+ void NotifyDefaultButtonLoaded(mozilla::dom::Element& aDefaultButton,
+ mozilla::ErrorResult& aError);
+ nsIMessageBroadcaster* GetMessageManager(mozilla::ErrorResult& aError);
+ nsIMessageBroadcaster* GetGroupMessageManager(const nsAString& aGroup,
+ mozilla::ErrorResult& aError);
+ void BeginWindowMove(mozilla::dom::Event& aMouseDownEvent,
+ mozilla::dom::Element* aPanel,
+ mozilla::ErrorResult& aError);
+
+ void GetDialogArgumentsOuter(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+ void GetDialogArguments(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
+ nsIPrincipal& aSubjectPrincipal,
+ 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, nsIJSID* aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aError);
+
+ already_AddRefed<nsWindowRoot> GetWindowRootOuter();
+ already_AddRefed<nsWindowRoot> GetWindowRoot(mozilla::ErrorResult& aError);
+
+ mozilla::dom::Performance* GetPerformance();
+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.
+ typedef int32_t
+ (nsGlobalWindow::*WindowCoordGetter)(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult&);
+ typedef void (nsGlobalWindow::*WindowCoordSetter)(int32_t,
+ mozilla::ErrorResult&);
+ void GetReplaceableWindowCoord(JSContext* aCx, WindowCoordGetter aGetter,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetReplaceableWindowCoord(JSContext* aCx, WindowCoordSetter aSetter,
+ JS::Handle<JS::Value> aValue,
+ const char* aPropName,
+ mozilla::ErrorResult& aError);
+ // And the implementations of WindowCoordGetter/WindowCoordSetter.
+public:
+ int32_t GetInnerWidthOuter(mozilla::ErrorResult& aError);
+protected:
+ int32_t GetInnerWidth(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsresult GetInnerWidth(int32_t* aWidth) override;
+ void SetInnerWidthOuter(int32_t aInnerWidth, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void SetInnerWidth(int32_t aInnerWidth, mozilla::ErrorResult& aError);
+public:
+ int32_t GetInnerHeightOuter(mozilla::ErrorResult& aError);
+protected:
+ int32_t GetInnerHeight(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ nsresult GetInnerHeight(int32_t* aHeight) override;
+ void SetInnerHeightOuter(int32_t aInnerHeight, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void SetInnerHeight(int32_t aInnerHeight, mozilla::ErrorResult& aError);
+ int32_t GetScreenXOuter(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetScreenX(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenXOuter(int32_t aScreenX, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void SetScreenX(int32_t aScreenX, mozilla::ErrorResult& aError);
+ int32_t GetScreenYOuter(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetScreenY(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetScreenYOuter(int32_t aScreenY, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void SetScreenY(int32_t aScreenY, mozilla::ErrorResult& aError);
+ int32_t GetOuterWidthOuter(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetOuterWidth(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterWidthOuter(int32_t aOuterWidth, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void SetOuterWidth(int32_t aOuterWidth, mozilla::ErrorResult& aError);
+ int32_t GetOuterHeightOuter(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ int32_t GetOuterHeight(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterHeightOuter(int32_t aOuterHeight, mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ void SetOuterHeight(int32_t aOuterHeight, mozilla::ErrorResult& aError);
+
+ // Array of idle observers that are notified of idle events.
+ nsTObserverArray<IdleObserverHolder> mIdleObservers;
+
+ // Idle timer used for function callbacks to notify idle observers.
+ nsCOMPtr<nsITimer> mIdleTimer;
+
+ // Idle fuzz time added to idle timer callbacks.
+ uint32_t mIdleFuzzFactor;
+
+ // Index in mArrayIdleObservers
+ // Next idle observer to notify user idle status
+ int32_t mIdleCallbackIndex;
+
+ // If false then the topic is "active"
+ // If true then the topic is "idle"
+ bool mCurrentlyIdle;
+
+ // Set to true when a fuzz time needs to be applied
+ // to active notifications to the idle observer.
+ bool mAddActiveEventFuzzTime;
+
+ nsCOMPtr <nsIIdleService> mIdleService;
+
+ RefPtr<mozilla::dom::WakeLock> mWakeLock;
+
+ static bool sIdleObserversAPIFuzzTimeDisabled;
+
+ friend class HashchangeCallback;
+ friend class mozilla::dom::BarProp;
+
+ // Object Management
+ virtual ~nsGlobalWindow();
+ void DropOuterWindowDocs();
+ void CleanUp();
+ void ClearControllers();
+ // Outer windows only.
+ void FinalClose();
+
+ inline void MaybeClearInnerWindow(nsGlobalWindow* aExpectedInner)
+ {
+ if(mInnerWindow == aExpectedInner->AsInner()) {
+ mInnerWindow = nullptr;
+ }
+ }
+
+ void FreeInnerObjects();
+ nsGlobalWindow *CallerInnerWindow();
+
+ // Only to be called on an inner window.
+ // aDocument must not be null.
+ void InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument);
+
+ // Inner windows only.
+ nsresult DefineArgumentsProperty(nsIArray *aArguments);
+
+ // Get the parent, returns null if this is a toplevel window
+ nsPIDOMWindowOuter* GetParentInternal();
+
+public:
+ // popup tracking
+ bool IsPopupSpamWindow()
+ {
+ if (IsInnerWindow() && !mOuterWindow) {
+ return false;
+ }
+
+ return GetOuterWindowInternal()->mIsPopupSpam;
+ }
+
+ // Outer windows only.
+ void SetIsPopupSpamWindow(bool aIsPopupSpam);
+
+protected:
+ // Window Control Functions
+
+ // Outer windows only.
+ virtual nsresult
+ OpenNoNavigate(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ nsPIDOMWindowOuter** _retval) override;
+
+private:
+ /**
+ * @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 aLoadInfo 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 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,
+ nsIDocShellLoadInfo* aLoadInfo,
+ bool aForceNoOpener,
+ nsPIDOMWindowOuter **aReturn);
+
+ template<typename Method>
+ void CallOnChildren(Method aMethod);
+
+ void FreezeInternal();
+ void ThawInternal();
+
+public:
+ // Timeout Functions
+ // Language agnostic timeout function (all args passed).
+ // |interval| is in milliseconds.
+ nsresult SetTimeoutOrInterval(nsITimeoutHandler* aHandler,
+ int32_t interval, bool aIsInterval,
+ mozilla::dom::Timeout::Reason aReason,
+ int32_t* aReturn);
+ int32_t SetTimeoutOrInterval(JSContext* aCx,
+ mozilla::dom::Function& aFunction,
+ int32_t aTimeout,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ bool aIsInterval, mozilla::ErrorResult& aError);
+ int32_t SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
+ int32_t aTimeout, bool aIsInterval,
+ mozilla::ErrorResult& aError);
+ void ClearTimeoutOrInterval(int32_t aTimerId,
+ mozilla::dom::Timeout::Reason aReason);
+
+ // The timeout implementation functions.
+ void RunTimeout(mozilla::dom::Timeout* aTimeout);
+ void RunTimeout() { RunTimeout(nullptr); }
+ // Return true if |aTimeout| was cleared while its handler ran.
+ bool RunTimeoutHandler(mozilla::dom::Timeout* aTimeout, nsIScriptContext* aScx);
+ // Return true if |aTimeout| needs to be reinserted into the timeout list.
+ bool RescheduleTimeout(mozilla::dom::Timeout* aTimeout, const TimeStamp& now,
+ bool aRunningPendingTimeouts);
+
+ void ClearAllTimeouts();
+ // Insert aTimeout into the list, before all timeouts that would
+ // fire after it, but no earlier than mTimeoutInsertionPoint, if any.
+ void InsertTimeoutIntoList(mozilla::dom::Timeout* aTimeout);
+ uint32_t GetTimeoutId(mozilla::dom::Timeout::Reason aReason);
+
+ // Helper Functions
+ already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
+ already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
+ already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
+ nsresult SecurityCheckURL(const char *aURL);
+ bool IsPrivateBrowsing();
+
+ bool PopupWhitelisted();
+ PopupControlState RevisePopupAbuseLevel(PopupControlState);
+ void FireAbuseEvents(const nsAString &aPopupURL,
+ const nsAString &aPopupWindowName,
+ const nsAString &aPopupWindowFeatures);
+ void FireOfflineStatusEventIfChanged();
+
+ bool GetIsPrerendered();
+
+ // Inner windows only.
+ nsresult ScheduleNextIdleObserverCallback();
+ uint32_t GetFuzzTimeMS();
+ nsresult ScheduleActiveTimerCallback();
+ uint32_t FindInsertionIndex(IdleObserverHolder* aIdleObserver);
+ virtual nsresult RegisterIdleObserver(nsIIdleObserver* aIdleObserverPtr) override;
+ nsresult FindIndexOfElementToRemove(nsIIdleObserver* aIdleObserver,
+ int32_t* aRemoveElementIndex);
+ virtual nsresult UnregisterIdleObserver(nsIIdleObserver* aIdleObserverPtr) override;
+
+ // Inner windows only.
+ nsresult FireHashchange(const nsAString &aOldURL, const nsAString &aNewURL);
+
+ void FlushPendingNotifications(mozFlushType aType);
+
+ // Outer windows only.
+ void EnsureReflowFlushAndPaint();
+ void CheckSecurityWidthAndHeight(int32_t* width, int32_t* height, bool aCallerIsChrome);
+ void CheckSecurityLeftAndTop(int32_t* left, int32_t* top, bool aCallerIsChrome);
+
+ // Outer windows only.
+ // Arguments to this function should have values in app units
+ void SetCSSViewportWidthAndHeight(nscoord width, nscoord height);
+ // Arguments to this function should have values in device pixels
+ nsresult SetDocShellWidthAndHeight(int32_t width, int32_t height);
+
+ static bool CanSetProperty(const char *aPrefName);
+
+ static void MakeScriptDialogTitle(nsAString& aOutTitle,
+ nsIPrincipal* aSubjectPrincipal);
+
+ // Outer windows only.
+ bool CanMoveResizeWindows(bool aCallerIsChrome);
+
+ // 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::CSSIntPoint GetScrollXY(bool aDoFlush);
+
+ int32_t GetScrollBoundaryOuter(mozilla::Side aSide);
+
+ // Outer windows only.
+ nsresult GetInnerSize(mozilla::CSSIntSize& aSize);
+ nsIntSize GetOuterSize(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+ void SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
+ mozilla::ErrorResult& aError, bool aCallerIsChrome);
+ nsRect GetInnerScreenRect();
+
+ void ScrollTo(const mozilla::CSSIntPoint& aScroll,
+ const mozilla::dom::ScrollOptions& aOptions);
+
+ bool IsFrame()
+ {
+ return GetParentInternal() != nullptr;
+ }
+
+ // 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 many methods that need to scale
+ // from device to CSS pixels or vice versa. Note: if a presentation
+ // context is not available, they will assume a 1:1 ratio.
+ int32_t DevToCSSIntPixels(int32_t px);
+ int32_t CSSToDevIntPixels(int32_t px);
+ nsIntSize DevToCSSIntPixels(nsIntSize px);
+ nsIntSize CSSToDevIntPixels(nsIntSize px);
+
+ virtual void SetFocusedNode(nsIContent* aNode,
+ uint32_t aFocusMethod = 0,
+ bool aNeedsFocus = false) override;
+
+ virtual uint32_t GetFocusMethod() override;
+
+ virtual bool ShouldShowFocusRing() override;
+
+ virtual void SetKeyboardIndicators(UIStateChangeType aShowAccelerators,
+ UIStateChangeType aShowFocusRings) override;
+
+ // Inner windows only.
+ void UpdateCanvasFocus(bool aFocusChanged, nsIContent* aNewContent);
+
+public:
+ virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() override;
+
+protected:
+ static void NotifyDOMWindowDestroyed(nsGlobalWindow* aWindow);
+ void NotifyWindowIDDestroyed(const char* aTopic);
+
+ static void NotifyDOMWindowFrozen(nsGlobalWindow* aWindow);
+ static void NotifyDOMWindowThawed(nsGlobalWindow* aWindow);
+
+ void ClearStatus();
+
+ virtual void UpdateParentTarget() override;
+
+ inline int32_t DOMMinTimeoutValue() const;
+
+ void InitializeShowFocusRings();
+
+ // 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);
+
+public:
+ // Outer windows only.
+ nsDOMWindowList* GetWindowList();
+
+protected:
+ // Helper for getComputedStyle and getDefaultComputedStyle
+ already_AddRefed<nsICSSDeclaration>
+ GetComputedStyleHelperOuter(mozilla::dom::Element& aElt,
+ const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly);
+ already_AddRefed<nsICSSDeclaration>
+ GetComputedStyleHelper(mozilla::dom::Element& aElt,
+ const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly,
+ mozilla::ErrorResult& aError);
+ nsresult GetComputedStyleHelper(nsIDOMElement* aElt,
+ const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly,
+ nsIDOMCSSStyleDeclaration** aReturn);
+
+ // Outer windows only.
+ void PreloadLocalStorage();
+
+ // Returns CSS pixels based on primary screen. Outer windows only.
+ mozilla::CSSIntPoint GetScreenXY(mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError);
+
+ nsGlobalWindow* InnerForSetTimeoutOrInterval(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);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ JS::Handle<JS::Value> aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ already_AddRefed<nsIVariant>
+ ShowModalDialogOuter(const nsAString& aUrl, nsIVariant* aArgument,
+ const nsAString& aOptions,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& aError);
+
+ already_AddRefed<nsIVariant>
+ ShowModalDialog(const nsAString& aUrl, nsIVariant* aArgument,
+ const nsAString& aOptions,
+ nsIPrincipal& aSubjectPrincipal,
+ 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:
+ // Fire the JS engine's onNewGlobalObject hook. Only used on inner windows.
+ void FireOnNewGlobalObject();
+
+ void DisconnectEventTargetObjects();
+
+
+ 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(nsIDocument* aDocument,
+ SecureContextFlags aFlags =
+ SecureContextFlags::eDefault);
+
+ // nsPIDOMWindow<T> should be able to see these helper methods.
+ friend class nsPIDOMWindow<mozIDOMWindowProxy>;
+ friend class nsPIDOMWindow<mozIDOMWindow>;
+ friend class nsPIDOMWindow<nsISupports>;
+
+ // Apply back pressure to the window if the TabGroup ThrottledEventQueue
+ // exists and has too many runnables waiting to run. For example, increase
+ // the minimum timer delay, etc.
+ void
+ MaybeApplyBackPressure();
+
+ // Check the current ThrottledEventQueue depth and update the back pressure
+ // state. If the queue has drained back pressure may be canceled.
+ void
+ CancelOrUpdateBackPressure();
+
+ // When timers are being throttled and we reduce the thottle delay we must
+ // reschedule. The amount of the old throttle delay must be provided in
+ // order to bound how many timers must be examined.
+ nsresult ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS);
+
+ mozilla::dom::TabGroup* TabGroupInner();
+ mozilla::dom::TabGroup* TabGroupOuter();
+
+protected:
+ // These members are only used on outer window objects. Make sure
+ // you never set any of these on an inner object!
+ bool mFullScreen : 1;
+ bool mFullscreenMode : 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;
+ bool mHadOriginalOpener : 1;
+ bool mOriginalOpenerWasSecureContext : 1;
+ bool mIsSecureContextIfOpenerIgnored : 1;
+ bool mIsPopupSpam : 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;
+
+ // 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;
+
+ // Track what sorts of events we need to fire when thawed
+ bool mNotifyIdleObserversIdleOnThaw : 1;
+ bool mNotifyIdleObserversActiveOnThaw : 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;
+
+ // 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;
+
+ // when true, show focus rings for the current focused content only.
+ // This will be reset when another element is focused
+ bool mShowFocusRingForContent : 1;
+
+ // true if tab navigation has occurred for this window. Focus rings
+ // should be displayed.
+ bool mFocusByKeyOccurred : 1;
+
+ // Inner windows only.
+ // Indicates whether this window wants gamepad input events
+ bool mHasGamepad : 1;
+
+ // Inner windows only.
+ // Indicates whether this window wants VR events
+ bool mHasVREvents : 1;
+#ifdef MOZ_GAMEPAD
+ nsCheapSet<nsUint32HashKey> mGamepadIndexSet;
+ nsRefPtrHashtable<nsUint32HashKey, mozilla::dom::Gamepad> mGamepads;
+ bool mHasSeenGamepadInput;
+#endif
+
+ // whether we've sent the destroy notification for our window id
+ bool mNotifiedIDDestroyed : 1;
+ // whether scripts may close the window,
+ // even if "dom.allow_scripts_to_close_windows" is false.
+ bool mAllowScriptsToClose : 1;
+
+ nsCOMPtr<nsIScriptContext> mContext;
+ nsWeakPtr mOpener;
+ nsCOMPtr<nsIControllers> mControllers;
+
+ // For |window.arguments|, via |openDialog|.
+ nsCOMPtr<nsIArray> mArguments;
+
+ // For |window.dialogArguments|, via |showModalDialog|.
+ RefPtr<DialogValueHolder> mDialogArguments;
+
+ // Only used in the outer.
+ RefPtr<DialogValueHolder> mReturnValue;
+
+ RefPtr<mozilla::dom::Navigator> mNavigator;
+ RefPtr<nsScreen> mScreen;
+ RefPtr<nsDOMWindowList> mFrames;
+ // All BarProps are inner window only.
+ 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<nsDOMWindowUtils> mWindowUtils;
+ nsString mStatus;
+ nsString mDefaultStatus;
+ RefPtr<nsGlobalWindowObserver> mObserver; // Inner windows only.
+ RefPtr<mozilla::dom::Crypto> mCrypto;
+ RefPtr<mozilla::dom::U2F> mU2F;
+ RefPtr<mozilla::dom::cache::CacheStorage> mCacheStorage;
+ RefPtr<mozilla::dom::Console> mConsole;
+ // We need to store an nsISupports pointer to this object because the
+ // mozilla::dom::External class doesn't exist on b2g and using the type
+ // forward declared here means that ~nsGlobalWindow wouldn't compile because
+ // it wouldn't see the ~External function's declaration.
+ nsCOMPtr<nsISupports> mExternal;
+
+ RefPtr<mozilla::dom::MozSelfSupport> mMozSelfSupport;
+
+ RefPtr<mozilla::dom::DOMStorage> mLocalStorage;
+ RefPtr<mozilla::dom::DOMStorage> mSessionStorage;
+
+ // These member variable are used only on inner windows.
+ RefPtr<mozilla::EventListenerManager> mListenerManager;
+ // mTimeouts is generally sorted by mWhen, unless mTimeoutInsertionPoint is
+ // non-null. In that case, the dummy timeout pointed to by
+ // mTimeoutInsertionPoint may have a later mWhen than some of the timeouts
+ // that come after it.
+ mozilla::LinkedList<mozilla::dom::Timeout> mTimeouts;
+ // If mTimeoutInsertionPoint is non-null, insertions should happen after it.
+ // This is a dummy timeout at the moment; if that ever changes, the logic in
+ // ResetTimersForThrottleReduction needs to change.
+ mozilla::dom::Timeout* mTimeoutInsertionPoint;
+ uint32_t mTimeoutIdCounter;
+ uint32_t mTimeoutFiringDepth;
+ RefPtr<mozilla::dom::Location> mLocation;
+ RefPtr<nsHistory> mHistory;
+ RefPtr<mozilla::dom::CustomElementRegistry> mCustomElements;
+
+ // These member variables are used on both inner and the outer windows.
+ nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
+
+ typedef nsTArray<RefPtr<mozilla::dom::StorageEvent>> nsDOMStorageEventArray;
+ nsDOMStorageEventArray mPendingStorageEvents;
+
+ uint32_t mSuspendDepth;
+ uint32_t mFreezeDepth;
+
+ int32_t mBackPressureDelayMS;
+
+ // the method that was used to focus mFocusedNode
+ uint32_t mFocusMethod;
+
+ uint32_t mSerial;
+
+ void DisableIdleCallbackRequests();
+ void UnthrottleIdleCallbackRequests();
+
+ void PostThrottledIdleCallback();
+
+ typedef mozilla::LinkedList<mozilla::dom::IdleRequest> IdleRequests;
+ static void InsertIdleCallbackIntoList(mozilla::dom::IdleRequest* aRequest,
+ IdleRequests& aList);
+
+ // The current idle request callback timeout handle
+ uint32_t mIdleCallbackTimeoutCounter;
+ // The current idle request callback handle
+ uint32_t mIdleRequestCallbackCounter;
+ IdleRequests mIdleRequestCallbacks;
+ IdleRequests mThrottledIdleRequestCallbacks;
+
+#ifdef DEBUG
+ bool mSetOpenerWindowCalled;
+ nsCOMPtr<nsIURI> mLastOpenedURI;
+#endif
+
+#ifdef MOZ_B2G
+ bool mNetworkUploadObserverEnabled;
+ bool mNetworkDownloadObserverEnabled;
+#endif // MOZ_B2G
+
+ bool mCleanedUp;
+
+ nsCOMPtr<nsIDOMOfflineResourceList> mApplicationCache;
+
+ using XBLPrototypeHandlerTable = nsJSThingHashtable<nsPtrHashKey<nsXBLPrototypeHandler>, JSObject*>;
+ nsAutoPtr<XBLPrototypeHandlerTable> mCachedXBLPrototypeHandlers;
+
+ // mSuspendedDoc is only set on outer windows. It's useful when we get matched
+ // EnterModalState/LeaveModalState calls, in which case the outer window is
+ // responsible for unsuspending events on the document. If we don't (for
+ // example, if the outer window is closed before the LeaveModalState call),
+ // then the inner window whose mDoc is our mSuspendedDoc is responsible for
+ // unsuspending it.
+ nsCOMPtr<nsIDocument> mSuspendedDoc;
+
+ RefPtr<mozilla::dom::IDBFactory> mIndexedDB;
+
+ // This counts the number of windows that have been opened in rapid succession
+ // (i.e. within dom.successive_dialog_time_limit of each other). It is reset
+ // to 0 once a dialog is opened after dom.successive_dialog_time_limit seconds
+ // have elapsed without any other dialogs.
+ uint32_t mDialogAbuseCount;
+
+ // This holds the time when the last modal dialog was shown. If more than
+ // MAX_DIALOG_LIMIT dialogs are shown within the time span defined by
+ // dom.successive_dialog_time_limit, we show a checkbox or confirmation prompt
+ // to allow disabling of further dialogs from this window.
+ TimeStamp mLastDialogQuitTime;
+
+ // This flag keeps track of whether dialogs are
+ // currently enabled on this window.
+ bool mAreDialogsEnabled;
+
+ nsTHashtable<nsPtrHashKey<mozilla::DOMEventTargetHelper> > mEventTargetObjects;
+
+ nsTArray<uint32_t> mEnabledSensors;
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+ nsAutoPtr<mozilla::dom::WindowOrientationObserver> mOrientationChangeObserver;
+#endif
+
+#ifdef MOZ_WEBSPEECH
+ // mSpeechSynthesis is only used on inner windows.
+ RefPtr<mozilla::dom::SpeechSynthesis> mSpeechSynthesis;
+#endif
+
+#ifdef DEBUG
+ // This member is used in the debug only assertions in TabGroup()
+ // to catch cyclic parent/opener trees and not overflow the stack.
+ bool mIsValidatingTabGroup;
+#endif
+
+ // 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;
+
+ nsAutoPtr<mozilla::dom::VREventObserver> mVREventObserver;
+
+ friend class nsDOMScriptableHelper;
+ friend class nsDOMWindowUtils;
+ friend class mozilla::dom::PostMessageEvent;
+ friend class DesktopNotification;
+
+ static WindowByIdTable* sWindowsById;
+ static bool sWarnedAboutWindowInternal;
+};
+
+inline nsISupports*
+ToSupports(nsGlobalWindow *p)
+{
+ return static_cast<nsIDOMEventTarget*>(p);
+}
+
+inline nsISupports*
+ToCanonicalSupports(nsGlobalWindow *p)
+{
+ return static_cast<nsIDOMEventTarget*>(p);
+}
+
+/*
+ * nsGlobalChromeWindow inherits from nsGlobalWindow. It is the global
+ * object created for a Chrome Window only.
+ */
+class nsGlobalChromeWindow : public nsGlobalWindow,
+ public nsIDOMChromeWindow
+{
+public:
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMChromeWindow interface
+ NS_DECL_NSIDOMCHROMEWINDOW
+
+ static already_AddRefed<nsGlobalChromeWindow> Create(nsGlobalWindow *aOuterWindow);
+
+ void DisconnectAndClearGroupMessageManagers()
+ {
+ for (auto iter = mGroupMessageManagers.Iter(); !iter.Done(); iter.Next()) {
+ nsIMessageBroadcaster* mm = iter.UserData();
+ if (mm) {
+ static_cast<nsFrameMessageManager*>(mm)->Disconnect();
+ }
+ }
+ mGroupMessageManagers.Clear();
+ }
+
+protected:
+ explicit nsGlobalChromeWindow(nsGlobalWindow *aOuterWindow)
+ : nsGlobalWindow(aOuterWindow),
+ mGroupMessageManagers(1)
+ {
+ mIsChrome = true;
+ mCleanMessageManager = true;
+ }
+
+ ~nsGlobalChromeWindow()
+ {
+ MOZ_ASSERT(mCleanMessageManager,
+ "chrome windows may always disconnect the msg manager");
+
+ DisconnectAndClearGroupMessageManagers();
+
+ if (mMessageManager) {
+ static_cast<nsFrameMessageManager *>(
+ mMessageManager.get())->Disconnect();
+ }
+
+ mCleanMessageManager = false;
+ }
+
+public:
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGlobalChromeWindow,
+ nsGlobalWindow)
+
+ using nsGlobalWindow::GetBrowserDOMWindow;
+ using nsGlobalWindow::SetBrowserDOMWindow;
+ using nsGlobalWindow::GetAttention;
+ using nsGlobalWindow::GetAttentionWithCycleCount;
+ using nsGlobalWindow::SetCursor;
+ using nsGlobalWindow::Maximize;
+ using nsGlobalWindow::Minimize;
+ using nsGlobalWindow::Restore;
+ using nsGlobalWindow::NotifyDefaultButtonLoaded;
+ using nsGlobalWindow::GetMessageManager;
+ using nsGlobalWindow::GetGroupMessageManager;
+ using nsGlobalWindow::BeginWindowMove;
+
+ nsCOMPtr<nsIBrowserDOMWindow> mBrowserDOMWindow;
+ nsCOMPtr<nsIMessageBroadcaster> mMessageManager;
+ nsInterfaceHashtable<nsStringHashKey, nsIMessageBroadcaster> mGroupMessageManagers;
+ // A weak pointer to the nsPresShell that we are doing fullscreen for.
+ // The pointer being set indicates we've set the IsInFullscreenChange
+ // flag on this pres shell.
+ nsWeakPtr mFullscreenPresShell;
+ nsCOMPtr<mozIDOMWindowProxy> mOpenerForInitialContentBrowser;
+};
+
+/*
+ * nsGlobalModalWindow inherits from nsGlobalWindow. It is the global
+ * object created for a modal content windows only (i.e. not modal
+ * chrome dialogs).
+ */
+class nsGlobalModalWindow : public nsGlobalWindow,
+ public nsIDOMModalContentWindow
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMMODALCONTENTWINDOW
+
+ static already_AddRefed<nsGlobalModalWindow> Create(nsGlobalWindow *aOuterWindow);
+
+protected:
+ explicit nsGlobalModalWindow(nsGlobalWindow *aOuterWindow)
+ : nsGlobalWindow(aOuterWindow)
+ {
+ mIsModalContentWindow = true;
+ }
+
+ ~nsGlobalModalWindow() {}
+};
+
+/* factory function */
+inline already_AddRefed<nsGlobalWindow>
+NS_NewScriptGlobalObject(bool aIsChrome, bool aIsModalContentWindow)
+{
+ RefPtr<nsGlobalWindow> global;
+
+ if (aIsChrome) {
+ global = nsGlobalChromeWindow::Create(nullptr);
+ } else if (aIsModalContentWindow) {
+ global = nsGlobalModalWindow::Create(nullptr);
+ } else {
+ global = nsGlobalWindow::Create(nullptr);
+ }
+
+ return global.forget();
+}
+
+#endif /* nsGlobalWindow_h___ */
diff --git a/dom/base/nsGlobalWindowCommands.cpp b/dom/base/nsGlobalWindowCommands.cpp
new file mode 100644
index 000000000..3ab2566b8
--- /dev/null
+++ b/dom/base/nsGlobalWindowCommands.cpp
@@ -0,0 +1,1264 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIComponentManager.h"
+#include "nsIDOMElement.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Preferences.h"
+
+#include "nsIControllerCommandTable.h"
+#include "nsICommandParams.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsIPresShell.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 "nsIWordBreaker.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/Selection.h"
+
+#include "nsIClipboardDragDropHooks.h"
+#include "nsIClipboardDragDropHookList.h"
+
+using namespace mozilla;
+
+const char * const sSelectAllString = "cmd_selectAll";
+const char * const sSelectNoneString = "cmd_selectNone";
+const char * const sCopyImageLocationString = "cmd_copyImageLocation";
+const char * const sCopyImageContentsString = "cmd_copyImageContents";
+const char * const sCopyImageString = "cmd_copyImage";
+
+const char * const sScrollTopString = "cmd_scrollTop";
+const char * const sScrollBottomString = "cmd_scrollBottom";
+const char * const sScrollPageUpString = "cmd_scrollPageUp";
+const char * const sScrollPageDownString = "cmd_scrollPageDown";
+const char * const sScrollLineUpString = "cmd_scrollLineUp";
+const char * const sScrollLineDownString = "cmd_scrollLineDown";
+const char * const sScrollLeftString = "cmd_scrollLeft";
+const char * const sScrollRightString = "cmd_scrollRight";
+const char * const sMoveTopString = "cmd_moveTop";
+const char * const sMoveBottomString = "cmd_moveBottom";
+const char * const sMovePageUpString = "cmd_movePageUp";
+const char * const sMovePageDownString = "cmd_movePageDown";
+const char * const sLinePreviousString = "cmd_linePrevious";
+const char * const sLineNextString = "cmd_lineNext";
+const char * const sCharPreviousString = "cmd_charPrevious";
+const char * const sCharNextString = "cmd_charNext";
+
+// These are so the browser can use editor navigation key bindings
+// helps with accessibility (boolean pref accessibility.browsewithcaret)
+
+const char * const sSelectCharPreviousString = "cmd_selectCharPrevious";
+const char * const sSelectCharNextString = "cmd_selectCharNext";
+
+const char * const sWordPreviousString = "cmd_wordPrevious";
+const char * const sWordNextString = "cmd_wordNext";
+const char * const sSelectWordPreviousString = "cmd_selectWordPrevious";
+const char * const sSelectWordNextString = "cmd_selectWordNext";
+
+const char * const sBeginLineString = "cmd_beginLine";
+const char * const sEndLineString = "cmd_endLine";
+const char * const sSelectBeginLineString = "cmd_selectBeginLine";
+const char * const sSelectEndLineString = "cmd_selectEndLine";
+
+const char * const sSelectLinePreviousString = "cmd_selectLinePrevious";
+const char * const sSelectLineNextString = "cmd_selectLineNext";
+
+const char * const sSelectPageUpString = "cmd_selectPageUp";
+const char * const sSelectPageDownString = "cmd_selectPageDown";
+
+const char * const sSelectTopString = "cmd_selectTop";
+const char * const sSelectBottomString = "cmd_selectBottom";
+
+// Physical-direction movement and selection commands
+const char * const sMoveLeftString = "cmd_moveLeft";
+const char * const sMoveRightString = "cmd_moveRight";
+const char * const sMoveUpString = "cmd_moveUp";
+const char * const sMoveDownString = "cmd_moveDown";
+const char * const sMoveLeft2String = "cmd_moveLeft2";
+const char * const sMoveRight2String = "cmd_moveRight2";
+const char * const sMoveUp2String = "cmd_moveUp2";
+const char * const sMoveDown2String = "cmd_moveDown2";
+
+const char * const sSelectLeftString = "cmd_selectLeft";
+const char * const sSelectRightString = "cmd_selectRight";
+const char * const sSelectUpString = "cmd_selectUp";
+const char * const sSelectDownString = "cmd_selectDown";
+const char * const sSelectLeft2String = "cmd_selectLeft2";
+const char * const sSelectRight2String = "cmd_selectRight2";
+const char * const sSelectUp2String = "cmd_selectUp2";
+const char * const 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;
+ NS_IMETHOD DoCommandParams(const char* aCommandName, nsICommandParams* aParams, nsISupports* aCommandContext) override;
+
+protected:
+ virtual ~nsSelectionCommandsBase() {}
+
+ static nsresult GetPresShellFromWindow(nsPIDOMWindowOuter *aWindow, nsIPresShell **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);
+
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // 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, nsIPresShell **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)
+{
+ *aSelCon = nullptr;
+
+ nsCOMPtr<nsIPresShell> presShell;
+ GetPresShellFromWindow(aWindow, getter_AddRefs(presShell));
+ if (presShell)
+ return CallQueryInterface(presShell, aSelCon);
+
+ return NS_ERROR_FAILURE;
+}
+
+#if 0
+#pragma mark -
+#endif
+
+// Helpers for nsSelectMoveScrollCommand and nsPhysicalSelectMoveScrollCommand
+static void
+AdjustFocusAfterCaretMove(nsPIDOMWindowOuter* aWindow)
+{
+ // adjust the focus to the new caret position
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<nsIDOMElement> 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 const struct BrowseCommand {
+ const char *reverse, *forward;
+ nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
+ nsresult (NS_STDCALL nsISelectionController::*move)(bool, bool);
+} browseCommands[] = {
+ { sScrollTopString, sScrollBottomString,
+ &nsISelectionController::CompleteScroll },
+ { sScrollPageUpString, sScrollPageDownString,
+ &nsISelectionController::ScrollPage },
+ { sScrollLineUpString, sScrollLineDownString,
+ &nsISelectionController::ScrollLine },
+ { sScrollLeftString, sScrollRightString,
+ &nsISelectionController::ScrollCharacter },
+ { sMoveTopString, sMoveBottomString,
+ &nsISelectionController::CompleteScroll,
+ &nsISelectionController::CompleteMove },
+ { sMovePageUpString, sMovePageDownString,
+ &nsISelectionController::ScrollPage,
+ &nsISelectionController::PageMove },
+ { sLinePreviousString, sLineNextString,
+ &nsISelectionController::ScrollLine,
+ &nsISelectionController::LineMove },
+ { sWordPreviousString, sWordNextString,
+ &nsISelectionController::ScrollCharacter,
+ &nsISelectionController::WordMove },
+ { sCharPreviousString, sCharNextString,
+ &nsISelectionController::ScrollCharacter,
+ &nsISelectionController::CharacterMove },
+ { sBeginLineString, sEndLineString,
+ &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);
+
+ bool caretOn = IsCaretOnInWindow(piWindow, selCont);
+
+ for (size_t i = 0; i < ArrayLength(browseCommands); i++) {
+ bool forward = !strcmp(aCommandName, browseCommands[i].forward);
+ if (forward || !strcmp(aCommandName, browseCommands[i].reverse)) {
+ if (caretOn && browseCommands[i].move &&
+ NS_SUCCEEDED((selCont->*(browseCommands[i].move))(forward, false))) {
+ AdjustFocusAfterCaretMove(piWindow);
+ return NS_OK;
+ }
+ return (selCont->*(browseCommands[i].scroll))(forward);
+ }
+ }
+
+ 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 {
+ const char *command;
+ int16_t direction, amount;
+ nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
+} physicalBrowseCommands[] = {
+ { sMoveLeftString, nsISelectionController::MOVE_LEFT, 0,
+ &nsISelectionController::ScrollCharacter },
+ { sMoveRightString, nsISelectionController::MOVE_RIGHT, 0,
+ &nsISelectionController::ScrollCharacter },
+ { sMoveUpString, nsISelectionController::MOVE_UP, 0,
+ &nsISelectionController::ScrollLine },
+ { sMoveDownString, nsISelectionController::MOVE_DOWN, 0,
+ &nsISelectionController::ScrollLine },
+ { sMoveLeft2String, nsISelectionController::MOVE_LEFT, 1,
+ &nsISelectionController::ScrollCharacter },
+ { sMoveRight2String, nsISelectionController::MOVE_RIGHT, 1,
+ &nsISelectionController::ScrollCharacter },
+ { sMoveUp2String, nsISelectionController::MOVE_UP, 1,
+ &nsISelectionController::CompleteScroll },
+ { sMoveDown2String, nsISelectionController::MOVE_DOWN, 1,
+ &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);
+
+ bool caretOn = IsCaretOnInWindow(piWindow, selCont);
+
+ for (size_t i = 0; i < ArrayLength(physicalBrowseCommands); i++) {
+ const PhysicalBrowseCommand& cmd = physicalBrowseCommands[i];
+ if (!strcmp(aCommandName, cmd.command)) {
+ int16_t dir = cmd.direction;
+ if (caretOn &&
+ NS_SUCCEEDED(selCont->PhysicalMove(dir, cmd.amount, false))) {
+ AdjustFocusAfterCaretMove(piWindow);
+ return NS_OK;
+ }
+
+ bool forward = (dir == nsISelectionController::MOVE_RIGHT ||
+ dir == nsISelectionController::MOVE_DOWN);
+ return (selCont->*(cmd.scroll))(forward);
+ }
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#if 0
+#pragma mark -
+#endif
+
+static const struct SelectCommand {
+ const char *reverse, *forward;
+ nsresult (NS_STDCALL nsISelectionController::*select)(bool, bool);
+} selectCommands[] = {
+ { sSelectCharPreviousString, sSelectCharNextString,
+ &nsISelectionController::CharacterMove },
+ { sSelectWordPreviousString, sSelectWordNextString,
+ &nsISelectionController::WordMove },
+ { sSelectBeginLineString, sSelectEndLineString,
+ &nsISelectionController::IntraLineMove },
+ { sSelectLinePreviousString, sSelectLineNextString,
+ &nsISelectionController::LineMove },
+ { sSelectPageUpString, sSelectPageDownString,
+ &nsISelectionController::PageMove },
+ { sSelectTopString, sSelectBottomString,
+ &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
+ for (size_t i = 0; i < ArrayLength(selectCommands); i++) {
+ bool forward = !strcmp(aCommandName, selectCommands[i].forward);
+ if (forward || !strcmp(aCommandName, selectCommands[i].reverse)) {
+ return (selCont->*(selectCommands[i].select))(forward, true);
+ }
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#if 0
+#pragma mark -
+#endif
+
+static const struct PhysicalSelectCommand {
+ const char *command;
+ int16_t direction, amount;
+} physicalSelectCommands[] = {
+ { sSelectLeftString, nsISelectionController::MOVE_LEFT, 0 },
+ { sSelectRightString, nsISelectionController::MOVE_RIGHT, 0 },
+ { sSelectUpString, nsISelectionController::MOVE_UP, 0 },
+ { sSelectDownString, nsISelectionController::MOVE_DOWN, 0 },
+ { sSelectLeft2String, nsISelectionController::MOVE_LEFT, 1 },
+ { sSelectRight2String, nsISelectionController::MOVE_RIGHT, 1 },
+ { sSelectUp2String, nsISelectionController::MOVE_UP, 1 },
+ { sSelectDown2String, 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);
+
+ for (size_t i = 0; i < ArrayLength(physicalSelectCommands); i++) {
+ if (!strcmp(aCommandName, physicalSelectCommands[i].command)) {
+ return selCont->PhysicalMove(physicalSelectCommands[i].direction,
+ physicalSelectCommands[i].amount,
+ true);
+ }
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#if 0
+#pragma mark -
+#endif
+
+class nsClipboardCommand final : public nsIControllerCommand
+{
+ ~nsClipboardCommand() {}
+
+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_copyAndCollapseToEnd") &&
+ strcmp(aCommandName, "cmd_cut") &&
+ strcmp(aCommandName, "cmd_paste"))
+ return NS_OK;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+ if (doc->IsHTMLOrXHTML()) {
+ // In HTML and XHTML documents, we always want the cut, copy and paste
+ // commands to be enabled.
+ *outCmdEnabled = true;
+ } else {
+ // Cut isn't enabled in xul documents which use nsClipboardCommand
+ if (strcmp(aCommandName, "cmd_copy") == 0 ||
+ strcmp(aCommandName, "cmd_copyAndCollapseToEnd") == 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_copyAndCollapseToEnd") &&
+ 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);
+
+ nsCOMPtr<nsIPresShell> 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;
+ bool notCancelled =
+ nsCopySupport::FireClipboardEvent(eventMessage,
+ nsIClipboard::kGlobalClipboard,
+ presShell, nullptr, &actionTaken);
+
+ if (notCancelled && !strcmp(aCommandName, "cmd_copyAndCollapseToEnd")) {
+ dom::Selection *sel =
+ presShell->GetCurrentSelection(SelectionType::eNormal);
+ NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE);
+ sel->CollapseToEnd();
+ }
+
+ if (actionTaken) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+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() {}
+
+ 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); \
+ virtual nsresult DoClipboardCommand(const char* aCommandName, \
+ nsIContentViewerEdit* aEdit, nsICommandParams* aParams); \
+ /* no member variables, please, we're stateless! */ \
+};
+
+NS_DECL_CLIPBOARD_COMMAND(nsClipboardCopyLinkCommand)
+NS_DECL_CLIPBOARD_COMMAND(nsClipboardImageCommands)
+NS_DECL_CLIPBOARD_COMMAND(nsClipboardSelectAllNoneCommands)
+NS_DECL_CLIPBOARD_COMMAND(nsClipboardGetContentsCommand)
+
+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)
+ aParams->GetLongValue("imageCopy", &copyFlags);
+ 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
+
+nsresult
+nsClipboardGetContentsCommand::IsClipboardCommandEnabled(const char* aCommandName, nsIContentViewerEdit* aEdit, bool *outCmdEnabled)
+{
+ return aEdit->GetCanGetContents(outCmdEnabled);
+}
+
+nsresult
+nsClipboardGetContentsCommand::DoClipboardCommand(const char *aCommandName, nsIContentViewerEdit* aEdit, nsICommandParams* aParams)
+{
+ NS_ENSURE_ARG(aParams);
+
+ nsAutoCString mimeType("text/plain");
+
+ nsXPIDLCString format; // nsICommandParams needs to use nsACString
+ if (NS_SUCCEEDED(aParams->GetCStringValue("format", getter_Copies(format))))
+ mimeType.Assign(format);
+
+ bool selectionOnly = false;
+ aParams->GetBooleanValue("selection_only", &selectionOnly);
+
+ nsAutoString contents;
+ nsresult rv = aEdit->GetContents(mimeType.get(), selectionOnly, contents);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return aParams->SetStringValue("result", contents);
+}
+
+#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
+
+/*---------------------------------------------------------------------------
+
+ nsClipboardDragDropHookCommand
+ params value type possible values
+ "addhook" isupports nsIClipboardDragDropHooks as nsISupports
+ "removehook" isupports nsIClipboardDragDropHooks as nsISupports
+
+----------------------------------------------------------------------------*/
+
+class nsClipboardDragDropHookCommand final : public nsIControllerCommand
+{
+ ~nsClipboardDragDropHookCommand() {}
+
+public:
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTROLLERCOMMAND
+
+protected:
+ // no member variables, please, we're stateless!
+};
+
+
+NS_IMPL_ISUPPORTS(nsClipboardDragDropHookCommand, nsIControllerCommand)
+
+NS_IMETHODIMP
+nsClipboardDragDropHookCommand::IsCommandEnabled(const char * aCommandName,
+ nsISupports *aCommandContext,
+ bool *outCmdEnabled)
+{
+ *outCmdEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboardDragDropHookCommand::DoCommand(const char *aCommandName,
+ nsISupports *aCommandContext)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsClipboardDragDropHookCommand::DoCommandParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *aCommandContext)
+{
+ NS_ENSURE_ARG(aParams);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aCommandContext);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsIDocShell *docShell = window->GetDocShell();
+
+ nsCOMPtr<nsIClipboardDragDropHookList> obj = do_GetInterface(docShell);
+ if (!obj) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsISupports> isuppHook;
+
+ nsresult returnValue = NS_OK;
+ nsresult rv = aParams->GetISupportsValue("addhook", getter_AddRefs(isuppHook));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIClipboardDragDropHooks> hook = do_QueryInterface(isuppHook);
+ if (hook)
+ returnValue = obj->AddClipboardDragDropHooks(hook);
+ else
+ returnValue = NS_ERROR_INVALID_ARG;
+ }
+
+ rv = aParams->GetISupportsValue("removehook", getter_AddRefs(isuppHook));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIClipboardDragDropHooks> hook = do_QueryInterface(isuppHook);
+ if (hook)
+ {
+ rv = obj->RemoveClipboardDragDropHooks(hook);
+ if (NS_FAILED(rv) && NS_SUCCEEDED(returnValue))
+ returnValue = rv;
+ }
+ else
+ returnValue = NS_ERROR_INVALID_ARG;
+ }
+
+ return returnValue;
+}
+
+NS_IMETHODIMP
+nsClipboardDragDropHookCommand::GetCommandStateParams(const char *aCommandName,
+ nsICommandParams *aParams,
+ nsISupports *aCommandContext)
+{
+ NS_ENSURE_ARG_POINTER(aParams);
+ return aParams->SetBooleanValue("state_enabled", true);
+}
+
+class nsLookUpDictionaryCommand final : public nsIControllerCommand
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTROLLERCOMMAND
+
+private:
+ virtual ~nsLookUpDictionaryCommand()
+ {
+ }
+};
+
+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;
+ }
+
+ int32_t x;
+ int32_t y;
+
+ nsresult rv = aParams->GetLongValue("x", &x);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aParams->GetLongValue("y", &y);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ 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;
+ }
+
+ nsCOMPtr<nsIPresShell> 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 charAt(true, eQueryCharacterAtPoint, widget);
+ charAt.mRefPoint.x = x;
+ charAt.mRefPoint.y = y;
+ ContentEventHandler handler(presContext);
+ handler.OnQueryCharacterAtPoint(&charAt);
+
+ if (NS_WARN_IF(!charAt.mSucceeded) ||
+ charAt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetQueryContentEvent textContent(true, eQueryTextContent, widget);
+ // OSX 10.7 queries 50 characters before/after current point. So we fetch
+ // same length.
+ uint32_t offset = charAt.mReply.mOffset;
+ if (offset > 50) {
+ offset -= 50;
+ } else {
+ offset = 0;
+ }
+ textContent.InitForQueryTextContent(offset, 100);
+ handler.OnQueryTextContent(&textContent);
+ if (NS_WARN_IF(!textContent.mSucceeded ||
+ textContent.mReply.mString.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX nsIWordBreaker doesn't use contextual breaker.
+ // If OS provides it, widget should use it if contextual breaker is needed.
+ nsCOMPtr<nsIWordBreaker> wordBreaker = nsContentUtils::WordBreaker();
+ if (NS_WARN_IF(!wordBreaker)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsWordRange range =
+ wordBreaker->FindWord(textContent.mReply.mString.get(),
+ textContent.mReply.mString.Length(),
+ charAt.mReply.mOffset - offset);
+ if (range.mEnd == range.mBegin) {
+ return NS_ERROR_FAILURE;
+ }
+ range.mBegin += offset;
+ range.mEnd += offset;
+
+ WidgetQueryContentEvent lookUpContent(true, eQueryTextContent, widget);
+ lookUpContent.InitForQueryTextContent(range.mBegin,
+ range.mEnd - range.mBegin);
+ lookUpContent.RequestFontRanges();
+ handler.OnQueryTextContent(&lookUpContent);
+ if (NS_WARN_IF(!lookUpContent.mSucceeded ||
+ lookUpContent.mReply.mString.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WidgetQueryContentEvent charRect(true, eQueryTextRect, widget);
+ charRect.InitForQueryTextRect(range.mBegin, range.mEnd - range.mBegin);
+ handler.OnQueryTextRect(&charRect);
+ if (NS_WARN_IF(!charRect.mSucceeded)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ widget->LookUpDictionary(lookUpContent.mReply.mString,
+ lookUpContent.mReply.mFontRanges,
+ charRect.mReply.mWritingMode.IsVertical(),
+ charRect.mReply.mRect.TopLeft());
+
+ return NS_OK;
+}
+
+/*---------------------------------------------------------------------------
+
+ RegisterWindowCommands
+
+----------------------------------------------------------------------------*/
+
+#define NS_REGISTER_ONE_COMMAND(_cmdClass, _cmdName) \
+ { \
+ _cmdClass* theCmd = new _cmdClass(); \
+ rv = inCommandTable->RegisterCommand(_cmdName, \
+ static_cast<nsIControllerCommand *>(theCmd)); \
+ }
+
+#define NS_REGISTER_FIRST_COMMAND(_cmdClass, _cmdName) \
+ { \
+ _cmdClass* theCmd = new _cmdClass(); \
+ rv = inCommandTable->RegisterCommand(_cmdName, \
+ static_cast<nsIControllerCommand *>(theCmd));
+
+#define NS_REGISTER_NEXT_COMMAND(_cmdClass, _cmdName) \
+ rv = inCommandTable->RegisterCommand(_cmdName, \
+ static_cast<nsIControllerCommand *>(theCmd));
+
+#define NS_REGISTER_LAST_COMMAND(_cmdClass, _cmdName) \
+ rv = inCommandTable->RegisterCommand(_cmdName, \
+ static_cast<nsIControllerCommand *>(theCmd)); \
+ }
+
+
+// static
+nsresult
+nsWindowCommandRegistration::RegisterWindowCommands(
+ nsIControllerCommandTable *inCommandTable)
+{
+ 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_copyAndCollapseToEnd");
+ 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);
+
+ NS_REGISTER_ONE_COMMAND(nsClipboardGetContentsCommand, "cmd_getContents");
+
+#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(nsClipboardDragDropHookCommand, "cmd_clipboardDragDropHook");
+
+ NS_REGISTER_ONE_COMMAND(nsLookUpDictionaryCommand, "cmd_lookUpDictionary");
+
+ return rv;
+}
diff --git a/dom/base/nsGlobalWindowCommands.h b/dom/base/nsGlobalWindowCommands.h
new file mode 100644
index 000000000..3508af8fb
--- /dev/null
+++ b/dom/base/nsGlobalWindowCommands.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 nsGlobalWindowCommands_h__
+#define nsGlobalWindowCommands_h__
+
+#include "nscore.h"
+
+class nsIControllerCommandTable;
+
+class nsWindowCommandRegistration
+{
+public:
+ static nsresult RegisterWindowCommands(nsIControllerCommandTable *ccm);
+};
+
+
+// XXX find a better home for these
+#define NS_WINDOWCONTROLLER_CID \
+ { /* 7BD05C78-6A26-11D7-B16F-0003938A9D96 */ \
+ 0x7BD05C78, 0x6A26, 0x11D7, {0xB1, 0x6F, 0x00, 0x03, 0x93, 0x8A, 0x9D, 0x96} }
+
+#define NS_WINDOWCONTROLLER_CONTRACTID "@mozilla.org/dom/window-controller;1"
+
+#endif // nsGlobalWindowCommands_h__
+
diff --git a/dom/base/nsHTMLContentSerializer.cpp b/dom/base/nsHTMLContentSerializer.cpp
new file mode 100644
index 000000000..ab8b4f2b2
--- /dev/null
+++ b/dom/base/nsHTMLContentSerializer.cpp
@@ -0,0 +1,622 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsIContentSerializer implementation that can be used with an
+ * nsIDocumentEncoder to convert an HTML (not XHTML!) DOM to an HTML
+ * string that could be parsed into more or less the original DOM.
+ */
+
+#include "nsHTMLContentSerializer.h"
+
+#include "nsIDOMElement.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsNameSpaceManager.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsXPIDLString.h"
+#include "nsIServiceManager.h"
+#include "nsIDocumentEncoder.h"
+#include "nsGkAtoms.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsEscape.h"
+#include "nsITextToSubURI.h"
+#include "nsCRT.h"
+#include "nsIParserService.h"
+#include "nsContentUtils.h"
+#include "nsLWBrkCIID.h"
+#include "nsIScriptElement.h"
+#include "nsAttrName.h"
+#include "nsIDocShell.h"
+#include "nsIEditor.h"
+#include "nsIHTMLEditor.h"
+#include "mozilla/dom/Element.h"
+#include "nsParserConstants.h"
+
+using namespace mozilla::dom;
+
+nsresult
+NS_NewHTMLContentSerializer(nsIContentSerializer** aSerializer)
+{
+ RefPtr<nsHTMLContentSerializer> it = new nsHTMLContentSerializer();
+ it.forget(aSerializer);
+ return NS_OK;
+}
+
+nsHTMLContentSerializer::nsHTMLContentSerializer()
+{
+ mIsHTMLSerializer = true;
+}
+
+nsHTMLContentSerializer::~nsHTMLContentSerializer()
+{
+}
+
+
+NS_IMETHODIMP
+nsHTMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr)
+{
+ return NS_OK;
+}
+
+bool
+nsHTMLContentSerializer::SerializeHTMLAttributes(nsIContent* aContent,
+ nsIContent *aOriginalElement,
+ nsAString& aTagPrefix,
+ const nsAString& aTagNamespaceURI,
+ nsIAtom* aTagName,
+ int32_t aNamespace,
+ nsAString& aStr)
+{
+ int32_t count = aContent->GetAttrCount();
+ if (!count)
+ return true;
+
+ nsresult rv;
+ nsAutoString valueStr;
+ NS_NAMED_LITERAL_STRING(_mozStr, "_moz");
+
+ for (int32_t index = 0; index < count; index++) {
+ const nsAttrName* name = aContent->GetAttrNameAt(index);
+ int32_t namespaceID = name->NamespaceID();
+ nsIAtom* attrName = name->LocalName();
+
+ // Filter out any attribute starting with [-|_]moz
+ nsDependentAtomString attrNameStr(attrName);
+ if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
+ StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
+ continue;
+ }
+ aContent->GetAttr(namespaceID, attrName, valueStr);
+
+ //
+ // Filter out special case of <br type="_moz"> or <br _moz*>,
+ // used by the editor. Bug 16988. Yuck.
+ //
+ if (aTagName == nsGkAtoms::br && aNamespace == kNameSpaceID_XHTML &&
+ attrName == nsGkAtoms::type && namespaceID == kNameSpaceID_None &&
+ StringBeginsWith(valueStr, _mozStr)) {
+ continue;
+ }
+
+ if (mIsCopying && mIsFirstChildOfOL &&
+ aTagName == nsGkAtoms::li && aNamespace == kNameSpaceID_XHTML &&
+ attrName == nsGkAtoms::value && namespaceID == kNameSpaceID_None){
+ // This is handled separately in SerializeLIValueAttribute()
+ continue;
+ }
+ bool isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr);
+
+ if (((attrName == nsGkAtoms::href &&
+ (namespaceID == kNameSpaceID_None ||
+ namespaceID == kNameSpaceID_XLink)) ||
+ (attrName == nsGkAtoms::src && namespaceID == kNameSpaceID_None))) {
+ // Make all links absolute when converting only the selection:
+ if (mFlags & nsIDocumentEncoder::OutputAbsoluteLinks) {
+ // Would be nice to handle OBJECT and APPLET tags,
+ // but that gets more complicated since we have to
+ // search the tag list for CODEBASE as well.
+ // For now, just leave them relative.
+ nsCOMPtr<nsIURI> uri = aContent->GetBaseURI();
+ if (uri) {
+ nsAutoString absURI;
+ rv = NS_MakeAbsoluteURI(absURI, valueStr, uri);
+ if (NS_SUCCEEDED(rv)) {
+ valueStr = absURI;
+ }
+ }
+ }
+ // Need to escape URI.
+ nsAutoString tempURI(valueStr);
+ if (!isJS && NS_FAILED(EscapeURI(aContent, tempURI, valueStr)))
+ valueStr = tempURI;
+ }
+
+ if (mRewriteEncodingDeclaration && aTagName == nsGkAtoms::meta &&
+ aNamespace == kNameSpaceID_XHTML && attrName == nsGkAtoms::content
+ && namespaceID == kNameSpaceID_None) {
+ // If we're serializing a <meta http-equiv="content-type">,
+ // use the proper value, rather than what's in the document.
+ nsAutoString header;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header);
+ if (header.LowerCaseEqualsLiteral("content-type")) {
+ valueStr = NS_LITERAL_STRING("text/html; charset=") +
+ NS_ConvertASCIItoUTF16(mCharset);
+ }
+ }
+
+ nsDependentAtomString nameStr(attrName);
+ nsAutoString prefix;
+ if (namespaceID == kNameSpaceID_XML) {
+ prefix.AssignLiteral(u"xml");
+ } else if (namespaceID == kNameSpaceID_XLink) {
+ prefix.AssignLiteral(u"xlink");
+ }
+
+ // Expand shorthand attribute.
+ if (aNamespace == kNameSpaceID_XHTML &&
+ namespaceID == kNameSpaceID_None &&
+ IsShorthandAttr(attrName, aTagName) &&
+ valueStr.IsEmpty()) {
+ valueStr = nameStr;
+ }
+ NS_ENSURE_TRUE(SerializeAttr(prefix, nameStr, valueStr,
+ aStr, !isJS), false);
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsHTMLContentSerializer::AppendElementStart(Element* aElement,
+ Element* aOriginalElement,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aElement);
+
+ nsIContent* content = aElement;
+
+ bool forceFormat = false;
+ nsresult rv = NS_OK;
+ if (!CheckElementStart(content, forceFormat, aStr, rv)) {
+ // When we go to AppendElementEnd for this element, we're going to
+ // MaybeLeaveFromPreContent(). So make sure to MaybeEnterInPreContent()
+ // now, so our PreLevel() doesn't get confused.
+ MaybeEnterInPreContent(content);
+ return rv;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIAtom *name = content->NodeInfo()->NameAtom();
+ int32_t ns = content->GetNameSpaceID();
+
+ bool lineBreakBeforeOpen = LineBreakBeforeOpen(ns, name);
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
+ if (mColPos && lineBreakBeforeOpen) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ if (!mColPos) {
+ NS_ENSURE_TRUE(AppendIndentation(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mAddSpace) {
+ bool result = AppendToString(char16_t(' '), aStr);
+ mAddSpace = false;
+ NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ else if (mAddSpace) {
+ bool result = AppendToString(char16_t(' '), aStr);
+ mAddSpace = false;
+ NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode wasn't
+ // called
+ mAddNewlineForRootNode = false;
+
+ NS_ENSURE_TRUE(AppendToString(kLessThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ENSURE_TRUE(AppendToString(nsDependentAtomString(name), aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ MaybeEnterInPreContent(content);
+
+ // for block elements, we increase the indentation
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel())
+ NS_ENSURE_TRUE(IncrIndentation(name), NS_ERROR_OUT_OF_MEMORY);
+
+ // Need to keep track of OL and LI elements in order to get ordinal number
+ // for the LI.
+ if (mIsCopying && name == nsGkAtoms::ol && ns == kNameSpaceID_XHTML){
+ // We are copying and current node is an OL;
+ // Store its start attribute value in olState->startVal.
+ nsAutoString start;
+ int32_t startAttrVal = 0;
+
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::start, start);
+ if (!start.IsEmpty()){
+ nsresult rv = NS_OK;
+ startAttrVal = start.ToInteger(&rv);
+ //If OL has "start" attribute, first LI element has to start with that value
+ //Therefore subtracting 1 as all the LI elements are incrementing it before using it;
+ //In failure of ToInteger(), default StartAttrValue to 0.
+ if (NS_SUCCEEDED(rv))
+ startAttrVal--;
+ else
+ startAttrVal = 0;
+ }
+ mOLStateStack.AppendElement(olState(startAttrVal, true));
+ }
+
+ if (mIsCopying && name == nsGkAtoms::li && ns == kNameSpaceID_XHTML) {
+ mIsFirstChildOfOL = IsFirstChildOfOL(aOriginalElement);
+ if (mIsFirstChildOfOL){
+ // If OL is parent of this LI, serialize attributes in different manner.
+ NS_ENSURE_TRUE(SerializeLIValueAttribute(aElement, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ // Even LI passed above have to go through this
+ // for serializing attributes other than "value".
+ nsAutoString dummyPrefix;
+ NS_ENSURE_TRUE(SerializeHTMLAttributes(content,
+ aOriginalElement,
+ dummyPrefix,
+ EmptyString(),
+ name,
+ ns,
+ aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ENSURE_TRUE(AppendToString(kGreaterThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ if (ns == kNameSpaceID_XHTML &&
+ (name == nsGkAtoms::script ||
+ name == nsGkAtoms::style ||
+ name == nsGkAtoms::noscript ||
+ name == nsGkAtoms::noframes)) {
+ ++mDisableEntityEncoding;
+ }
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel() &&
+ LineBreakAfterOpen(ns, name)) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ NS_ENSURE_TRUE(AfterElementStart(content, aOriginalElement, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLContentSerializer::AppendElementEnd(Element* aElement,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aElement);
+
+ nsIContent* content = aElement;
+
+ nsIAtom *name = content->NodeInfo()->NameAtom();
+ int32_t ns = content->GetNameSpaceID();
+
+ if (ns == kNameSpaceID_XHTML &&
+ (name == nsGkAtoms::script ||
+ name == nsGkAtoms::style ||
+ name == nsGkAtoms::noscript ||
+ name == nsGkAtoms::noframes)) {
+ --mDisableEntityEncoding;
+ }
+
+ bool forceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) &&
+ content->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty);
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
+ DecrIndentation(name);
+ }
+
+ if (name == nsGkAtoms::script) {
+ nsCOMPtr<nsIScriptElement> script = do_QueryInterface(aElement);
+
+ if (ShouldMaintainPreLevel() && script && script->IsMalformed()) {
+ // We're looking at a malformed script tag. This means that the end tag
+ // was missing in the source. Imitate that here by not serializing the end
+ // tag.
+ --PreLevel();
+ return NS_OK;
+ }
+ }
+ else if (mIsCopying && name == nsGkAtoms::ol && ns == kNameSpaceID_XHTML) {
+ NS_ASSERTION((!mOLStateStack.IsEmpty()), "Cannot have an empty OL Stack");
+ /* Though at this point we must always have an state to be deleted as all
+ the OL opening tags are supposed to push an olState object to the stack*/
+ if (!mOLStateStack.IsEmpty()) {
+ mOLStateStack.RemoveElementAt(mOLStateStack.Length() -1);
+ }
+ }
+
+ if (ns == kNameSpaceID_XHTML) {
+ nsIParserService* parserService = nsContentUtils::GetParserService();
+
+ if (parserService) {
+ bool isContainer;
+
+ parserService->
+ IsContainer(parserService->HTMLCaseSensitiveAtomTagToId(name),
+ isContainer);
+ if (!isContainer) {
+ // Keep this in sync with the cleanup at the end of this method.
+ MOZ_ASSERT(name != nsGkAtoms::body);
+ MaybeLeaveFromPreContent(content);
+ return NS_OK;
+ }
+ }
+ }
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
+
+ bool lineBreakBeforeClose = LineBreakBeforeClose(ns, name);
+
+ if (mColPos && lineBreakBeforeClose) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ if (!mColPos) {
+ NS_ENSURE_TRUE(AppendIndentation(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mAddSpace) {
+ bool result = AppendToString(char16_t(' '), aStr);
+ mAddSpace = false;
+ NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ else if (mAddSpace) {
+ bool result = AppendToString(char16_t(' '), aStr);
+ mAddSpace = false;
+ NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ NS_ENSURE_TRUE(AppendToString(kEndTag, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(nsDependentAtomString(name), aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(kGreaterThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ // Keep this cleanup in sync with the IsContainer() early return above.
+ MaybeLeaveFromPreContent(content);
+
+ if ((mDoFormat || forceFormat)&& !mDoRaw && !PreLevel()
+ && LineBreakAfterClose(ns, name)) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ MaybeFlagNewlineForRootNode(aElement);
+ }
+
+ if (name == nsGkAtoms::body && ns == kNameSpaceID_XHTML) {
+ --mInBody;
+ }
+
+ return NS_OK;
+}
+
+static const uint16_t kValNBSP = 160;
+
+#define _ 0
+
+// This table indexes into kEntityStrings[].
+static const uint8_t kEntities[] = {
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, 2, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ 3, _, 4, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ 5
+};
+
+// This table indexes into kEntityStrings[].
+static const uint8_t kAttrEntities[] = {
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, 1, _, _, _, 2, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ 3, _, 4, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ 5
+};
+
+#undef _
+
+static const char* const kEntityStrings[] = {
+ /* 0 */ nullptr,
+ /* 1 */ "&quot;",
+ /* 2 */ "&amp;",
+ /* 3 */ "&lt;",
+ /* 4 */ "&gt;",
+ /* 5 */ "&nbsp;"
+};
+
+uint32_t FindNextBasicEntity(const nsAString& aStr,
+ const uint32_t aLen,
+ uint32_t aIndex,
+ const uint8_t* aEntityTable,
+ const char** aEntity)
+{
+ for (; aIndex < aLen; ++aIndex) {
+ // for each character in this chunk, check if it
+ // needs to be replaced
+ char16_t val = aStr[aIndex];
+ if (val <= kValNBSP && aEntityTable[val]) {
+ *aEntity = kEntityStrings[aEntityTable[val]];
+ return aIndex;
+ }
+ }
+ return aIndex;
+}
+
+bool
+nsHTMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
+ nsAString& aOutputStr)
+{
+ if (mBodyOnly && !mInBody) {
+ return true;
+ }
+
+ if (mDisableEntityEncoding) {
+ return aOutputStr.Append(aStr, mozilla::fallible);
+ }
+
+ bool nonBasicEntities =
+ !!(mFlags & (nsIDocumentEncoder::OutputEncodeLatin1Entities |
+ nsIDocumentEncoder::OutputEncodeHTMLEntities |
+ nsIDocumentEncoder::OutputEncodeW3CEntities));
+
+ if (!nonBasicEntities &&
+ (mFlags & (nsIDocumentEncoder::OutputEncodeBasicEntities))) {
+ const uint8_t* entityTable = mInAttribute ? kAttrEntities : kEntities;
+ uint32_t start = 0;
+ const uint32_t len = aStr.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ const char* entity = nullptr;
+ i = FindNextBasicEntity(aStr, len, i, entityTable, &entity);
+ uint32_t normalTextLen = i - start;
+ if (normalTextLen) {
+ NS_ENSURE_TRUE(aOutputStr.Append(Substring(aStr, start, normalTextLen),
+ mozilla::fallible), false);
+ }
+ if (entity) {
+ NS_ENSURE_TRUE(aOutputStr.AppendASCII(entity, mozilla::fallible), false);
+ start = i + 1;
+ }
+ }
+ return true;
+ } else if (nonBasicEntities) {
+ nsIParserService* parserService = nsContentUtils::GetParserService();
+
+ if (!parserService) {
+ NS_ERROR("Can't get parser service");
+ return true;
+ }
+
+ nsReadingIterator<char16_t> done_reading;
+ aStr.EndReading(done_reading);
+
+ // for each chunk of |aString|...
+ uint32_t advanceLength = 0;
+ nsReadingIterator<char16_t> iter;
+
+ const uint8_t* entityTable = mInAttribute ? kAttrEntities : kEntities;
+ nsAutoCString entityReplacement;
+
+ for (aStr.BeginReading(iter);
+ iter != done_reading;
+ iter.advance(int32_t(advanceLength))) {
+ uint32_t fragmentLength = done_reading - iter;
+ uint32_t lengthReplaced = 0; // the number of UTF-16 codepoints
+ // replaced by a particular entity
+ const char16_t* c = iter.get();
+ const char16_t* fragmentStart = c;
+ const char16_t* fragmentEnd = c + fragmentLength;
+ const char* entityText = nullptr;
+ const char* fullConstEntityText = nullptr;
+ char* fullEntityText = nullptr;
+
+ advanceLength = 0;
+ // for each character in this chunk, check if it
+ // needs to be replaced
+ for (; c < fragmentEnd; c++, advanceLength++) {
+ char16_t val = *c;
+ if (val <= kValNBSP && entityTable[val]) {
+ fullConstEntityText = kEntityStrings[entityTable[val]];
+ break;
+ } else if (val > 127 &&
+ ((val < 256 &&
+ mFlags & nsIDocumentEncoder::OutputEncodeLatin1Entities) ||
+ mFlags & nsIDocumentEncoder::OutputEncodeHTMLEntities)) {
+ entityReplacement.Truncate();
+ parserService->HTMLConvertUnicodeToEntity(val, entityReplacement);
+
+ if (!entityReplacement.IsEmpty()) {
+ entityText = entityReplacement.get();
+ break;
+ }
+ }
+ else if (val > 127 &&
+ mFlags & nsIDocumentEncoder::OutputEncodeW3CEntities &&
+ mEntityConverter) {
+ if (NS_IS_HIGH_SURROGATE(val) &&
+ c + 1 < fragmentEnd &&
+ NS_IS_LOW_SURROGATE(*(c + 1))) {
+ uint32_t valUTF32 = SURROGATE_TO_UCS4(val, *(++c));
+ if (NS_SUCCEEDED(mEntityConverter->ConvertUTF32ToEntity(valUTF32,
+ nsIEntityConverter::entityW3C, &fullEntityText))) {
+ lengthReplaced = 2;
+ break;
+ }
+ else {
+ advanceLength++;
+ }
+ }
+ else if (NS_SUCCEEDED(mEntityConverter->ConvertToEntity(val,
+ nsIEntityConverter::entityW3C,
+ &fullEntityText))) {
+ lengthReplaced = 1;
+ break;
+ }
+ }
+ }
+
+ bool result = aOutputStr.Append(fragmentStart, advanceLength, mozilla::fallible);
+ if (entityText) {
+ NS_ENSURE_TRUE(aOutputStr.Append(char16_t('&'), mozilla::fallible), false);
+ NS_ENSURE_TRUE(AppendASCIItoUTF16(entityText, aOutputStr, mozilla::fallible), false);
+ NS_ENSURE_TRUE(aOutputStr.Append(char16_t(';'), mozilla::fallible), false);
+ advanceLength++;
+ }
+ else if (fullConstEntityText) {
+ NS_ENSURE_TRUE(aOutputStr.AppendASCII(fullConstEntityText, mozilla::fallible), false);
+ ++advanceLength;
+ }
+ // if it comes from nsIEntityConverter, it already has '&' and ';'
+ else if (fullEntityText) {
+ bool ok = AppendASCIItoUTF16(fullEntityText, aOutputStr, mozilla::fallible);
+ free(fullEntityText);
+ advanceLength += lengthReplaced;
+ NS_ENSURE_TRUE(ok, false);
+ }
+ NS_ENSURE_TRUE(result, false);
+ }
+ } else {
+ NS_ENSURE_TRUE(nsXMLContentSerializer::AppendAndTranslateEntities(aStr, aOutputStr), false);
+ }
+
+ return true;
+}
diff --git a/dom/base/nsHTMLContentSerializer.h b/dom/base/nsHTMLContentSerializer.h
new file mode 100644
index 000000000..6f3500e01
--- /dev/null
+++ b/dom/base/nsHTMLContentSerializer.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/. */
+
+/*
+ * nsIContentSerializer implementation that can be used with an
+ * nsIDocumentEncoder to convert an HTML (not XHTML!) DOM to an HTML
+ * string that could be parsed into more or less the original DOM.
+ */
+
+#ifndef nsHTMLContentSerializer_h__
+#define nsHTMLContentSerializer_h__
+
+#include "mozilla/Attributes.h"
+#include "nsXHTMLContentSerializer.h"
+#include "nsIEntityConverter.h"
+#include "nsString.h"
+
+class nsIContent;
+class nsIAtom;
+
+class nsHTMLContentSerializer final : public nsXHTMLContentSerializer {
+ public:
+ nsHTMLContentSerializer();
+ virtual ~nsHTMLContentSerializer();
+
+ NS_IMETHOD AppendElementStart(mozilla::dom::Element* aElement,
+ mozilla::dom::Element* aOriginalElement,
+ nsAString& aStr) override;
+
+ NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement,
+ nsAString& aStr) override;
+
+ NS_IMETHOD AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr) override;
+ protected:
+
+ MOZ_MUST_USE
+ virtual bool SerializeHTMLAttributes(nsIContent* aContent,
+ nsIContent *aOriginalElement,
+ nsAString& aTagPrefix,
+ const nsAString& aTagNamespaceURI,
+ nsIAtom* aTagName,
+ int32_t aNamespace,
+ nsAString& aStr);
+
+ MOZ_MUST_USE
+ virtual bool AppendAndTranslateEntities(const nsAString& aStr,
+ nsAString& aOutputStr) override;
+
+};
+
+nsresult
+NS_NewHTMLContentSerializer(nsIContentSerializer** aSerializer);
+
+#endif
diff --git a/dom/base/nsHistory.cpp b/dom/base/nsHistory.cpp
new file mode 100644
index 000000000..3459df4b0
--- /dev/null
+++ b/dom/base/nsHistory.cpp
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIDocShell.h"
+#include "nsIWebNavigation.h"
+#include "nsIURI.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsReadableUtils.h"
+#include "nsContentUtils.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//
+// 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_ENTRY(nsIDOMHistory) // Empty, needed for extension compat
+NS_INTERFACE_MAP_END
+
+nsHistory::nsHistory(nsPIDOMWindowInner* aInnerWindow)
+ : mInnerWindow(do_GetWeakReference(aInnerWindow))
+{
+ MOZ_ASSERT(aInnerWindow->IsInnerWindow());
+}
+
+nsHistory::~nsHistory()
+{
+}
+
+nsPIDOMWindowInner*
+nsHistory::GetParentObject() const
+{
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ return win;
+}
+
+JSObject*
+nsHistory::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HistoryBinding::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
+ nsCOMPtr<nsISHistory> sHistory = GetSessionHistory();
+ if (!sHistory) {
+ aRv.Throw(NS_ERROR_FAILURE);
+
+ return 0;
+ }
+
+ int32_t len;
+ nsresult rv = sHistory->GetCount(&len);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+
+ return 0;
+ }
+
+ 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<nsIDocument> doc =
+ do_QueryInterface(win->GetExtantDoc());
+ if (!doc) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ nsCOMPtr<nsIVariant> variant;
+ doc->GetStateObject(getter_AddRefs(variant));
+
+ if (variant) {
+ aRv = variant->GetAsJSVal(aResult);
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (!JS_WrapValue(aCx, aResult)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ return;
+ }
+
+ aResult.setNull();
+}
+
+void
+nsHistory::Go(int32_t aDelta, ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+
+ return;
+ }
+
+ if (!aDelta) {
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (nsIDocShell* docShell = GetDocShell()) {
+ window = docShell->GetWindow();
+ }
+
+ if (window && window->IsHandlingResizeEvent()) {
+ // history.go(0) (aka 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.
+
+ nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+
+ nsIPresShell *shell;
+ nsPresContext *pcx;
+ if (doc && (shell = doc->GetShell()) && (pcx = shell->GetPresContext())) {
+ pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW, eRestyle_Subtree);
+ }
+
+ return;
+ }
+ }
+
+ nsCOMPtr<nsISHistory> session_history = GetSessionHistory();
+ nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(session_history));
+ if (!webnav) {
+ aRv.Throw(NS_ERROR_FAILURE);
+
+ return;
+ }
+
+ int32_t curIndex = -1;
+ int32_t len = 0;
+ session_history->GetIndex(&curIndex);
+ session_history->GetCount(&len);
+
+ int32_t index = curIndex + aDelta;
+ if (index > -1 && index < len)
+ webnav->GotoIndex(index);
+
+ // Ignore the return value from GotoIndex(), since returning errors
+ // from GotoIndex() can lead to exceptions and a possible leak
+ // of history length
+}
+
+void
+nsHistory::Back(ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+
+ return;
+ }
+
+ nsCOMPtr<nsISHistory> sHistory = GetSessionHistory();
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(sHistory));
+ if (!webNav) {
+ aRv.Throw(NS_ERROR_FAILURE);
+
+ return;
+ }
+
+ webNav->GoBack();
+}
+
+void
+nsHistory::Forward(ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
+ if (!win || !win->HasActiveDocument()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+
+ return;
+ }
+
+ nsCOMPtr<nsISHistory> sHistory = GetSessionHistory();
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(sHistory));
+ if (!webNav) {
+ aRv.Throw(NS_ERROR_FAILURE);
+
+ return;
+ }
+
+ webNav->GoForward();
+}
+
+void
+nsHistory::PushState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ ErrorResult& aRv)
+{
+ PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, false);
+}
+
+void
+nsHistory::ReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ ErrorResult& aRv)
+{
+ PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, true);
+}
+
+void
+nsHistory::PushOrReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ 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;
+ }
+
+ // 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);
+}
+
+nsIDocShell*
+nsHistory::GetDocShell() const
+{
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mInnerWindow);
+ if (!win) {
+ return nullptr;
+ }
+ return win->GetDocShell();
+}
+
+already_AddRefed<nsISHistory>
+nsHistory::GetSessionHistory() const
+{
+ nsIDocShell *docShell = GetDocShell();
+ NS_ENSURE_TRUE(docShell, nullptr);
+
+ // Get the root DocShell from it
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(root));
+ NS_ENSURE_TRUE(webNav, nullptr);
+
+ nsCOMPtr<nsISHistory> shistory;
+
+ // Get SH from nsIWebNavigation
+ webNav->GetSessionHistory(getter_AddRefs(shistory));
+
+ return shistory.forget();
+}
diff --git a/dom/base/nsHistory.h b/dom/base/nsHistory.h
new file mode 100644
index 000000000..be556934d
--- /dev/null
+++ b/dom/base/nsHistory.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 nsHistory_h___
+#define nsHistory_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/HistoryBinding.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDOMHistory.h"
+#include "nsPIDOMWindow.h" // for GetParentObject
+#include "nsStringFwd.h"
+#include "nsWrapperCache.h"
+
+class nsIDocShell;
+class nsISHistory;
+class nsIWeakReference;
+class nsPIDOMWindowInner;
+
+// Script "History" object
+class nsHistory final : public nsIDOMHistory, // Empty, needed for extension
+ // backwards compat
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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, mozilla::ErrorResult& aRv);
+ void Back(mozilla::ErrorResult& aRv);
+ void Forward(mozilla::ErrorResult& aRv);
+ void PushState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ mozilla::ErrorResult& aRv);
+ void ReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ mozilla::ErrorResult& aRv);
+
+protected:
+ virtual ~nsHistory();
+
+ nsIDocShell* GetDocShell() const;
+
+ void PushOrReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
+ const nsAString& aTitle, const nsAString& aUrl,
+ mozilla::ErrorResult& aRv, bool aReplace);
+
+ already_AddRefed<nsISHistory> GetSessionHistory() const;
+
+ nsCOMPtr<nsIWeakReference> mInnerWindow;
+};
+
+#endif /* nsHistory_h___ */
diff --git a/dom/base/nsHostObjectProtocolHandler.cpp b/dom/base/nsHostObjectProtocolHandler.cpp
new file mode 100644
index 000000000..f2fdfc530
--- /dev/null
+++ b/dom/base/nsHostObjectProtocolHandler.cpp
@@ -0,0 +1,1087 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsHostObjectProtocolHandler.h"
+
+#include "DOMMediaStream.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "mozilla/dom/MediaSource.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsClassHashtable.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsHostObjectURI.h"
+#include "nsIMemoryReporter.h"
+#include "nsIPrincipal.h"
+#include "nsIUUIDGenerator.h"
+#include "nsNetUtil.h"
+
+#define RELEASING_TIMER 1000
+
+using mozilla::DOMMediaStream;
+using mozilla::dom::BlobImpl;
+using mozilla::dom::MediaSource;
+using mozilla::ErrorResult;
+using mozilla::net::LoadInfo;
+using mozilla::Move;
+using mozilla::Unused;
+
+// -----------------------------------------------------------------------
+// Hash table
+struct DataInfo
+{
+ enum ObjectType {
+ eBlobImpl,
+ eMediaStream,
+ eMediaSource
+ };
+
+ DataInfo(BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal)
+ : mObjectType(eBlobImpl)
+ , mBlobImpl(aBlobImpl)
+ , mPrincipal(aPrincipal)
+ {}
+
+ DataInfo(DOMMediaStream* aMediaStream, nsIPrincipal* aPrincipal)
+ : mObjectType(eMediaStream)
+ , mMediaStream(aMediaStream)
+ , mPrincipal(aPrincipal)
+ {}
+
+ DataInfo(MediaSource* aMediaSource, nsIPrincipal* aPrincipal)
+ : mObjectType(eMediaSource)
+ , mMediaSource(aMediaSource)
+ , mPrincipal(aPrincipal)
+ {}
+
+ ObjectType mObjectType;
+
+ RefPtr<BlobImpl> mBlobImpl;
+ RefPtr<DOMMediaStream> mMediaStream;
+ RefPtr<MediaSource> mMediaSource;
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCString mStack;
+
+ // WeakReferences of nsHostObjectURI objects.
+ nsTArray<nsWeakPtr> mURIs;
+};
+
+static nsClassHashtable<nsCStringHashKey, DataInfo>* gDataTable;
+
+static DataInfo*
+GetDataInfo(const nsACString& aUri)
+{
+ if (!gDataTable) {
+ return nullptr;
+ }
+
+ DataInfo* res;
+
+ // Let's remove any fragment and query from this URI.
+ int32_t hasFragmentPos = aUri.FindChar('#');
+ int32_t hasQueryPos = aUri.FindChar('?');
+
+ int32_t pos = -1;
+ if (hasFragmentPos >= 0 && hasQueryPos >= 0) {
+ pos = std::min(hasFragmentPos, hasQueryPos);
+ } else if (hasFragmentPos >= 0) {
+ pos = hasFragmentPos;
+ } else {
+ pos = hasQueryPos;
+ }
+
+ if (pos < 0) {
+ gDataTable->Get(aUri, &res);
+ } else {
+ gDataTable->Get(StringHead(aUri, pos), &res);
+ }
+
+ return res;
+}
+
+static DataInfo*
+GetDataInfoFromURI(nsIURI* aURI)
+{
+ if (!aURI) {
+ return nullptr;
+ }
+
+ nsCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ return GetDataInfo(spec);
+}
+
+// Memory reporting for the hash table.
+namespace mozilla {
+
+void
+BroadcastBlobURLRegistration(const nsACString& aURI,
+ BlobImpl* aBlobImpl,
+ nsIPrincipal* aPrincipal)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBlobImpl);
+
+ if (XRE_IsParentProcess()) {
+ dom::ContentParent::BroadcastBlobURLRegistration(aURI, aBlobImpl,
+ aPrincipal);
+ return;
+ }
+
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ dom::BlobChild* actor = cc->GetOrCreateActorForBlobImpl(aBlobImpl);
+ if (NS_WARN_IF(!actor)) {
+ return;
+ }
+
+ Unused << NS_WARN_IF(!cc->SendStoreAndBroadcastBlobURLRegistration(
+ nsCString(aURI), actor, IPC::Principal(aPrincipal)));
+}
+
+void
+BroadcastBlobURLUnregistration(const nsACString& aURI, DataInfo* aInfo)
+{
+ MOZ_ASSERT(aInfo);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_IsParentProcess()) {
+ dom::ContentParent::BroadcastBlobURLUnregistration(aURI);
+ return;
+ }
+
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+ Unused << NS_WARN_IF(!cc->SendUnstoreAndBroadcastBlobURLUnregistration(
+ nsCString(aURI)));
+}
+
+class HostObjectURLsReporter final : public nsIMemoryReporter
+{
+ ~HostObjectURLsReporter() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) override
+ {
+ MOZ_COLLECT_REPORT(
+ "host-object-urls", KIND_OTHER, UNITS_COUNT,
+ gDataTable ? gDataTable->Count() : 0,
+ "The number of host objects stored for access via URLs "
+ "(e.g. blobs passed to URL.createObjectURL).");
+
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(HostObjectURLsReporter, nsIMemoryReporter)
+
+class BlobURLsReporter final : public nsIMemoryReporter
+{
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD CollectReports(nsIHandleReportCallback* aCallback,
+ nsISupports* aData, bool aAnonymize) override
+ {
+ if (!gDataTable) {
+ return NS_OK;
+ }
+
+ nsDataHashtable<nsPtrHashKey<BlobImpl>, uint32_t> refCounts;
+
+ // Determine number of URLs per BlobImpl, to handle the case where it's > 1.
+ for (auto iter = gDataTable->Iter(); !iter.Done(); iter.Next()) {
+ if (iter.UserData()->mObjectType != DataInfo::eBlobImpl) {
+ continue;
+ }
+
+ BlobImpl* blobImpl = iter.UserData()->mBlobImpl;
+ MOZ_ASSERT(blobImpl);
+
+ refCounts.Put(blobImpl, refCounts.Get(blobImpl) + 1);
+ }
+
+ for (auto iter = gDataTable->Iter(); !iter.Done(); iter.Next()) {
+ nsCStringHashKey::KeyType key = iter.Key();
+ DataInfo* info = iter.UserData();
+
+ if (iter.UserData()->mObjectType == DataInfo::eBlobImpl) {
+ BlobImpl* blobImpl = iter.UserData()->mBlobImpl;
+ MOZ_ASSERT(blobImpl);
+
+ NS_NAMED_LITERAL_CSTRING(desc,
+ "A blob URL allocated with URL.createObjectURL; the referenced "
+ "blob cannot be freed until all URLs for it have been explicitly "
+ "invalidated with URL.revokeObjectURL.");
+ nsAutoCString path, url, owner, specialDesc;
+ uint64_t size = 0;
+ uint32_t refCount = 1;
+ DebugOnly<bool> blobImplWasCounted;
+
+ blobImplWasCounted = refCounts.Get(blobImpl, &refCount);
+ MOZ_ASSERT(blobImplWasCounted);
+ MOZ_ASSERT(refCount > 0);
+
+ bool isMemoryFile = blobImpl->IsMemoryFile();
+
+ if (isMemoryFile) {
+ ErrorResult rv;
+ size = blobImpl->GetSize(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ size = 0;
+ }
+ }
+
+ path = isMemoryFile ? "memory-blob-urls/" : "file-blob-urls/";
+ BuildPath(path, key, info, aAnonymize);
+
+ if (refCount > 1) {
+ nsAutoCString addrStr;
+
+ addrStr = "0x";
+ addrStr.AppendInt((uint64_t)(BlobImpl*)blobImpl, 16);
+
+ path += " ";
+ path.AppendInt(refCount);
+ path += "@";
+ path += addrStr;
+
+ specialDesc = desc;
+ specialDesc += "\n\nNOTE: This blob (address ";
+ specialDesc += addrStr;
+ specialDesc += ") has ";
+ specialDesc.AppendInt(refCount);
+ specialDesc += " URLs.";
+ if (isMemoryFile) {
+ specialDesc += " Its size is divided ";
+ specialDesc += refCount > 2 ? "among" : "between";
+ specialDesc += " them in this report.";
+ }
+ }
+
+ const nsACString& descString = specialDesc.IsEmpty()
+ ? static_cast<const nsACString&>(desc)
+ : static_cast<const nsACString&>(specialDesc);
+ if (isMemoryFile) {
+ aCallback->Callback(EmptyCString(),
+ path,
+ KIND_OTHER,
+ UNITS_BYTES,
+ size / refCount,
+ descString,
+ aData);
+ } else {
+ aCallback->Callback(EmptyCString(),
+ path,
+ KIND_OTHER,
+ UNITS_COUNT,
+ 1,
+ descString,
+ aData);
+ }
+ continue;
+ }
+
+ // Just report the path for the DOMMediaStream or MediaSource.
+ nsAutoCString path;
+ path = iter.UserData()->mObjectType == DataInfo::eMediaSource
+ ? "media-source-urls/" : "dom-media-stream-urls/";
+ BuildPath(path, key, info, aAnonymize);
+
+ NS_NAMED_LITERAL_CSTRING(desc,
+ "An object URL allocated with URL.createObjectURL; the referenced "
+ "data cannot be freed until all URLs for it have been explicitly "
+ "invalidated with URL.revokeObjectURL.");
+
+ aCallback->Callback(EmptyCString(), path, KIND_OTHER, UNITS_COUNT, 1,
+ desc, aData);
+ }
+
+ return NS_OK;
+ }
+
+ // Initialize info->mStack to record JS stack info, if enabled.
+ // The string generated here is used in ReportCallback, below.
+ static void GetJSStackForBlob(DataInfo* aInfo)
+ {
+ nsCString& stack = aInfo->mStack;
+ MOZ_ASSERT(stack.IsEmpty());
+ const uint32_t maxFrames = Preferences::GetUint("memory.blob_report.stack_frames");
+
+ if (maxFrames == 0) {
+ return;
+ }
+
+ nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack(maxFrames);
+
+ nsAutoCString origin;
+ nsCOMPtr<nsIURI> principalURI;
+ if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI)))
+ && principalURI) {
+ principalURI->GetPrePath(origin);
+ }
+
+ // If we got a frame, we better have a current JSContext. This is cheating
+ // a bit; ideally we'd have our caller pass in a JSContext, or have
+ // GetCurrentJSStack() hand out the JSContext it found.
+ JSContext* cx = frame ? nsContentUtils::GetCurrentJSContext() : nullptr;
+
+ for (uint32_t i = 0; frame; ++i) {
+ nsString fileNameUTF16;
+ int32_t lineNumber = 0;
+
+ frame->GetFilename(cx, fileNameUTF16);
+ frame->GetLineNumber(cx, &lineNumber);
+
+ if (!fileNameUTF16.IsEmpty()) {
+ NS_ConvertUTF16toUTF8 fileName(fileNameUTF16);
+ stack += "js(";
+ if (!origin.IsEmpty()) {
+ // Make the file name root-relative for conciseness if possible.
+ const char* originData;
+ uint32_t originLen;
+
+ originLen = origin.GetData(&originData);
+ // If fileName starts with origin + "/", cut up to that "/".
+ if (fileName.Length() >= originLen + 1 &&
+ memcmp(fileName.get(), originData, originLen) == 0 &&
+ fileName[originLen] == '/') {
+ fileName.Cut(0, originLen);
+ }
+ }
+ fileName.ReplaceChar('/', '\\');
+ stack += fileName;
+ if (lineNumber > 0) {
+ stack += ", line=";
+ stack.AppendInt(lineNumber);
+ }
+ stack += ")/";
+ }
+
+ nsCOMPtr<nsIStackFrame> caller;
+ nsresult rv = frame->GetCaller(cx, getter_AddRefs(caller));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ caller.swap(frame);
+ }
+ }
+
+ private:
+ ~BlobURLsReporter() {}
+
+ static void BuildPath(nsAutoCString& path,
+ nsCStringHashKey::KeyType aKey,
+ DataInfo* aInfo,
+ bool anonymize)
+ {
+ nsCOMPtr<nsIURI> principalURI;
+ nsAutoCString url, owner;
+ if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI))) &&
+ principalURI != nullptr &&
+ NS_SUCCEEDED(principalURI->GetSpec(owner)) &&
+ !owner.IsEmpty()) {
+ owner.ReplaceChar('/', '\\');
+ path += "owner(";
+ if (anonymize) {
+ path += "<anonymized>";
+ } else {
+ path += owner;
+ }
+ path += ")";
+ } else {
+ path += "owner unknown";
+ }
+ path += "/";
+ if (anonymize) {
+ path += "<anonymized-stack>";
+ } else {
+ path += aInfo->mStack;
+ }
+ url = aKey;
+ url.ReplaceChar('/', '\\');
+ if (anonymize) {
+ path += "<anonymized-url>";
+ } else {
+ path += url;
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS(BlobURLsReporter, nsIMemoryReporter)
+
+class ReleasingTimerHolder final : public nsITimerCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ static void
+ Create(nsTArray<nsWeakPtr>&& aArray)
+ {
+ RefPtr<ReleasingTimerHolder> holder = new ReleasingTimerHolder(Move(aArray));
+ holder->mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+
+ // If we are shutting down, we are not able to create a timer.
+ if (!holder->mTimer) {
+ return;
+ }
+
+ nsresult rv = holder->mTimer->InitWithCallback(holder, RELEASING_TIMER,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override
+ {
+ for (uint32_t i = 0; i < mURIs.Length(); ++i) {
+ nsCOMPtr<nsIURI> uri = do_QueryReferent(mURIs[i]);
+ if (uri) {
+ static_cast<nsHostObjectURI*>(uri.get())->ForgetBlobImpl();
+ }
+ }
+
+ return NS_OK;
+ }
+
+private:
+ explicit ReleasingTimerHolder(nsTArray<nsWeakPtr>&& aArray)
+ : mURIs(aArray)
+ {}
+
+ ~ReleasingTimerHolder()
+ {}
+
+ nsTArray<nsWeakPtr> mURIs;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS(ReleasingTimerHolder, nsITimerCallback)
+
+} // namespace mozilla
+
+template<typename T>
+static nsresult
+AddDataEntryInternal(const nsACString& aURI, T aObject,
+ nsIPrincipal* aPrincipal)
+{
+ if (!gDataTable) {
+ gDataTable = new nsClassHashtable<nsCStringHashKey, DataInfo>;
+ }
+
+ DataInfo* info = new DataInfo(aObject, aPrincipal);
+ mozilla::BlobURLsReporter::GetJSStackForBlob(info);
+
+ gDataTable->Put(aURI, info);
+ return NS_OK;
+}
+
+void
+nsHostObjectProtocolHandler::Init(void)
+{
+ static bool initialized = false;
+
+ if (!initialized) {
+ initialized = true;
+ RegisterStrongMemoryReporter(new mozilla::HostObjectURLsReporter());
+ RegisterStrongMemoryReporter(new mozilla::BlobURLsReporter());
+ }
+}
+
+nsHostObjectProtocolHandler::nsHostObjectProtocolHandler()
+{
+ Init();
+}
+
+/* static */ nsresult
+nsHostObjectProtocolHandler::AddDataEntry(BlobImpl* aBlobImpl,
+ nsIPrincipal* aPrincipal,
+ nsACString& aUri)
+{
+ Init();
+
+ nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddDataEntryInternal(aUri, aBlobImpl, aPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::BroadcastBlobURLRegistration(aUri, aBlobImpl, aPrincipal);
+ return NS_OK;
+}
+
+/* static */ nsresult
+nsHostObjectProtocolHandler::AddDataEntry(DOMMediaStream* aMediaStream,
+ nsIPrincipal* aPrincipal,
+ nsACString& aUri)
+{
+ Init();
+
+ nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddDataEntryInternal(aUri, aMediaStream, aPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/* static */ nsresult
+nsHostObjectProtocolHandler::AddDataEntry(MediaSource* aMediaSource,
+ nsIPrincipal* aPrincipal,
+ nsACString& aUri)
+{
+ Init();
+
+ nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddDataEntryInternal(aUri, aMediaSource, aPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/* static */ nsresult
+nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aURI,
+ nsIPrincipal* aPrincipal,
+ mozilla::dom::BlobImpl* aBlobImpl)
+{
+ return AddDataEntryInternal(aURI, aBlobImpl, aPrincipal);
+}
+
+/* static */ bool
+nsHostObjectProtocolHandler::GetAllBlobURLEntries(
+ nsTArray<mozilla::dom::BlobURLRegistrationData>& aRegistrations,
+ mozilla::dom::ContentParent* aCP)
+{
+ MOZ_ASSERT(aCP);
+
+ if (!gDataTable) {
+ return true;
+ }
+
+ for (auto iter = gDataTable->ConstIter(); !iter.Done(); iter.Next()) {
+ DataInfo* info = iter.UserData();
+ MOZ_ASSERT(info);
+
+ if (info->mObjectType != DataInfo::eBlobImpl) {
+ continue;
+ }
+
+ MOZ_ASSERT(info->mBlobImpl);
+ mozilla::dom::PBlobParent* blobParent =
+ aCP->GetOrCreateActorForBlobImpl(info->mBlobImpl);
+ if (!blobParent) {
+ return false;
+ }
+
+ aRegistrations.AppendElement(mozilla::dom::BlobURLRegistrationData(
+ nsCString(iter.Key()), blobParent, nullptr,
+ IPC::Principal(info->mPrincipal)));
+ }
+
+ return true;
+}
+
+/*static */ void
+nsHostObjectProtocolHandler::RemoveDataEntry(const nsACString& aUri,
+ bool aBroadcastToOtherProcesses)
+{
+ if (!gDataTable) {
+ return;
+ }
+
+ DataInfo* info = GetDataInfo(aUri);
+ if (!info) {
+ return;
+ }
+
+ if (aBroadcastToOtherProcesses && info->mObjectType == DataInfo::eBlobImpl) {
+ mozilla::BroadcastBlobURLUnregistration(aUri, info);
+ }
+
+ if (!info->mURIs.IsEmpty()) {
+ mozilla::ReleasingTimerHolder::Create(Move(info->mURIs));
+ }
+
+ gDataTable->Remove(aUri);
+ if (gDataTable->Count() == 0) {
+ delete gDataTable;
+ gDataTable = nullptr;
+ }
+}
+
+/* static */ void
+nsHostObjectProtocolHandler::RemoveDataEntries()
+{
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ if (!gDataTable) {
+ return;
+ }
+
+ gDataTable->Clear();
+ delete gDataTable;
+ gDataTable = nullptr;
+}
+
+/* static */ bool
+nsHostObjectProtocolHandler::HasDataEntry(const nsACString& aUri)
+{
+ return !!GetDataInfo(aUri);
+}
+
+/* static */ nsresult
+nsHostObjectProtocolHandler::GenerateURIString(const nsACString &aScheme,
+ nsIPrincipal* aPrincipal,
+ nsACString& aUri)
+{
+ nsresult rv;
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsID id;
+ rv = uuidgen->GenerateUUIDInPlace(&id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char chars[NSID_LENGTH];
+ id.ToProvidedString(chars);
+
+ aUri = aScheme;
+ aUri.Append(':');
+
+ if (aPrincipal) {
+ nsAutoCString origin;
+ rv = nsContentUtils::GetASCIIOrigin(aPrincipal, origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aUri.Append(origin);
+ aUri.Append('/');
+ }
+
+ aUri += Substring(chars + 1, chars + NSID_LENGTH - 2);
+
+ return NS_OK;
+}
+
+/* static */ nsresult
+nsHostObjectProtocolHandler::GenerateURIStringForBlobURL(nsIPrincipal* aPrincipal,
+ nsACString& aUri)
+{
+ return
+ GenerateURIString(NS_LITERAL_CSTRING(BLOBURI_SCHEME), aPrincipal, aUri);
+}
+
+/* static */ nsIPrincipal*
+nsHostObjectProtocolHandler::GetDataEntryPrincipal(const nsACString& aUri)
+{
+ if (!gDataTable) {
+ return nullptr;
+ }
+
+ DataInfo* res = GetDataInfo(aUri);
+
+ if (!res) {
+ return nullptr;
+ }
+
+ return res->mPrincipal;
+}
+
+/* static */ void
+nsHostObjectProtocolHandler::Traverse(const nsACString& aUri,
+ nsCycleCollectionTraversalCallback& aCallback)
+{
+ if (!gDataTable) {
+ return;
+ }
+
+ DataInfo* res;
+ gDataTable->Get(aUri, &res);
+ if (!res) {
+ return;
+ }
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mBlobImpl");
+ aCallback.NoteXPCOMChild(res->mBlobImpl);
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mMediaSource");
+ aCallback.NoteXPCOMChild(res->mMediaSource);
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mMediaStream");
+ aCallback.NoteXPCOMChild(res->mMediaStream);
+}
+
+// -----------------------------------------------------------------------
+// Protocol handler
+
+NS_IMPL_ISUPPORTS(nsHostObjectProtocolHandler, nsIProtocolHandler)
+
+NS_IMETHODIMP
+nsHostObjectProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_SUBSUMERS |
+ URI_NON_PERSISTABLE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectProtocolHandler::GetFlagsForURI(nsIURI *aURI, uint32_t *aResult)
+{
+ Unused << nsHostObjectProtocolHandler::GetProtocolFlags(aResult);
+ if (IsFontTableURI(aURI) || IsBlobURI(aURI)) {
+ *aResult |= URI_IS_LOCAL_RESOURCE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBlobProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ Unused << nsHostObjectProtocolHandler::GetProtocolFlags(result);
+ *result |= URI_IS_LOCAL_RESOURCE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectProtocolHandler::NewURI(const nsACString& aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **aResult)
+{
+ *aResult = nullptr;
+ nsresult rv;
+
+ DataInfo* info = GetDataInfo(aSpec);
+
+ RefPtr<nsHostObjectURI> uri;
+ if (info && info->mObjectType == DataInfo::eBlobImpl) {
+ MOZ_ASSERT(info->mBlobImpl);
+ uri = new nsHostObjectURI(info->mPrincipal, info->mBlobImpl);
+ } else {
+ uri = new nsHostObjectURI(nullptr, nullptr);
+ }
+
+ rv = uri->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_TryToSetImmutable(uri);
+ uri.forget(aResult);
+
+ if (info && info->mObjectType == DataInfo::eBlobImpl) {
+ info->mURIs.AppendElement(do_GetWeakReference(*aResult));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ *result = nullptr;
+
+ nsCOMPtr<nsIURIWithBlobImpl> uriBlobImpl = do_QueryInterface(uri);
+ if (!uriBlobImpl) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ nsCOMPtr<nsISupports> tmp;
+ MOZ_ALWAYS_SUCCEEDS(uriBlobImpl->GetBlobImpl(getter_AddRefs(tmp)));
+ nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(tmp);
+ if (!blobImpl) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+#ifdef DEBUG
+ DataInfo* info = GetDataInfoFromURI(uri);
+
+ // Info can be null, in case this blob URL has been revoked already.
+ if (info) {
+ nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(uri);
+ nsCOMPtr<nsIPrincipal> principal;
+ uriPrinc->GetPrincipal(getter_AddRefs(principal));
+ NS_ASSERTION(info->mPrincipal == principal, "Wrong principal!");
+ }
+#endif
+
+ ErrorResult rv;
+ nsCOMPtr<nsIInputStream> stream;
+ blobImpl->GetInternalStream(getter_AddRefs(stream), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ nsAutoString contentType;
+ blobImpl->GetType(contentType);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
+ uri,
+ stream,
+ NS_ConvertUTF16toUTF8(contentType),
+ EmptyCString(), // aContentCharset
+ aLoadInfo);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ if (blobImpl->IsFile()) {
+ nsString filename;
+ blobImpl->GetName(filename);
+ channel->SetContentDispositionFilename(filename);
+ }
+
+ uint64_t size = blobImpl->GetSize(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ channel->SetOriginalURI(uri);
+ channel->SetContentType(NS_ConvertUTF16toUTF8(contentType));
+ channel->SetContentLength(size);
+
+ channel.forget(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+NS_IMETHODIMP
+nsHostObjectProtocolHandler::AllowPort(int32_t port, const char *scheme,
+ bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBlobProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral(BLOBURI_SCHEME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFontTableProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ Unused << nsHostObjectProtocolHandler::GetProtocolFlags(result);
+ *result |= URI_IS_LOCAL_RESOURCE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFontTableProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral(FONTTABLEURI_SCHEME);
+ return NS_OK;
+}
+
+nsresult
+NS_GetBlobForBlobURI(nsIURI* aURI, BlobImpl** aBlob)
+{
+ NS_ASSERTION(IsBlobURI(aURI), "Only call this with blob URIs");
+
+ *aBlob = nullptr;
+
+ DataInfo* info = GetDataInfoFromURI(aURI);
+ if (!info || info->mObjectType != DataInfo::eBlobImpl) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ RefPtr<BlobImpl> blob = info->mBlobImpl;
+ blob.forget(aBlob);
+ return NS_OK;
+}
+
+nsresult
+NS_GetBlobForBlobURISpec(const nsACString& aSpec, BlobImpl** aBlob)
+{
+ *aBlob = nullptr;
+
+ DataInfo* info = GetDataInfo(aSpec);
+ if (!info || info->mObjectType != DataInfo::eBlobImpl) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ RefPtr<BlobImpl> blob = info->mBlobImpl;
+ blob.forget(aBlob);
+ return NS_OK;
+}
+
+nsresult
+NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream)
+{
+ RefPtr<BlobImpl> blobImpl;
+ ErrorResult rv;
+ rv = NS_GetBlobForBlobURI(aURI, getter_AddRefs(blobImpl));
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ blobImpl->GetInternalStream(aStream, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+NS_GetStreamForMediaStreamURI(nsIURI* aURI, mozilla::DOMMediaStream** aStream)
+{
+ NS_ASSERTION(IsMediaStreamURI(aURI), "Only call this with mediastream URIs");
+
+ DataInfo* info = GetDataInfoFromURI(aURI);
+ if (!info || info->mObjectType != DataInfo::eMediaStream) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ RefPtr<DOMMediaStream> mediaStream = info->mMediaStream;
+ mediaStream.forget(aStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFontTableProtocolHandler::NewURI(const nsACString& aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **aResult)
+{
+ RefPtr<nsIURI> uri;
+
+ // Either you got here via a ref or a fonttable: uri
+ if (aSpec.Length() && aSpec.CharAt(0) == '#') {
+ nsresult rv = aBaseURI->CloneIgnoringRef(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uri->SetRef(aSpec);
+ } else {
+ // Relative URIs (other than #ref) are not meaningful within the
+ // fonttable: scheme.
+ // If aSpec is a relative URI -other- than a bare #ref,
+ // this will leave uri empty, and we'll return a failure code below.
+ uri = new mozilla::net::nsSimpleURI();
+ nsresult rv = uri->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool schemeIs;
+ if (NS_FAILED(uri->SchemeIs(FONTTABLEURI_SCHEME, &schemeIs)) || !schemeIs) {
+ NS_WARNING("Non-fonttable spec in nsFontTableProtocolHander");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ uri.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+NS_GetSourceForMediaSourceURI(nsIURI* aURI, mozilla::dom::MediaSource** aSource)
+{
+ NS_ASSERTION(IsMediaSourceURI(aURI), "Only call this with mediasource URIs");
+
+ *aSource = nullptr;
+
+ DataInfo* info = GetDataInfoFromURI(aURI);
+ if (!info || info->mObjectType != DataInfo::eMediaSource) {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+
+ RefPtr<MediaSource> mediaSource = info->mMediaSource;
+ mediaSource.forget(aSource);
+ return NS_OK;
+}
+
+#define NS_BLOBPROTOCOLHANDLER_CID \
+{ 0xb43964aa, 0xa078, 0x44b2, \
+ { 0xb0, 0x6b, 0xfd, 0x4d, 0x1b, 0x17, 0x2e, 0x66 } }
+
+#define NS_FONTTABLEPROTOCOLHANDLER_CID \
+{ 0x3fc8f04e, 0xd719, 0x43ca, \
+ { 0x9a, 0xd0, 0x18, 0xee, 0x32, 0x02, 0x11, 0xf2 } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBlobProtocolHandler)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFontTableProtocolHandler)
+
+NS_DEFINE_NAMED_CID(NS_BLOBPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_FONTTABLEPROTOCOLHANDLER_CID);
+
+static const mozilla::Module::CIDEntry kHostObjectProtocolHandlerCIDs[] = {
+ { &kNS_BLOBPROTOCOLHANDLER_CID, false, nullptr, nsBlobProtocolHandlerConstructor },
+ { &kNS_FONTTABLEPROTOCOLHANDLER_CID, false, nullptr, nsFontTableProtocolHandlerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kHostObjectProtocolHandlerContracts[] = {
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX BLOBURI_SCHEME, &kNS_BLOBPROTOCOLHANDLER_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX FONTTABLEURI_SCHEME, &kNS_FONTTABLEPROTOCOLHANDLER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kHostObjectProtocolHandlerModule = {
+ mozilla::Module::kVersion,
+ kHostObjectProtocolHandlerCIDs,
+ kHostObjectProtocolHandlerContracts
+};
+
+NSMODULE_DEFN(HostObjectProtocolHandler) = &kHostObjectProtocolHandlerModule;
+
+bool IsType(nsIURI* aUri, DataInfo::ObjectType aType)
+{
+ DataInfo* info = GetDataInfoFromURI(aUri);
+ if (!info) {
+ return false;
+ }
+
+ return info->mObjectType == aType;
+}
+
+bool IsBlobURI(nsIURI* aUri)
+{
+ return IsType(aUri, DataInfo::eBlobImpl);
+}
+
+bool IsMediaStreamURI(nsIURI* aUri)
+{
+ return IsType(aUri, DataInfo::eMediaStream);
+}
+
+bool IsMediaSourceURI(nsIURI* aUri)
+{
+ return IsType(aUri, DataInfo::eMediaSource);
+}
diff --git a/dom/base/nsHostObjectProtocolHandler.h b/dom/base/nsHostObjectProtocolHandler.h
new file mode 100644
index 000000000..3df9fc004
--- /dev/null
+++ b/dom/base/nsHostObjectProtocolHandler.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsHostObjectProtocolHandler_h
+#define nsHostObjectProtocolHandler_h
+
+#include "mozilla/Attributes.h"
+#include "nsIProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsTArray.h"
+
+#define BLOBURI_SCHEME "blob"
+#define FONTTABLEURI_SCHEME "moz-fonttable"
+#define RTSPURI_SCHEME "rtsp"
+
+class nsIPrincipal;
+
+namespace mozilla {
+class BlobURLsReporter;
+class DOMMediaStream;
+namespace dom {
+class BlobImpl;
+class BlobURLRegistrationData;
+class ContentParent;
+class MediaSource;
+} // namespace dom
+} // namespace mozilla
+
+class nsHostObjectProtocolHandler : public nsIProtocolHandler
+ , public nsIProtocolHandlerWithDynamicFlags
+{
+public:
+ nsHostObjectProtocolHandler();
+ NS_DECL_ISUPPORTS
+
+ // nsIProtocolHandler methods, except for GetScheme which is only defined
+ // in subclasses.
+ NS_IMETHOD GetDefaultPort(int32_t *aDefaultPort) override;
+ NS_IMETHOD GetProtocolFlags(uint32_t *aProtocolFlags) override;
+ NS_IMETHOD NewURI(const nsACString & aSpec, const char * aOriginCharset, nsIURI *aBaseURI, nsIURI * *_retval) override;
+ NS_IMETHOD NewChannel2(nsIURI *aURI, nsILoadInfo *aLoadinfo, nsIChannel * *_retval) override;
+ NS_IMETHOD NewChannel(nsIURI *aURI, nsIChannel * *_retval) override;
+ NS_IMETHOD AllowPort(int32_t port, const char * scheme, bool *_retval) override;
+
+ // nsIProtocolHandlerWithDynamicFlags methods
+ NS_IMETHOD GetFlagsForURI(nsIURI *aURI, uint32_t *aResult) override;
+
+ // If principal is not null, its origin will be used to generate the URI.
+ static nsresult GenerateURIString(const nsACString &aScheme,
+ nsIPrincipal* aPrincipal,
+ nsACString &aUri);
+ static nsresult GenerateURIStringForBlobURL(nsIPrincipal* aPrincipal,
+ nsACString &aUri);
+
+ // Methods for managing uri->object mapping
+ // AddDataEntry creates the URI with the given scheme and returns it in aUri
+ static nsresult AddDataEntry(mozilla::dom::BlobImpl* aBlobImpl,
+ nsIPrincipal* aPrincipal,
+ nsACString& aUri);
+ static nsresult AddDataEntry(mozilla::DOMMediaStream* aMediaStream,
+ nsIPrincipal* aPrincipal,
+ nsACString& aUri);
+ static nsresult AddDataEntry(mozilla::dom::MediaSource* aMediaSource,
+ nsIPrincipal* aPrincipal,
+ nsACString& aUri);
+ // IPC only
+ static nsresult AddDataEntry(const nsACString& aURI,
+ nsIPrincipal* aPrincipal,
+ mozilla::dom::BlobImpl* aBlobImpl);
+
+ static void RemoveDataEntry(const nsACString& aUri,
+ bool aBroadcastToOTherProcesses = true);
+
+ // This is for IPC only.
+ static void RemoveDataEntries();
+
+ static bool HasDataEntry(const nsACString& aUri);
+
+ static nsIPrincipal* GetDataEntryPrincipal(const nsACString& aUri);
+ static void Traverse(const nsACString& aUri, nsCycleCollectionTraversalCallback& aCallback);
+
+ static bool
+ GetAllBlobURLEntries(nsTArray<mozilla::dom::BlobURLRegistrationData>& aRegistrations,
+ mozilla::dom::ContentParent* aCP);
+
+protected:
+ virtual ~nsHostObjectProtocolHandler() {}
+
+private:
+ static void Init();
+};
+
+class nsBlobProtocolHandler : public nsHostObjectProtocolHandler
+{
+public:
+ NS_IMETHOD GetProtocolFlags(uint32_t *aProtocolFlags) override;
+ NS_IMETHOD GetScheme(nsACString &result) override;
+};
+
+class nsMediaSourceProtocolHandler : public nsHostObjectProtocolHandler
+{
+public:
+ NS_IMETHOD GetScheme(nsACString &result) override;
+};
+
+class nsFontTableProtocolHandler : public nsHostObjectProtocolHandler
+{
+public:
+ NS_IMETHOD GetProtocolFlags(uint32_t *aProtocolFlags) override;
+ NS_IMETHOD GetScheme(nsACString &result) override;
+ NS_IMETHOD NewURI(const nsACString & aSpec,
+ const char *aOriginCharset,
+ nsIURI *aBaseURI,
+ nsIURI **_retval) override;
+};
+
+bool IsBlobURI(nsIURI* aUri);
+bool IsMediaStreamURI(nsIURI* aUri);
+bool IsMediaSourceURI(nsIURI* aUri);
+
+inline bool IsRtspURI(nsIURI* aUri)
+{
+ bool isRtsp;
+ return NS_SUCCEEDED(aUri->SchemeIs(RTSPURI_SCHEME, &isRtsp)) && isRtsp;
+}
+
+inline bool IsFontTableURI(nsIURI* aUri)
+{
+ bool isFont;
+ return NS_SUCCEEDED(aUri->SchemeIs(FONTTABLEURI_SCHEME, &isFont)) && isFont;
+}
+
+extern nsresult
+NS_GetBlobForBlobURI(nsIURI* aURI, mozilla::dom::BlobImpl** aBlob);
+
+extern nsresult
+NS_GetBlobForBlobURISpec(const nsACString& aSpec, mozilla::dom::BlobImpl** aBlob);
+
+extern nsresult
+NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream);
+
+extern nsresult
+NS_GetStreamForMediaStreamURI(nsIURI* aURI, mozilla::DOMMediaStream** aStream);
+
+extern nsresult
+NS_GetSourceForMediaSourceURI(nsIURI* aURI, mozilla::dom::MediaSource** aSource);
+
+#endif /* nsHostObjectProtocolHandler_h */
diff --git a/dom/base/nsHostObjectURI.cpp b/dom/base/nsHostObjectURI.cpp
new file mode 100644
index 000000000..0d505c0ef
--- /dev/null
+++ b/dom/base/nsHostObjectURI.cpp
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsHostObjectURI.h"
+
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsHostObjectProtocolHandler.h"
+
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+
+static NS_DEFINE_CID(kHOSTOBJECTURICID, NS_HOSTOBJECTURI_CID);
+
+static NS_DEFINE_CID(kThisSimpleURIImplementationCID,
+ NS_THIS_SIMPLEURI_IMPLEMENTATION_CID);
+
+NS_IMPL_ADDREF_INHERITED(nsHostObjectURI, mozilla::net::nsSimpleURI)
+NS_IMPL_RELEASE_INHERITED(nsHostObjectURI, mozilla::net::nsSimpleURI)
+
+NS_INTERFACE_MAP_BEGIN(nsHostObjectURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURIWithBlobImpl)
+ NS_INTERFACE_MAP_ENTRY(nsIURIWithPrincipal)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ if (aIID.Equals(kHOSTOBJECTURICID))
+ foundInterface = static_cast<nsIURI*>(this);
+ else if (aIID.Equals(kThisSimpleURIImplementationCID)) {
+ // Need to return explicitly here, because if we just set foundInterface
+ // to null the NS_INTERFACE_MAP_END_INHERITING will end up calling into
+ // nsSimplURI::QueryInterface and finding something for this CID.
+ *aInstancePtr = nullptr;
+ return NS_NOINTERFACE;
+ }
+ else
+NS_INTERFACE_MAP_END_INHERITING(mozilla::net::nsSimpleURI)
+
+// nsIURIWithBlobImpl methods:
+
+NS_IMETHODIMP
+nsHostObjectURI::GetBlobImpl(nsISupports** aBlobImpl)
+{
+ RefPtr<mozilla::dom::BlobImpl> blobImpl(mBlobImpl);
+ blobImpl.forget(aBlobImpl);
+ return NS_OK;
+}
+
+// nsIURIWithPrincipal methods:
+
+NS_IMETHODIMP
+nsHostObjectURI::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+ NS_IF_ADDREF(*aPrincipal = mPrincipal);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::GetPrincipalUri(nsIURI** aUri)
+{
+ if (mPrincipal) {
+ mPrincipal->GetURI(aUri);
+ }
+ else {
+ *aUri = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// nsISerializable methods:
+
+NS_IMETHODIMP
+nsHostObjectURI::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv = mozilla::net::nsSimpleURI::Read(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> supports;
+ rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPrincipal = do_QueryInterface(supports, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::Write(nsIObjectOutputStream* aStream)
+{
+ nsresult rv = mozilla::net::nsSimpleURI::Write(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_WriteOptionalCompoundObject(aStream, mPrincipal,
+ NS_GET_IID(nsIPrincipal),
+ true);
+}
+
+// nsIIPCSerializableURI methods:
+void
+nsHostObjectURI::Serialize(mozilla::ipc::URIParams& aParams)
+{
+ using namespace mozilla::ipc;
+
+ HostObjectURIParams hostParams;
+ URIParams simpleParams;
+
+ mozilla::net::nsSimpleURI::Serialize(simpleParams);
+ hostParams.simpleParams() = simpleParams;
+
+ if (mPrincipal) {
+ PrincipalInfo info;
+ nsresult rv = PrincipalToPrincipalInfo(mPrincipal, &info);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ hostParams.principal() = info;
+ } else {
+ hostParams.principal() = mozilla::void_t();
+ }
+
+ aParams = hostParams;
+}
+
+bool
+nsHostObjectURI::Deserialize(const mozilla::ipc::URIParams& aParams)
+{
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::THostObjectURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const HostObjectURIParams& hostParams = aParams.get_HostObjectURIParams();
+
+ if (!mozilla::net::nsSimpleURI::Deserialize(hostParams.simpleParams())) {
+ return false;
+ }
+
+ if (hostParams.principal().type() == OptionalPrincipalInfo::Tvoid_t) {
+ return true;
+ }
+
+ mPrincipal = PrincipalInfoToPrincipal(hostParams.principal().get_PrincipalInfo());
+ if (!mPrincipal) {
+ return false;
+ }
+
+ // If this fails, we still want to complete the operation. Probably this
+ // blobURL has been revoked in the meantime.
+ NS_GetBlobForBlobURI(this, getter_AddRefs(mBlobImpl));
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::SetScheme(const nsACString& aScheme)
+{
+ // Disallow setting the scheme, since that could cause us to be associated
+ // with a different protocol handler that doesn't expect us to be carrying
+ // around a principal with nsIURIWithPrincipal.
+ return NS_ERROR_FAILURE;
+}
+
+// nsIURI methods:
+nsresult
+nsHostObjectURI::CloneInternal(mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI** aClone)
+{
+ nsCOMPtr<nsIURI> simpleClone;
+ nsresult rv =
+ mozilla::net::nsSimpleURI::CloneInternal(aRefHandlingMode, newRef, getter_AddRefs(simpleClone));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DEBUG
+ RefPtr<nsHostObjectURI> uriCheck;
+ rv = simpleClone->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(uriCheck));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && uriCheck);
+#endif
+
+ nsHostObjectURI* u = static_cast<nsHostObjectURI*>(simpleClone.get());
+
+ u->mPrincipal = mPrincipal;
+ u->mBlobImpl = mBlobImpl;
+
+ simpleClone.forget(aClone);
+ return NS_OK;
+}
+
+/* virtual */ nsresult
+nsHostObjectURI::EqualsInternal(nsIURI* aOther,
+ mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode,
+ bool* aResult)
+{
+ if (!aOther) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ RefPtr<nsHostObjectURI> otherUri;
+ aOther->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(otherUri));
+ if (!otherUri) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // Compare the member data that our base class knows about.
+ if (!mozilla::net::nsSimpleURI::EqualsInternal(otherUri, aRefHandlingMode)) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // Compare the piece of additional member data that we add to base class,
+ // but we cannot compare BlobImpl. This should not be a problem, because we
+ // don't support changing the underlying mBlobImpl.
+
+ if (mPrincipal && otherUri->mPrincipal) {
+ // Both of us have mPrincipals. Compare them.
+ return mPrincipal->Equals(otherUri->mPrincipal, aResult);
+ }
+ // else, at least one of us lacks a principal; only equal if *both* lack it.
+ *aResult = (!mPrincipal && !otherUri->mPrincipal);
+ return NS_OK;
+}
+
+// nsIClassInfo methods:
+NS_IMETHODIMP
+nsHostObjectURI::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ *count = 0;
+ *array = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::GetContractID(char * *aContractID)
+{
+ // Make sure to modify any subclasses as needed if this ever
+ // changes.
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::GetClassID(nsCID * *aClassID)
+{
+ // Make sure to modify any subclasses as needed if this ever
+ // changes to not call the virtual GetClassIDNoAlloc.
+ *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
+ NS_ENSURE_TRUE(*aClassID, NS_ERROR_OUT_OF_MEMORY);
+
+ return GetClassIDNoAlloc(*aClassID);
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::GetFlags(uint32_t *aFlags)
+{
+ *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHostObjectURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kHOSTOBJECTURICID;
+ return NS_OK;
+}
+
+void
+nsHostObjectURI::ForgetBlobImpl()
+{
+ MOZ_ASSERT(mBlobImpl);
+ mBlobImpl = nullptr;
+}
diff --git a/dom/base/nsHostObjectURI.h b/dom/base/nsHostObjectURI.h
new file mode 100644
index 000000000..8c1c9eadb
--- /dev/null
+++ b/dom/base/nsHostObjectURI.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 nsHostObjectURI_h
+#define nsHostObjectURI_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/File.h"
+#include "nsCOMPtr.h"
+#include "nsIClassInfo.h"
+#include "nsIPrincipal.h"
+#include "nsISerializable.h"
+#include "nsIURIWithBlobImpl.h"
+#include "nsIURIWithPrincipal.h"
+#include "nsSimpleURI.h"
+#include "nsIIPCSerializableURI.h"
+#include "nsWeakReference.h"
+
+
+/**
+ * These URIs refer to host objects: Blobs, with scheme "blob",
+ * MediaStreams, with scheme "mediastream", and MediaSources, with scheme
+ * "mediasource".
+ */
+class nsHostObjectURI : public mozilla::net::nsSimpleURI
+ , public nsIURIWithPrincipal
+ , public nsIURIWithBlobImpl
+ , public nsSupportsWeakReference
+{
+public:
+ nsHostObjectURI(nsIPrincipal* aPrincipal,
+ mozilla::dom::BlobImpl* aBlobImpl)
+ : mozilla::net::nsSimpleURI()
+ , mPrincipal(aPrincipal)
+ , mBlobImpl(aBlobImpl)
+ {}
+
+ // For use only from deserialization
+ nsHostObjectURI() : mozilla::net::nsSimpleURI() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIURIWITHBLOBIMPL
+ NS_DECL_NSIURIWITHPRINCIPAL
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIIPCSERIALIZABLEURI
+
+ NS_IMETHOD SetScheme(const nsACString &aProtocol) override;
+
+ // Override CloneInternal() and EqualsInternal()
+ virtual nsresult CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI** aClone) override;
+ virtual nsresult EqualsInternal(nsIURI* aOther,
+ RefHandlingEnum aRefHandlingMode,
+ bool* aResult) override;
+
+ // Override StartClone to hand back a nsHostObjectURI
+ virtual mozilla::net::nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) override
+ {
+ nsHostObjectURI* url = new nsHostObjectURI();
+ SetRefOnClone(url, refHandlingMode, newRef);
+ return url;
+ }
+
+ void ForgetBlobImpl();
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ RefPtr<mozilla::dom::BlobImpl> mBlobImpl;
+
+protected:
+ virtual ~nsHostObjectURI() {}
+};
+
+#define NS_HOSTOBJECTURI_CID \
+{ 0xf5475c51, 0x59a7, 0x4757, \
+ { 0xb3, 0xd9, 0xe2, 0x11, 0xa9, 0x41, 0x08, 0x72 } }
+
+#endif /* nsHostObjectURI_h */
diff --git a/dom/base/nsIAnimationObserver.h b/dom/base/nsIAnimationObserver.h
new file mode 100644
index 000000000..e39616b11
--- /dev/null
+++ b/dom/base/nsIAnimationObserver.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 nsIAnimationObserver_h___
+#define nsIAnimationObserver_h___
+
+#include "nsIMutationObserver.h"
+
+namespace mozilla {
+namespace dom {
+class Animation;
+} // namespace dom
+} // namespace mozilla
+
+#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/nsIAttribute.h b/dom/base/nsIAttribute.h
new file mode 100644
index 000000000..ee18d54f5
--- /dev/null
+++ b/dom/base/nsIAttribute.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 nsIAttribute_h___
+#define nsIAttribute_h___
+
+#include "nsINode.h"
+
+class nsDOMAttributeMap;
+
+#define NS_IATTRIBUTE_IID \
+{ 0x84d43da7, 0xb45d, 0x47ae, \
+ { 0x8f, 0xbf, 0x95, 0x26, 0x78, 0x4d, 0x5e, 0x47 } }
+
+class nsIAttribute : public nsINode
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IATTRIBUTE_IID)
+
+ virtual void SetMap(nsDOMAttributeMap *aMap) = 0;
+
+ nsDOMAttributeMap *GetMap()
+ {
+ return mAttrMap;
+ }
+
+ mozilla::dom::NodeInfo *NodeInfo() const
+ {
+ return mNodeInfo;
+ }
+
+ /**
+ * Called when our ownerElement is moved into a new document.
+ * Updates the nodeinfo of this node.
+ */
+ virtual nsresult SetOwnerDocument(nsIDocument* aDocument) = 0;
+
+protected:
+#ifdef MOZILLA_INTERNAL_API
+ nsIAttribute(nsDOMAttributeMap *aAttrMap,
+ already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
+#endif //MOZILLA_INTERNAL_API
+ virtual ~nsIAttribute();
+
+ RefPtr<nsDOMAttributeMap> mAttrMap;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIAttribute, NS_IATTRIBUTE_IID)
+
+#endif /* nsIAttribute_h___ */
diff --git a/dom/base/nsIContent.h b/dom/base/nsIContent.h
new file mode 100644
index 000000000..f05c47a61
--- /dev/null
+++ b/dom/base/nsIContent.h
@@ -0,0 +1,1067 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Attributes.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "nsCaseTreatment.h" // for enum, cannot be forward-declared
+#include "nsINode.h"
+
+// Forward declarations
+class nsAString;
+class nsIAtom;
+class nsIURI;
+class nsRuleWalker;
+class nsAttrValue;
+class nsAttrName;
+class nsTextFragment;
+class nsIFrame;
+class nsXBLBinding;
+
+namespace mozilla {
+class EventChainPreVisitor;
+namespace dom {
+class ShadowRoot;
+struct CustomElementData;
+} // namespace dom
+namespace widget {
+struct IMEState;
+} // namespace widget
+} // namespace mozilla
+
+enum nsLinkState {
+ eLinkState_Unvisited = 1,
+ eLinkState_Visited = 2,
+ eLinkState_NotLink = 3
+};
+
+// IID for the nsIContent interface
+#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:
+ typedef mozilla::widget::IMEState IMEState;
+
+#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(aNodeInfo)
+ {
+ MOZ_ASSERT(mNodeInfo);
+ SetNodeIsContent();
+ }
+#endif // MOZILLA_INTERNAL_API
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENT_IID)
+
+ /**
+ * 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.
+ * @param aDocument The new document for the content node. May not be null
+ * if aParent is null. Must match the current document of
+ * aParent, if aParent is not null (note that
+ * aParent->GetUncomposedDoc() can be null, in which case
+ * this must also be null).
+ * @param aParent The new parent for the content node. May be null if the
+ * node is being bound as a direct child of the document.
+ * @param aBindingParent The new binding parent for the content node.
+ * This is must either be non-null if a particular
+ * binding parent is desired or match aParent's binding
+ * parent.
+ * @param aCompileEventHandlers whether to initialize the event handlers in
+ * the document (used by nsXULElement)
+ * @note either aDocument or aParent must be non-null. If both are null,
+ * this method _will_ crash.
+ * @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
+ */
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) = 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 aDeep = true,
+ 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. XBL flattened tree children of this node
+ * 3. native anonymous nodes
+ * 4. :after generated node
+ */
+ eAllChildren = 0,
+
+ /**
+ * All XBL explicit children of the node (see
+ * http://www.w3.org/TR/xbl/#explicit3 ), as well as :before and :after
+ * anonymous content and native anonymous children.
+ *
+ * @note the result children order is
+ * 1. :before generated node
+ * 2. XBL explicit children of the node
+ * 3. native anonymous nodes
+ * 4. :after generated node
+ */
+ eAllButXBL = 1,
+
+ /**
+ * Skip native anonymous content created for placeholder of HTML input,
+ * used in conjunction with eAllChildren or eAllButXBL.
+ */
+ eSkipPlaceholderContent = 2
+ };
+
+ /**
+ * Return either the XBL explicit children of the node or the XBL flattened
+ * tree children of the node, depending on the filter, as well as
+ * native anonymous children.
+ *
+ * @note calling this method with eAllButXBL will return children that are
+ * also in the eAllButXBL and eAllChildren child lists of other descendants
+ * of this node in the tree, but those other nodes cannot be reached from the
+ * eAllButXBL child list.
+ */
+ virtual already_AddRefed<nsINodeList> GetChildren(uint32_t aFilter) = 0;
+
+ /**
+ * Get whether this content is C++-generated anonymous content
+ * @see nsIAnonymousContentCreator
+ * @return whether this content is anonymous
+ */
+ bool IsRootOfNativeAnonymousSubtree() const
+ {
+ NS_ASSERTION(!HasFlag(NODE_IS_NATIVE_ANONYMOUS_ROOT) ||
+ (HasFlag(NODE_IS_ANONYMOUS_ROOT) &&
+ HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE)),
+ "Some flags seem to be missing!");
+ return HasFlag(NODE_IS_NATIVE_ANONYMOUS_ROOT);
+ }
+
+ bool IsRootOfChromeAccessOnlySubtree() const
+ {
+ return HasFlag(NODE_IS_NATIVE_ANONYMOUS_ROOT |
+ NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS);
+ }
+
+ /**
+ * Makes this content anonymous
+ * @see nsIAnonymousContentCreator
+ */
+ void SetIsNativeAnonymousRoot()
+ {
+ SetFlags(NODE_IS_ANONYMOUS_ROOT | 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.
+ */
+ virtual nsIContent* FindFirstNonChromeOnlyAccessContent() const;
+
+ /**
+ * Returns true if and only if this node has a parent, but is not in
+ * its parent's child list.
+ */
+ bool IsRootOfAnonymousSubtree() const
+ {
+ NS_ASSERTION(!IsRootOfNativeAnonymousSubtree() ||
+ (GetParent() && GetBindingParent() == GetParent()),
+ "root of native anonymous subtree must have parent equal "
+ "to binding parent");
+ NS_ASSERTION(!GetParent() ||
+ ((GetBindingParent() == GetParent()) ==
+ HasFlag(NODE_IS_ANONYMOUS_ROOT)) ||
+ // Unfortunately default content for XBL insertion points is
+ // anonymous content that is bound with the parent of the
+ // insertion point as the parent but the bound element for the
+ // binding as the binding parent. So we have to complicate
+ // the assert a bit here.
+ (GetBindingParent() &&
+ (GetBindingParent() == GetParent()->GetBindingParent()) ==
+ HasFlag(NODE_IS_ANONYMOUS_ROOT)),
+ "For nodes with parent, flag and GetBindingParent() check "
+ "should match");
+ return HasFlag(NODE_IS_ANONYMOUS_ROOT);
+ }
+
+ /**
+ * Returns true if there is NOT a path through child lists
+ * from the top of this node's parent chain back to this node or
+ * if the node is in native anonymous subtree without a parent.
+ */
+ bool IsInAnonymousSubtree() const
+ {
+ NS_ASSERTION(!IsInNativeAnonymousSubtree() || GetBindingParent() ||
+ (!IsInUncomposedDoc() &&
+ static_cast<nsIContent*>(SubtreeRoot())->IsInNativeAnonymousSubtree()),
+ "Must have binding parent when in native anonymous subtree which is in document.\n"
+ "Native anonymous subtree which is not in document must have native anonymous root.");
+ return IsInNativeAnonymousSubtree() || (!IsInShadowTree() && GetBindingParent() != nullptr);
+ }
+
+ /**
+ * 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
+ */
+ virtual 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(nsIAtom* 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(nsIAtom* 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(nsIAtom* 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(nsIAtom* 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...);
+ }
+ inline bool IsActiveChildrenElement() const
+ {
+ return mNodeInfo->Equals(nsGkAtoms::children, kNameSpaceID_XBL) &&
+ GetBindingParent();
+ }
+
+ bool IsGeneratedContentContainerForBefore() const
+ {
+ return IsRootOfNativeAnonymousSubtree() &&
+ mNodeInfo->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore;
+ }
+
+ bool IsGeneratedContentContainerForAfter() const
+ {
+ return IsRootOfNativeAnonymousSubtree() &&
+ mNodeInfo->NameAtom() == nsGkAtoms::mozgeneratedcontentafter;
+ }
+
+ /**
+ * 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, nsIAtom* aName,
+ const nsAString& aValue, bool aNotify)
+ {
+ return SetAttr(aNameSpaceID, aName, nullptr, aValue, 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 aNotify specifies how whether or not the document should be
+ * notified of the attribute change.
+ */
+ virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsIAtom* aPrefix, const nsAString& aValue,
+ bool aNotify) = 0;
+
+ /**
+ * 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
+ * @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.
+ */
+ bool GetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAString& aResult) const;
+
+ /**
+ * Determine if an attribute has been set (empty string or otherwise).
+ *
+ * @param aNameSpaceId the namespace id of the attribute
+ * @param aAttr the attribute name
+ * @return whether an attribute exists
+ */
+ bool HasAttr(int32_t aNameSpaceID, nsIAtom* aName) const;
+
+ /**
+ * Test whether this content node'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.
+ */
+ bool AttrValueIs(int32_t aNameSpaceID,
+ nsIAtom* aName,
+ const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const;
+
+ /**
+ * Test whether this content node'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,
+ nsIAtom* aName,
+ nsIAtom* aValue,
+ nsCaseTreatment aCaseSensitive) const;
+
+ enum {
+ ATTR_MISSING = -1,
+ ATTR_VALUE_NO_MATCH = -2
+ };
+ /**
+ * Check whether this content node'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
+ */
+ typedef nsIAtom* const* const AttrValuesArray;
+ virtual int32_t FindAttrValueIn(int32_t aNameSpaceID,
+ nsIAtom* aName,
+ AttrValuesArray* aValues,
+ nsCaseTreatment aCaseSensitive) const
+ {
+ return ATTR_MISSING;
+ }
+
+ /**
+ * Remove an attribute so that it is no longer explicitly specified.
+ *
+ * @param aNameSpaceID the namespace id of the attribute
+ * @param aAttr the name of the attribute to unset
+ * @param aNotify specifies whether or not the document should be
+ * notified of the attribute change
+ */
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
+ bool aNotify) = 0;
+
+
+ /**
+ * 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.
+ */
+ virtual const nsAttrName* GetAttrNameAt(uint32_t aIndex) const = 0;
+
+ /**
+ * Gets the attribute info (name and value) for this content at a given index.
+ */
+ virtual mozilla::dom::BorrowedAttrInfo GetAttrInfoAt(uint32_t aIndex) const = 0;
+
+ /**
+ * Get the number of all specified attributes.
+ *
+ * @return the number of attributes
+ */
+ virtual uint32_t GetAttrCount() const = 0;
+
+ /**
+ * 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 overridden by subclasses as needed
+ * @param aName the event name to look up
+ */
+ virtual bool IsEventAttributeName(nsIAtom* aName)
+ {
+ return false;
+ }
+
+ /**
+ * Set the text to the given value. If aNotify is true then
+ * the document is notified of the content change.
+ * NOTE: For elements this always ASSERTS and returns NS_ERROR_FAILURE
+ */
+ virtual nsresult SetText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify) = 0;
+
+ /**
+ * Append the given value to the current text. If aNotify is true then
+ * the document is notified of the content change.
+ * NOTE: For elements this always ASSERTS and returns NS_ERROR_FAILURE
+ */
+ virtual nsresult AppendText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify) = 0;
+
+ /**
+ * Set the text to the given value. If aNotify is true then
+ * the document is notified of the content change.
+ * NOTE: For elements this always asserts and returns NS_ERROR_FAILURE
+ */
+ nsresult SetText(const nsAString& aStr, bool aNotify)
+ {
+ return SetText(aStr.BeginReading(), aStr.Length(), aNotify);
+ }
+
+ /**
+ * Query method to see if the frame is nothing but whitespace
+ * NOTE: Always returns false for elements
+ */
+ virtual bool TextIsOnlyWhitespace() = 0;
+
+ /**
+ * 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.
+ * NOTE: Always returns false for elements.
+ */
+ virtual bool HasTextForTranslation() = 0;
+
+ /**
+ * Append the text content to aResult.
+ * NOTE: This asserts and returns for elements
+ */
+ virtual void AppendTextTo(nsAString& aResult) = 0;
+
+ /**
+ * Append the text content to aResult.
+ * NOTE: This asserts and returns for elements
+ */
+ MOZ_MUST_USE
+ virtual bool AppendTextTo(nsAString& aResult, const mozilla::fallible_t&) = 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);
+
+ /**
+ * 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 true if the focus was changed.
+ */
+ virtual bool PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent)
+ {
+ return false;
+ }
+
+ /*
+ * 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 DISABLED, you should not set the OPEN and CLOSE
+ * value.
+ * PASSWORD should be returned only from password editor, this value
+ * has a special meaning. It is used as alternative of DISABLED.
+ * 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 content node with the binding (or native code, possibly on the
+ * frame) responsible for our construction (and existence). Used by
+ * anonymous content (both XBL-generated and native-anonymous).
+ *
+ * null for all explicit content (i.e., content reachable from the top
+ * of its GetParent() chain via child lists).
+ *
+ * @return the binding parent
+ */
+ virtual nsIContent *GetBindingParent() const = 0;
+
+ /**
+ * Gets the current XBL binding that is bound to this element.
+ *
+ * @return the current binding.
+ */
+ virtual nsXBLBinding *GetXBLBinding() const = 0;
+
+ /**
+ * Sets or unsets an XBL binding for this element. Setting a
+ * binding on an element that already has a binding will remove the
+ * old binding.
+ *
+ * @param aBinding The binding to bind to this content. If nullptr is
+ * provided as the argument, then existing binding will be
+ * removed.
+ *
+ * @param aOldBindingManager The old binding manager that contains
+ * this content if this content was adopted
+ * to another document.
+ */
+ virtual void SetXBLBinding(nsXBLBinding* aBinding,
+ nsBindingManager* aOldBindingManager = nullptr) = 0;
+
+ /**
+ * 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.
+ */
+ virtual void SetShadowRoot(mozilla::dom::ShadowRoot* aShadowRoot) = 0;
+
+ /**
+ * 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.
+ * This method is called |GetContainingShadow| instead of |GetRootShadowRoot|
+ * to avoid confusion with |GetShadowRoot|.
+ *
+ * @return The ShadowRoot that is the root of the node tree.
+ */
+ virtual mozilla::dom::ShadowRoot *GetContainingShadow() const = 0;
+
+ /**
+ * Gets an array of destination insertion points where this content
+ * is distributed by web component distribution algorithms.
+ * The array is created if it does not already exist.
+ */
+ virtual nsTArray<nsIContent*> &DestInsertionPoints() = 0;
+
+ /**
+ * Same as DestInsertionPoints except that this method will return
+ * null if the array of destination insertion points does not already
+ * exist.
+ */
+ virtual nsTArray<nsIContent*> *GetExistingDestInsertionPoints() const = 0;
+
+ /**
+ * Gets the insertion parent element of the XBL binding.
+ * The insertion parent is our one true parent in the transformed DOM.
+ *
+ * @return the insertion parent element.
+ */
+ virtual nsIContent *GetXBLInsertionParent() const = 0;
+
+ /**
+ * Sets the insertion parent element of the XBL binding.
+ *
+ * @param aContent The insertion parent element.
+ */
+ virtual void SetXBLInsertionParent(nsIContent* aContent) = 0;
+
+ /**
+ * Same as GetFlattenedTreeParentNode, but returns null if the parent is
+ * non-nsIContent.
+ */
+ inline nsIContent *GetFlattenedTreeParent() const;
+
+ /**
+ * Helper method, which we leave public so that it's accessible from nsINode.
+ */
+ nsINode *GetFlattenedTreeParentNodeInternal() const;
+
+ /**
+ * 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.
+ */
+ virtual mozilla::dom::CustomElementData *GetCustomElementData() const = 0;
+
+ /**
+ * Sets the custom element data, ownership of the
+ * callback data is taken by this content.
+ *
+ * @param aCallbackData The custom element data.
+ */
+ virtual void SetCustomElementData(mozilla::dom::CustomElementData* aData) = 0;
+
+ /**
+ * API to check if this is a link that's traversed in response to user input
+ * (e.g. a click event). Specializations for HTML/SVG/generic XML allow for
+ * different types of link in different types of content.
+ *
+ * @param aURI Required out param. If this content is a link, a new nsIURI
+ * set to this link's URI will be passed out.
+ *
+ * @note The out param, aURI, is guaranteed to be set to a non-null pointer
+ * when the return value is true.
+ *
+ * XXXjwatt: IMO IsInteractiveLink would be a better name.
+ */
+ virtual bool IsLink(nsIURI** aURI) const = 0;
+
+ /**
+ * 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.
+ */
+ virtual already_AddRefed<nsIURI> GetHrefURI() const
+ {
+ return nullptr;
+ }
+
+ /**
+ * 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 currently only called for input and button, in the HTML
+ * content sink. If you want to call it on your element, modify the content
+ * sink of your choice to do so. 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 begins creating the element's
+ * children, if any are present.
+ *
+ * This is only called for XTF elements currently.
+ */
+ virtual void BeginAddingChildren()
+ {
+ }
+
+ /**
+ * This method is called when the parser finishes creating the element's children,
+ * if any are present.
+ *
+ * NOTE: this is currently only called for textarea, select, applet, and
+ * object elements in the HTML content sink. If you want
+ * to call it on your element, modify the content sink of your
+ * choice to do so. 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, applet, 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, applet, or object
+ * element and not all children have been added.
+ * @returns true otherwise.
+ */
+ virtual bool IsDoneAddingChildren()
+ {
+ return true;
+ }
+
+ /**
+ * 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.
+ */
+ nsIAtom* GetID() const {
+ if (HasID()) {
+ return DoGetID();
+ }
+ return nullptr;
+ }
+
+ /**
+ * Get the class list of this content node (this corresponds to the
+ * value of the class attribute). This may be null if there are no
+ * classes, but that's not guaranteed.
+ */
+ const nsAttrValue* GetClasses() const {
+ if (HasFlag(NODE_MAY_HAVE_CLASS)) {
+ return DoGetClasses();
+ }
+ return nullptr;
+ }
+
+ /**
+ * Walk aRuleWalker over the content style rules (presentational
+ * hint rules) for this content node.
+ */
+ NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) = 0;
+
+ /**
+ * 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;
+ }
+ void SetPrimaryFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(IsInUncomposedDoc() || IsInShadowTree(), "This will end badly!");
+ NS_PRECONDITION(!aFrame || !mPrimaryFrame || aFrame == mPrimaryFrame,
+ "Losing track of existing primary frame");
+ mPrimaryFrame = 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();
+
+ /**
+ * 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();
+
+
+ /**
+ * Set NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO all the way up the flattened
+ * parent chain to the document. If an ancestor is found with the bit already
+ * set, this method asserts that all of its ancestors also have the bit set.
+ */
+ void MarkAncestorsAsHavingDirtyDescendantsForServo();
+
+ /**
+ * 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 false if no language was specified.
+ */
+ bool GetLang(nsAString& aResult) const {
+ for (const nsIContent* content = this; content; content = content->GetParent()) {
+ if (content->GetAttrCount() > 0) {
+ // xml:lang has precedence over lang on HTML elements (see
+ // XHTML1 section C.7).
+ bool hasAttr = content->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang,
+ aResult);
+ if (!hasAttr && (content->IsHTMLElement() || content->IsSVGElement() ||
+ content->IsXULElement())) {
+ hasAttr = content->GetAttr(kNameSpaceID_None, nsGkAtoms::lang,
+ aResult);
+ }
+ NS_ASSERTION(hasAttr || aResult.IsEmpty(),
+ "GetAttr that returns false should not make string non-empty");
+ if (hasAttr) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Overloaded from nsINode
+ virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
+
+ virtual nsresult PreHandleEvent(
+ mozilla::EventChainPreVisitor& aVisitor) override;
+
+ virtual bool IsPurple() = 0;
+ virtual void RemovePurple() = 0;
+
+ virtual bool OwnedOnlyByTheDOMTree() { return false; }
+protected:
+ /**
+ * Hook for implementing GetID. This is guaranteed to only be
+ * called if HasID() is true.
+ */
+ nsIAtom* DoGetID() const;
+
+private:
+ /**
+ * Hook for implementing GetClasses. This is guaranteed to only be
+ * called if the NODE_MAY_HAVE_CLASS flag is set.
+ */
+ const nsAttrValue* DoGetClasses() const;
+
+public:
+#ifdef DEBUG
+ /**
+ * 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
+
+ /**
+ * Append to aOutDescription a short (preferably one line) string
+ * describing the content.
+ * Currently implemented for elements only.
+ */
+ virtual void Describe(nsAString& aOutDescription) const {
+ aOutDescription = NS_LITERAL_STRING("(not an element)");
+ }
+
+ 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)
+
+inline nsIContent* nsINode::AsContent()
+{
+ MOZ_ASSERT(IsContent());
+ return static_cast<nsIContent*>(this);
+}
+
+#define NS_IMPL_FROMCONTENT_HELPER(_class, _check) \
+ static _class* FromContent(nsIContent* aContent) \
+ { \
+ return aContent->_check ? static_cast<_class*>(aContent) : nullptr; \
+ } \
+ static _class* FromContentOrNull(nsIContent* aContent) \
+ { \
+ return aContent ? FromContent(aContent) : nullptr; \
+ }
+
+#define NS_IMPL_FROMCONTENT(_class, _nsid) \
+ NS_IMPL_FROMCONTENT_HELPER(_class, IsInNamespace(_nsid))
+
+#define NS_IMPL_FROMCONTENT_WITH_TAG(_class, _nsid, _tag) \
+ NS_IMPL_FROMCONTENT_HELPER(_class, NodeInfo()->Equals(nsGkAtoms::_tag, _nsid))
+
+#define NS_IMPL_FROMCONTENT_HTML_WITH_TAG(_class, _tag) \
+ NS_IMPL_FROMCONTENT_WITH_TAG(_class, kNameSpaceID_XHTML, _tag)
+
+#endif /* nsIContent_h___ */
diff --git a/dom/base/nsIContentInlines.h b/dom/base/nsIContentInlines.h
new file mode 100644
index 000000000..368a0422b
--- /dev/null
+++ b/dom/base/nsIContentInlines.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/. */
+
+#ifndef nsIContentInlines_h
+#define nsIContentInlines_h
+
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Element.h"
+
+inline bool
+nsIContent::IsInHTMLDocument() const
+{
+ return OwnerDoc()->IsHTMLDocument();
+}
+
+inline bool
+nsIContent::IsInChromeDocument() const
+{
+ return nsContentUtils::IsChromeDoc(OwnerDoc());
+}
+
+inline mozilla::dom::ShadowRoot* nsIContent::GetShadowRoot() const
+{
+ if (!IsElement()) {
+ return nullptr;
+ }
+
+ return AsElement()->FastGetShadowRoot();
+}
+
+inline nsINode* nsINode::GetFlattenedTreeParentNode() const
+{
+ nsINode* parent = GetParentNode();
+
+ // Try to short-circuit past the complicated and not-exactly-fast logic for
+ // computing the flattened parent.
+ //
+ // There are three cases where we need might something other than parentNode:
+ // (1) The node is an explicit child of an XBL-bound element, re-bound
+ // to an XBL insertion point.
+ // (2) The node is a top-level element in a shadow tree, whose flattened
+ // parent is the host element (as opposed to the actual parent which
+ // is the shadow root).
+ // (3) The node is an explicit child of an element with a shadow root,
+ // re-bound to an insertion point.
+ bool needSlowCall = HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) ||
+ IsInShadowTree() ||
+ (parent && parent->IsContent() &&
+ parent->AsContent()->GetShadowRoot());
+ if (MOZ_UNLIKELY(needSlowCall)) {
+ MOZ_ASSERT(IsContent());
+ return AsContent()->GetFlattenedTreeParentNodeInternal();
+ }
+
+ return parent;
+}
+
+inline nsIContent*
+nsIContent::GetFlattenedTreeParent() const
+{
+ nsINode* parent = GetFlattenedTreeParentNode();
+ return (parent && parent->IsContent()) ? parent->AsContent() : nullptr;
+}
+
+
+#endif // nsIContentInlines_h
diff --git a/dom/base/nsIContentIterator.h b/dom/base/nsIContentIterator.h
new file mode 100644
index 000000000..0c41ad485
--- /dev/null
+++ b/dom/base/nsIContentIterator.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 __nsIContentIterator_h___
+#define __nsIContentIterator_h___
+
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+
+class nsINode;
+class nsIDOMRange;
+
+#define NS_ICONTENTITERATOR_IID \
+{ 0x2550078e, 0xae87, 0x4914, \
+ { 0xb3, 0x04, 0xe4, 0xd1, 0x46, 0x19, 0x3d, 0x5f } }
+
+class nsIContentIterator : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENTITERATOR_IID)
+
+ /* Initializes an iterator for the subtree rooted by the node aRoot
+ */
+ virtual nsresult Init(nsINode* aRoot) = 0;
+
+ /* Initializes an iterator for the subtree defined by the range aRange
+ Subclasses should make sure they implement both of these!
+ */
+ virtual nsresult Init(nsIDOMRange* aRange) = 0;
+
+ /** First will reset the list.
+ */
+ virtual void First() = 0;
+
+ /** Last will reset the list to the end.
+ */
+ virtual void Last() = 0;
+
+ /** Next will advance the list.
+ */
+ virtual void Next() = 0;
+
+ /** Prev will decrement the list.
+ */
+ virtual void Prev() = 0;
+
+ /** CurrentItem will return the current item, or null if the list is empty
+ * @return the current node
+ */
+ virtual nsINode *GetCurrentNode() = 0;
+
+ /** return if the collection is at the end. that is the beginning following a call to Prev
+ * and it is the end of the list following a call to next
+ * @return if the iterator is done.
+ */
+ virtual bool IsDone() = 0;
+
+ /** PositionAt will position the iterator to the supplied node
+ */
+ virtual nsresult PositionAt(nsINode* aCurNode) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIContentIterator, NS_ICONTENTITERATOR_IID)
+
+already_AddRefed<nsIContentIterator> NS_NewContentIterator();
+already_AddRefed<nsIContentIterator> NS_NewPreContentIterator();
+already_AddRefed<nsIContentIterator> NS_NewContentSubtreeIterator();
+
+#endif // __nsIContentIterator_h___
diff --git a/dom/base/nsIContentPolicy.idl b/dom/base/nsIContentPolicy.idl
new file mode 100644
index 000000000..a73565a9a
--- /dev/null
+++ b/dom/base/nsIContentPolicy.idl
@@ -0,0 +1,131 @@
+/* -*- 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"
+#include "nsIContentPolicyBase.idl"
+
+interface nsIURI;
+interface nsIDOMNode;
+interface nsIPrincipal;
+
+/**
+ * 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 : nsIContentPolicyBase
+{
+ /**
+ * 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 aContentType the type of content being tested. This will be one
+ * one of the TYPE_* constants.
+ *
+ * @param aContentLocation the location of the content being checked; must
+ * not be null
+ *
+ * @param aRequestOrigin OPTIONAL. the location of the resource that
+ * initiated this load request; can be null if
+ * inapplicable
+ *
+ * @param aContext OPTIONAL. the nsIDOMNode or nsIDOMWindow that
+ * initiated the request, or something that can QI
+ * to one of those; can be null if inapplicable.
+ * Note that for navigation events (new windows and
+ * link clicks), this is the NEW window.
+ *
+ * @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
+ *
+ * @param aExtra an OPTIONAL argument, pass-through for non-Gecko
+ * callers to pass extra data to callees.
+ *
+ * @param aRequestPrincipal an OPTIONAL argument, defines the principal that
+ * caused the load. This is optional only for
+ * non-gecko code: all gecko code should set this
+ * argument. For navigation events, this is
+ * the principal of the page that caused this load.
+ *
+ * @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 nsContentPolicyType aContentType,
+ in nsIURI aContentLocation,
+ in nsIURI aRequestOrigin,
+ in nsISupports aContext,
+ in ACString aMimeTypeGuess,
+ in nsISupports aExtra,
+ [optional] in nsIPrincipal aRequestPrincipal);
+
+ /**
+ * 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 aContentType the type of content being tested. This will be one
+ * one of the TYPE_* constants.
+ *
+ * @param aContentLocation OPTIONAL; the location of the resource being
+ * requested: MAY be, e.g., a post-redirection URI
+ * for the resource.
+ *
+ * @param aRequestOrigin OPTIONAL. the location of the resource that
+ * initiated this load request; can be null if
+ * inapplicable
+ *
+ * @param aContext OPTIONAL. the nsIDOMNode or nsIDOMWindow that
+ * initiated the request, or something that can QI
+ * to one of those; can be null if inapplicable.
+ *
+ * @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, e.g., TYPE_REFRESH).
+ *
+ * @param aExtra an OPTIONAL argument, pass-through for non-Gecko
+ * callers to pass extra data to callees.
+ *
+ * @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 nsContentPolicyType aContentType,
+ in nsIURI aContentLocation,
+ in nsIURI aRequestOrigin,
+ in nsISupports aContext,
+ in ACString aMimeType,
+ in nsISupports aExtra,
+ [optional] in nsIPrincipal aRequestPrincipal);
+};
diff --git a/dom/base/nsIContentPolicyBase.idl b/dom/base/nsIContentPolicyBase.idl
new file mode 100644
index 000000000..884e3d96d
--- /dev/null
+++ b/dom/base/nsIContentPolicyBase.idl
@@ -0,0 +1,381 @@
+/* -*- 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 nsIDOMNode;
+interface nsIPrincipal;
+
+/**
+ * The type of nsIContentPolicy::TYPE_*
+ */
+typedef unsigned long nsContentPolicyType;
+
+/**
+ * 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(17418187-d86f-48dd-92d1-238838df0a4e)]
+interface nsIContentPolicyBase : nsISupports
+{
+ /**
+ * Indicates a unset or bogus policy type.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType TYPE_OTHER = 1;
+
+ /**
+ * Indicates an executable script (such as JavaScript).
+ */
+ const nsContentPolicyType TYPE_SCRIPT = 2;
+
+ /**
+ * Indicates an image (e.g., IMG elements).
+ */
+ const nsContentPolicyType TYPE_IMAGE = 3;
+
+ /**
+ * Indicates a stylesheet (e.g., STYLE elements).
+ */
+ const nsContentPolicyType TYPE_STYLESHEET = 4;
+
+ /**
+ * Indicates a generic object (plugin-handled content typically falls under
+ * this category).
+ */
+ const nsContentPolicyType TYPE_OBJECT = 5;
+
+ /**
+ * Indicates a document at the top-level (i.e., in a browser).
+ */
+ const nsContentPolicyType TYPE_DOCUMENT = 6;
+
+ /**
+ * Indicates a document contained within another document (e.g., IFRAMEs,
+ * FRAMES, and OBJECTs).
+ */
+ const nsContentPolicyType TYPE_SUBDOCUMENT = 7;
+
+ /**
+ * Indicates a timed refresh.
+ *
+ * shouldLoad will never get this, because it does not represent content
+ * to be loaded (the actual load triggered by the refresh will go through
+ * shouldLoad as expected).
+ *
+ * shouldProcess will get this for, e.g., META Refresh elements and HTTP
+ * Refresh headers.
+ */
+ const nsContentPolicyType TYPE_REFRESH = 8;
+
+ /**
+ * Indicates an XBL binding request, triggered either by -moz-binding CSS
+ * property.
+ */
+ const nsContentPolicyType TYPE_XBL = 9;
+
+ /**
+ * Indicates a ping triggered by a click on <A PING="..."> element.
+ */
+ const nsContentPolicyType TYPE_PING = 10;
+
+ /**
+ * Indicates an XMLHttpRequest. Also used for document.load and for EventSource.
+ */
+ const nsContentPolicyType TYPE_XMLHTTPREQUEST = 11;
+ const nsContentPolicyType TYPE_DATAREQUEST = 11; // alias
+
+ /**
+ * Indicates a request by a plugin.
+ */
+ const nsContentPolicyType TYPE_OBJECT_SUBREQUEST = 12;
+
+ /**
+ * Indicates a DTD loaded by an XML document.
+ */
+ const nsContentPolicyType TYPE_DTD = 13;
+
+ /**
+ * Indicates a font loaded via @font-face rule.
+ */
+ const nsContentPolicyType TYPE_FONT = 14;
+
+ /**
+ * Indicates a video or audio load.
+ */
+ const nsContentPolicyType TYPE_MEDIA = 15;
+
+ /**
+ * Indicates a WebSocket load.
+ */
+ const nsContentPolicyType TYPE_WEBSOCKET = 16;
+
+ /**
+ * Indicates a Content Security Policy report.
+ */
+ const nsContentPolicyType TYPE_CSP_REPORT = 17;
+
+ /**
+ * Indicates a style sheet transformation.
+ */
+ const nsContentPolicyType TYPE_XSLT = 18;
+
+ /**
+ * Indicates a beacon post.
+ */
+ const nsContentPolicyType TYPE_BEACON = 19;
+
+ /**
+ * Indicates a load initiated by the fetch() function from the Fetch
+ * specification.
+ */
+ const nsContentPolicyType TYPE_FETCH = 20;
+
+ /**
+ * Indicates a <img srcset> or <picture> request.
+ */
+ const nsContentPolicyType TYPE_IMAGESET = 21;
+
+ /**
+ * Indicates a web manifest.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_SHARED_WORKER = 25;
+
+ /**
+ * Indicates an internal constant for content loaded from embed elements.
+ *
+ * This will be mapped to TYPE_OBJECT.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_EMBED = 26;
+
+ /**
+ * Indicates an internal constant for content loaded from object elements.
+ *
+ * This will be mapped to TYPE_OBJECT.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_OBJECT = 27;
+
+ /**
+ * Indicates an internal constant for content loaded from frame elements.
+ *
+ * This will be mapped to TYPE_SUBDOCUMENT.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_FRAME = 28;
+
+ /**
+ * Indicates an internal constant for content loaded from iframe elements.
+ *
+ * This will be mapped to TYPE_SUBDOCUMENT.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_IFRAME = 29;
+
+ /**
+ * Indicates an internal constant for content loaded from audio elements.
+ *
+ * This will be mapped to TYPE_MEDIA.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_AUDIO = 30;
+
+ /**
+ * Indicates an internal constant for content loaded from video elements.
+ *
+ * This will be mapped to TYPE_MEDIA.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_VIDEO = 31;
+
+ /**
+ * Indicates an internal constant for content loaded from track elements.
+ *
+ * This will be mapped to TYPE_MEDIA.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_TRACK = 32;
+
+ /**
+ * Indicates an internal constant for an XMLHttpRequest.
+ *
+ * This will be mapped to TYPE_XMLHTTPREQUEST.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_XMLHTTPREQUEST = 33;
+
+ /**
+ * Indicates an internal constant for EventSource.
+ *
+ * This will be mapped to TYPE_DATAREQUEST.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType 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.
+ */
+ const nsContentPolicyType TYPE_INTERNAL_IMAGE_FAVICON = 41;
+
+ /* When adding new content types, please update nsContentBlocker,
+ * NS_CP_ContentTypeName, nsCSPContext, all nsIContentPolicy
+ * implementations, the static_assert in dom/cache/DBSchema.cpp,
+ * 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 or process request
+ * is not rejected.
+ */
+ const short ACCEPT = 1;
+};
diff --git a/dom/base/nsIContentSerializer.h b/dom/base/nsIContentSerializer.h
new file mode 100644
index 000000000..f023cbc90
--- /dev/null
+++ b/dom/base/nsIContentSerializer.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 nsIContentSerializer_h
+#define nsIContentSerializer_h
+
+#include "nsISupports.h"
+
+class nsIContent;
+class nsIDocument;
+class nsAString;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+#define NS_ICONTENTSERIALIZER_IID \
+{ 0xb1ee32f2, 0xb8c4, 0x49b9, \
+ { 0x93, 0xdf, 0xb6, 0xfa, 0xb5, 0xd5, 0x46, 0x88 } }
+
+class nsIContentSerializer : public nsISupports {
+ public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENTSERIALIZER_IID)
+
+ NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
+ const char* aCharSet, bool aIsCopying,
+ bool aIsWholeDocument) = 0;
+
+ NS_IMETHOD AppendText(nsIContent* aText, int32_t aStartOffset,
+ int32_t aEndOffset, nsAString& aStr) = 0;
+
+ NS_IMETHOD AppendCDATASection(nsIContent* aCDATASection,
+ int32_t aStartOffset, int32_t aEndOffset,
+ nsAString& aStr) = 0;
+
+ NS_IMETHOD AppendProcessingInstruction(nsIContent* aPI,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr) = 0;
+
+ NS_IMETHOD AppendComment(nsIContent* aComment, int32_t aStartOffset,
+ int32_t aEndOffset, nsAString& aStr) = 0;
+
+ NS_IMETHOD AppendDoctype(nsIContent *aDoctype,
+ nsAString& aStr) = 0;
+
+ NS_IMETHOD AppendElementStart(mozilla::dom::Element* aElement,
+ mozilla::dom::Element* aOriginalElement,
+ nsAString& aStr) = 0;
+
+ NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement,
+ nsAString& aStr) = 0;
+
+ NS_IMETHOD Flush(nsAString& aStr) = 0;
+
+ /**
+ * Append any items in the beginning of the document that won't be
+ * serialized by other methods. XML declaration is the most likely
+ * thing this method can produce.
+ */
+ NS_IMETHOD AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIContentSerializer, NS_ICONTENTSERIALIZER_IID)
+
+#define NS_CONTENTSERIALIZER_CONTRACTID_PREFIX \
+"@mozilla.org/layout/contentserializer;1?mimetype="
+
+#endif /* nsIContentSerializer_h */
diff --git a/dom/base/nsIDOMBlob.idl b/dom/base/nsIDOMBlob.idl
new file mode 100644
index 000000000..152da4459
--- /dev/null
+++ b/dom/base/nsIDOMBlob.idl
@@ -0,0 +1,12 @@
+/* -*- 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"
+
+[scriptable, builtinclass, uuid(f344146a-ee1f-417e-8a68-6984ca56f0ae)]
+interface nsIDOMBlob : nsISupports
+{
+ // Just an empty interface.
+};
diff --git a/dom/base/nsIDOMClassInfo.h b/dom/base/nsIDOMClassInfo.h
new file mode 100644
index 000000000..90c65c124
--- /dev/null
+++ b/dom/base/nsIDOMClassInfo.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 nsIDOMClassInfo_h___
+#define nsIDOMClassInfo_h___
+
+#include "nsIXPCScriptable.h"
+
+#define DOM_BASE_SCRIPTABLE_FLAGS \
+ (nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY | \
+ nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY | \
+ nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \
+ nsIXPCScriptable::ALLOW_PROP_MODS_TO_PROTOTYPE | \
+ nsIXPCScriptable::DONT_ASK_INSTANCE_FOR_SCRIPTABLE | \
+ nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES)
+
+#define DEFAULT_SCRIPTABLE_FLAGS \
+ (DOM_BASE_SCRIPTABLE_FLAGS | \
+ nsIXPCScriptable::WANT_RESOLVE | \
+ nsIXPCScriptable::WANT_PRECREATE)
+
+#define DOM_DEFAULT_SCRIPTABLE_FLAGS \
+ (DEFAULT_SCRIPTABLE_FLAGS | \
+ nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \
+ nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY)
+
+#endif /* nsIDOMClassInfo_h___ */
diff --git a/dom/base/nsIDOMDOMCursor.idl b/dom/base/nsIDOMDOMCursor.idl
new file mode 100644
index 000000000..ea8deb905
--- /dev/null
+++ b/dom/base/nsIDOMDOMCursor.idl
@@ -0,0 +1,20 @@
+/* -*- 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"
+
+[scriptable, function, uuid(3a75d80a-9258-4ab8-95fd-ec0b5f634df1)]
+interface nsICursorContinueCallback : nsISupports
+{
+ void handleContinue();
+};
+
+[builtinclass, uuid(062ea35a-5158-425a-b7bc-3ae9daa84398)]
+interface nsIDOMDOMCursor : nsISupports
+{
+ readonly attribute boolean done;
+ void continue();
+};
diff --git a/dom/base/nsIDOMDOMRequest.idl b/dom/base/nsIDOMDOMRequest.idl
new file mode 100644
index 000000000..d316db7db
--- /dev/null
+++ b/dom/base/nsIDOMDOMRequest.idl
@@ -0,0 +1,45 @@
+/* -*- 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 "nsIDOMEventTarget.idl"
+
+interface mozIDOMWindow;
+interface nsIDOMDOMCursor;
+interface nsICursorContinueCallback;
+
+[builtinclass, uuid(e39da69e-2232-4e49-9856-b8a4a6210336)]
+interface nsIDOMDOMRequest : nsIDOMEventTarget
+{
+ readonly attribute DOMString readyState; // "pending" or "done"
+
+ readonly attribute jsval result;
+
+ // DOMError
+ readonly attribute nsISupports error;
+
+ [implicit_jscontext] attribute jsval onsuccess;
+ [implicit_jscontext] attribute jsval onerror;
+};
+
+[scriptable, builtinclass, uuid(9a57e5de-ce93-45fa-8145-755722834f7c)]
+interface nsIDOMRequestService : nsISupports
+{
+ nsIDOMDOMRequest createRequest(in mozIDOMWindow window);
+ /*
+ * @param aCallback
+ * Called when `continue()' is called in the cursor, should be used to
+ * notify the data provider that content wants the next result.
+ */
+ nsIDOMDOMCursor createCursor(in mozIDOMWindow window,
+ in nsICursorContinueCallback aCallback);
+
+ void fireSuccess(in nsIDOMDOMRequest request, in jsval result);
+ void fireError(in nsIDOMDOMRequest request, in DOMString error);
+ void fireDetailedError(in nsIDOMDOMRequest request, in nsISupports error);
+ void fireSuccessAsync(in nsIDOMDOMRequest request, in jsval result);
+ void fireErrorAsync(in nsIDOMDOMRequest request, in DOMString error);
+ void fireDone(in nsIDOMDOMCursor cursor);
+};
diff --git a/dom/base/nsIDOMDataChannel.idl b/dom/base/nsIDOMDataChannel.idl
new file mode 100644
index 000000000..5836b2186
--- /dev/null
+++ b/dom/base/nsIDOMDataChannel.idl
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMEventTarget.idl"
+
+%{C++
+#ifdef GetBinaryType
+// Windows apparently has a #define for GetBinaryType...
+#undef GetBinaryType
+#endif
+%}
+
+interface nsIVariant;
+
+[builtinclass, uuid(b00a4ca7-312e-4926-84f6-8ebb43e53d83)]
+interface nsIDOMDataChannel : nsIDOMEventTarget
+{
+ readonly attribute DOMString label;
+ readonly attribute DOMString protocol;
+ readonly attribute boolean reliable;
+ readonly attribute boolean ordered;
+
+ readonly attribute DOMString readyState;
+ readonly attribute unsigned long bufferedAmount;
+
+ readonly attribute unsigned short id;
+
+ [implicit_jscontext] attribute jsval onopen;
+ [implicit_jscontext] attribute jsval onerror;
+ [implicit_jscontext] attribute jsval onclose;
+ [implicit_jscontext] attribute jsval onmessage;
+
+ attribute DOMString binaryType;
+
+ void close();
+};
diff --git a/dom/base/nsIDOMFileList.idl b/dom/base/nsIDOMFileList.idl
new file mode 100644
index 000000000..8e6dcf52a
--- /dev/null
+++ b/dom/base/nsIDOMFileList.idl
@@ -0,0 +1,14 @@
+/* -*- 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"
+
+[builtinclass, uuid(57128a85-34de-42db-a252-84dd57724a59)]
+interface nsIDOMFileList : nsISupports
+{
+ readonly attribute unsigned long length;
+ // returns a DOM File object
+ nsISupports item(in unsigned long index);
+};
diff --git a/dom/base/nsIDOMFormData.idl b/dom/base/nsIDOMFormData.idl
new file mode 100644
index 000000000..fa1bcc8b7
--- /dev/null
+++ b/dom/base/nsIDOMFormData.idl
@@ -0,0 +1,22 @@
+/* -*- 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 nsIVariant;
+
+[uuid(256c9139-5a29-41e1-8698-f9f9aae7d6cf)]
+interface nsIDOMFormData : nsISupports
+{
+ void append(in DOMString name, in nsIVariant value);
+};
+
+%{ C++
+#define NS_FORMDATA_CID \
+{ 0x6b192618, 0x26f2, 0x426e, \
+ { 0xa7, 0xac, 0x1e, 0x13, 0xa6, 0xa4, 0x52, 0x2b } }
+
+#define NS_FORMDATA_CONTRACTID "@mozilla.org/files/formdata;1"
+%}
diff --git a/dom/base/nsIDOMParser.idl b/dom/base/nsIDOMParser.idl
new file mode 100644
index 000000000..370eaa27b
--- /dev/null
+++ b/dom/base/nsIDOMParser.idl
@@ -0,0 +1,102 @@
+/* -*- 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 nsIInputStream;
+interface nsIDOMDocument;
+interface nsIURI;
+interface nsIPrincipal;
+interface nsIGlobalObject;
+
+/**
+ * The nsIDOMParser interface is a non-SAX interface that can be used
+ * to parse a string or byte stream containing XML or HTML content
+ * to a DOM document. Parsing is always synchronous - a document is always
+ * returned from the parsing methods. This is as opposed to loading and
+ * parsing with the XMLHttpRequest interface, which can be used for
+ * asynchronous (callback-based) loading.
+ */
+[uuid(70b9600e-8622-4c93-9ad8-22c28058dc44)]
+interface nsIDOMParser : nsISupports
+{
+ /**
+ * The string passed in is parsed into a DOM document.
+ *
+ * @param str The UTF16 string to be parsed
+ * @param contentType The content type of the string (see parseFromStream)
+ * @returns The DOM document created as a result of parsing the
+ * string
+ */
+ nsIDOMDocument parseFromString(in wstring str, in string contentType);
+
+ /**
+ * The buffer is parsed into a DOM document.
+ * The charset is determined from the xml entity decl.
+ *
+ * @param buf The octet array data to be parsed
+ * @param bufLen Length (in bytes) of the data
+ * @param contentType The content type of the data (see parseFromStream)
+ * @returns The DOM document created as a result of parsing the
+ * string
+ */
+ nsIDOMDocument parseFromBuffer([const,array,size_is(bufLen)] in octet buf,
+ in uint32_t bufLen, in string contentType);
+
+ /**
+ * The byte stream passed in is parsed into a DOM document.
+ *
+ * Not accessible from web content.
+ *
+ * @param stream The byte stream whose contents are parsed
+ * @param charset The character set that was used to encode the byte
+ * stream. NULL if not specified.
+ * @param contentLength The number of bytes in the input stream.
+ * @param contentType The content type of the string - either text/xml,
+ * application/xml, or application/xhtml+xml.
+ * Must not be NULL.
+ * @returns The DOM document created as a result of parsing the
+ * stream
+ */
+ nsIDOMDocument parseFromStream(in nsIInputStream stream,
+ in string charset,
+ in long contentLength,
+ in string contentType);
+
+ /**
+ * Initialize the principal and document and base URIs that the parser should
+ * use for documents it creates. If this is not called, then a null
+ * principal and its URI will be used. When creating a DOMParser via the JS
+ * constructor, this will be called automatically. This method may only be
+ * called once. If this method fails, all following parse attempts will
+ * fail.
+ *
+ * @param principal The principal to use for documents we create.
+ * If this is null, a codebase principal will be created
+ * based on documentURI; in that case the documentURI must
+ * be non-null.
+ * @param documentURI The documentURI to use for the documents we create.
+ * If null, the principal's URI will be used;
+ * in that case, the principal must be non-null and its
+ * URI must be non-null.
+ * @param baseURI The baseURI to use for the documents we create.
+ * If null, the documentURI will be used.
+ * @param scriptObject The object from which the context for event handling
+ * can be got.
+ */
+ [noscript] void init(in nsIPrincipal principal,
+ in nsIURI documentURI,
+ in nsIURI baseURI,
+ in nsIGlobalObject scriptObject);
+};
+
+%{ C++
+#define NS_DOMPARSER_CID \
+ { /* 3a8a3a50-512c-11d4-9a54-000064657374 */ \
+ 0x3a8a3a50, 0x512c, 0x11d4, \
+ {0x9a, 0x54, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74} }
+#define NS_DOMPARSER_CONTRACTID \
+"@mozilla.org/xmlextras/domparser;1"
+%}
diff --git a/dom/base/nsIDOMSerializer.idl b/dom/base/nsIDOMSerializer.idl
new file mode 100644
index 000000000..3aaf8455e
--- /dev/null
+++ b/dom/base/nsIDOMSerializer.idl
@@ -0,0 +1,53 @@
+/* -*- 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 nsIOutputStream;
+interface nsIDOMNode;
+
+/**
+ * The nsIDOMSerializer interface is really a placeholder till the W3C
+ * DOM Working Group defines a mechanism for serializing DOM nodes.
+ * An instance of this interface can be used to serialize a DOM document
+ * or any DOM subtree.
+ */
+
+[uuid(9fd4ba15-e67c-4c98-b52c-7715f62c9196)]
+interface nsIDOMSerializer : nsISupports
+{
+ /**
+ * The subtree rooted by the specified element is serialized to
+ * a string.
+ *
+ * @param root The root of the subtree to be serialized. This could
+ * be any node, including a Document.
+ * @returns The serialized subtree in the form of a Unicode string
+ */
+ AString serializeToString(in nsIDOMNode root);
+
+ /**
+ * The subtree rooted by the specified element is serialized to
+ * a byte stream using the character set specified.
+ * @param root The root of the subtree to be serialized. This could
+ * be any node, including a Document.
+ * @param stream The byte stream to which the subtree is serialized.
+ * @param charset The name of the character set to use for the encoding
+ * to a byte stream. If this string is empty and root is
+ * a document, the document's character set will be used.
+ */
+ void serializeToStream(in nsIDOMNode root, in nsIOutputStream stream,
+ in AUTF8String charset);
+};
+
+%{ C++
+#define NS_XMLSERIALIZER_CID \
+ { /* a6cf9124-15b3-11d2-932e-00805f8add32 */ \
+ 0xa6cf9124, 0x15b3, 0x11d2, \
+ {0x93, 0x2e, 0x00, 0x80, 0x5f, 0x8a, 0xdd, 0x32} }
+#define NS_XMLSERIALIZER_CONTRACTID \
+"@mozilla.org/xmlextras/xmlserializer;1"
+%}
+
diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h
new file mode 100644
index 000000000..5b10c9914
--- /dev/null
+++ b/dom/base/nsIDocument.h
@@ -0,0 +1,3461 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDocument_h___
+#define nsIDocument_h___
+
+#include "mozFlushType.h" // for enum
+#include "nsAutoPtr.h" // for member
+#include "nsCOMArray.h" // for member
+#include "nsCRT.h" // for NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+#include "nsCompatibility.h" // for member
+#include "nsCOMPtr.h" // for member
+#include "nsGkAtoms.h" // for static class members
+#include "nsIDocumentObserver.h" // for typedef (nsUpdateType)
+#include "nsILoadGroup.h" // for member (in nsCOMPtr)
+#include "nsINode.h" // for base class
+#include "nsIScriptGlobalObject.h" // for member (in nsCOMPtr)
+#include "nsIServiceManager.h"
+#include "nsIUUIDGenerator.h"
+#include "nsPIDOMWindow.h" // for use in inline functions
+#include "nsPropertyTable.h" // for member
+#include "nsDataHashtable.h" // for member
+#include "nsURIHashKey.h" // for member
+#include "mozilla/net/ReferrerPolicy.h" // for member
+#include "nsWeakReference.h"
+#include "mozilla/UseCounter.h"
+#include "mozilla/WeakPtr.h"
+#include "Units.h"
+#include "nsContentListDeclarations.h"
+#include "nsExpirationTracker.h"
+#include "nsClassHashtable.h"
+#include "prclist.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StyleBackendType.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/TimeStamp.h"
+#include <bitset> // for member
+
+#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 gfxUserFontSet;
+class imgIRequest;
+class nsAString;
+class nsBindingManager;
+class nsIDocShell;
+class nsDocShell;
+class nsDOMNavigationTiming;
+class nsFrameLoader;
+class nsHTMLCSSStyleSheet;
+class nsHTMLDocument;
+class nsHTMLStyleSheet;
+class nsIAtom;
+class nsIBFCacheEntry;
+class nsIChannel;
+class nsIContent;
+class nsIContentSink;
+class nsIDocShell;
+class nsIDocShellTreeItem;
+class nsIDocumentEncoder;
+class nsIDocumentObserver;
+class nsIDOMDocument;
+class nsIDOMDocumentType;
+class nsIDOMElement;
+class nsIDOMNodeFilter;
+class nsIDOMNodeList;
+class nsIHTMLCollection;
+class nsILayoutHistoryState;
+class nsILoadContext;
+class nsIObjectLoadingContent;
+class nsIObserver;
+class nsIPresShell;
+class nsIPrincipal;
+class nsIRequest;
+class nsIRunnable;
+class nsIStreamListener;
+class nsIStructuredCloneContainer;
+class nsIURI;
+class nsIVariant;
+class nsViewManager;
+class nsPresContext;
+class nsRange;
+class nsScriptLoader;
+class nsSMILAnimationController;
+class nsTextNode;
+class nsWindowSizes;
+class nsDOMCaretPosition;
+class nsViewportInfo;
+class nsIGlobalObject;
+struct nsCSSSelectorList;
+
+namespace mozilla {
+class CSSStyleSheet;
+class ErrorResult;
+class EventStates;
+class PendingAnimationTracker;
+class StyleSetHandle;
+class SVGAttrAnimationRuleProcessor;
+template<typename> class OwningNonNull;
+
+namespace css {
+class Loader;
+class ImageLoader;
+class Rule;
+} // namespace css
+
+namespace dom {
+class Animation;
+class AnonymousContent;
+class Attr;
+class BoxObject;
+class CDATASection;
+class Comment;
+struct CustomElementDefinition;
+class DocGroup;
+class DocumentFragment;
+class DocumentTimeline;
+class DocumentType;
+class DOMImplementation;
+class DOMIntersectionObserver;
+class DOMStringList;
+class Element;
+struct ElementCreationOptions;
+struct ElementRegistrationOptions;
+class Event;
+class EventTarget;
+class FontFaceSet;
+class FrameRequestCallback;
+struct FullscreenRequest;
+class ImageTracker;
+class ImportManager;
+class HTMLBodyElement;
+struct LifecycleCallbackArgs;
+class Link;
+class Location;
+class MediaQueryList;
+class GlobalObject;
+class NodeFilter;
+class NodeIterator;
+enum class OrientationType : uint32_t;
+class ProcessingInstruction;
+class Promise;
+class StyleSheetList;
+class SVGDocument;
+class SVGSVGElement;
+class Touch;
+class TouchList;
+class TreeWalker;
+class XPathEvaluator;
+class XPathExpression;
+class XPathNSResolver;
+class XPathResult;
+template<typename> class Sequence;
+
+template<typename, typename> class CallbackObjectHolder;
+typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
+
+} // namespace dom
+} // namespace mozilla
+
+#define NS_IDOCUMENT_IID \
+{ 0xce1f7627, 0x7109, 0x4977, \
+ { 0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa } }
+
+// 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
+ DocumentFlavorPlain, // Just a Document
+};
+
+// Enum for HSTS priming states
+enum class HSTSPrimingState {
+ eNO_HSTS_PRIMING = 0, // don't do HSTS Priming
+ eHSTS_PRIMING_ALLOW = 1, // if HSTS priming fails, allow the load to proceed
+ eHSTS_PRIMING_BLOCK = 2 // if HSTS priming fails, block the load
+};
+
+// Document states
+
+// RTL locale: specific to the XUL localedir attribute
+#define NS_DOCUMENT_STATE_RTL_LOCALE NS_DEFINE_EVENT_STATE_MACRO(0)
+// Window activation status
+#define NS_DOCUMENT_STATE_WINDOW_INACTIVE NS_DEFINE_EVENT_STATE_MACRO(1)
+
+// Some function forward-declarations
+class nsContentList;
+
+//----------------------------------------------------------------------
+
+// Document interface. This is implemented by all document objects in
+// Gecko.
+class nsIDocument : public nsINode
+{
+ typedef mozilla::dom::GlobalObject GlobalObject;
+
+public:
+ typedef mozilla::net::ReferrerPolicy ReferrerPolicyEnum;
+ typedef mozilla::dom::Element Element;
+ typedef mozilla::dom::FullscreenRequest FullscreenRequest;
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOCUMENT_IID)
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+
+#ifdef MOZILLA_INTERNAL_API
+ nsIDocument();
+#endif
+
+ // This helper class must be set when we dispatch beforeunload and unload
+ // events in order to avoid unterminate sync XHRs.
+ class MOZ_RAII PageUnloadingEventTimeStamp
+ {
+ nsCOMPtr<nsIDocument> mDocument;
+ bool mSet;
+
+ public:
+ explicit PageUnloadingEventTimeStamp(nsIDocument* 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.
+ * @param aSink The content sink to use for the data. If this is null and
+ * the document needs a content sink, it will create one based
+ * on whatever it knows about the data it's going to load.
+ * This MUST be null if the underlying document is an HTML
+ * document. Even in the XML case, please don't add new calls
+ * with non-null sink.
+ *
+ * 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().
+ */
+ virtual nsresult StartDocumentLoad(const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener **aDocListener,
+ bool aReset,
+ nsIContentSink* aSink = nullptr) = 0;
+ virtual void StopDocumentLoad() = 0;
+
+ virtual void SetSuppressParserErrorElement(bool aSuppress) {}
+ virtual bool SuppressParserErrorElement() { return false; }
+
+ virtual void SetSuppressParserErrorConsoleMessages(bool aSuppress) {}
+ virtual bool SuppressParserErrorConsoleMessages() { return false; }
+
+ /**
+ * Signal that the document title may have changed
+ * (see nsDocument::GetTitle).
+ * @param aBoundTitleElement true if an HTML or SVG <title> element
+ * has just been bound to the document.
+ */
+ virtual void NotifyPossibleTitleChange(bool aBoundTitleElement) = 0;
+
+ /**
+ * Return the URI for the document. May 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;
+ }
+
+ /**
+ * Set the URI for the document. This also sets the document's original URI,
+ * if it's null.
+ */
+ virtual void SetDocumentURI(nsIURI* aURI) = 0;
+
+ /**
+ * Set the URI for the document loaded via XHR, when accessed from
+ * chrome privileged script.
+ */
+ virtual void SetChromeXHRDocURI(nsIURI* aURI) = 0;
+
+ /**
+ * Set the base URI for the document loaded via XHR, when accessed from
+ * chrome privileged script.
+ */
+ virtual void SetChromeXHRDocBaseURI(nsIURI* aURI) = 0;
+
+ /**
+ * Set referrer policy and upgrade-insecure-requests flags
+ */
+ virtual void ApplySettingsFromCSP(bool aSpeculative) = 0;
+
+ /**
+ * Return the referrer policy of the document. Return "default" if there's no
+ * valid meta referrer tag found in the document.
+ */
+ ReferrerPolicyEnum GetReferrerPolicy() const
+ {
+ return mReferrerPolicy;
+ }
+
+ /**
+ * GetReferrerPolicy() for Document.webidl.
+ */
+ uint32_t 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 SetReferrer(const nsACString& aReferrer) {
+ mReferrer = aReferrer;
+ }
+
+ /**
+ * Check to see if a subresource we want to load requires HSTS priming
+ * to be done.
+ */
+ HSTSPrimingState GetHSTSPrimingStateForLocation(nsIURI* aContentLocation) const
+ {
+ HSTSPrimingState state;
+ if (mHSTSPrimingURIList.Get(aContentLocation, &state)) {
+ return state;
+ }
+ return HSTSPrimingState::eNO_HSTS_PRIMING;
+ }
+
+ /**
+ * Add a subresource to the HSTS priming list. If this URI is
+ * not in the HSTS cache, it will trigger an HSTS priming request
+ * when we try to load it.
+ */
+ void AddHSTSPrimingLocation(nsIURI* aContentLocation, HSTSPrimingState aState)
+ {
+ mHSTSPrimingURIList.Put(aContentLocation, aState);
+ }
+
+ void ClearHSTSPrimingLocation(nsIURI* aContentLocation)
+ {
+ mHSTSPrimingURIList.Remove(aContentLocation);
+ }
+
+ /**
+ * Set the principal responsible for this document.
+ */
+ virtual void SetPrincipal(nsIPrincipal *aPrincipal) = 0;
+
+ /**
+ * 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 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();
+ }
+ virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
+
+ virtual void SetBaseURI(nsIURI* aURI) = 0;
+
+ /**
+ * Get/Set the base target of a link in a document.
+ */
+ virtual void GetBaseTarget(nsAString &aBaseTarget) = 0;
+ void SetBaseTarget(const nsString& aBaseTarget) {
+ mBaseTarget = aBaseTarget;
+ }
+
+ /**
+ * Return a standard name for the document's character set.
+ */
+ const nsCString& GetDocumentCharacterSet() const
+ {
+ return mCharacterSet;
+ }
+
+ /**
+ * Set the document's character encoding. |aCharSetID| should be canonical.
+ * That is, callers are responsible for the charset alias resolution.
+ */
+ virtual void SetDocumentCharacterSet(const nsACString& aCharSetID) = 0;
+
+ 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;
+ }
+
+ /**
+ * Add an observer that gets notified whenever the charset changes.
+ */
+ virtual nsresult AddCharSetObserver(nsIObserver* aObserver) = 0;
+
+ /**
+ * Remove a charset observer.
+ */
+ virtual void RemoveCharSetObserver(nsIObserver* aObserver) = 0;
+
+ /**
+ * 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.
+ */
+ virtual Element* AddIDTargetObserver(nsIAtom* aID, IDTargetObserver aObserver,
+ void* aData, bool aForImage) = 0;
+ /**
+ * Remove the (aObserver, aData, aForImage) triple for a specific ID, if
+ * registered.
+ */
+ virtual void RemoveIDTargetObserver(nsIAtom* aID, IDTargetObserver aObserver,
+ void* aData, bool aForImage) = 0;
+
+ /**
+ * Get the Content-Type of this document.
+ * (This will always return NS_OK, but has this signature to be compatible
+ * with nsIDOMDocument::GetContentType())
+ */
+ NS_IMETHOD GetContentType(nsAString& aContentType) = 0;
+
+ /**
+ * Set the Content-Type of this document.
+ */
+ virtual void SetContentType(const nsAString& aContentType) = 0;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Check if the document contains (or has contained) any MathML elements.
+ */
+ bool GetMathMLEnabled() const
+ {
+ return mMathMLEnabled;
+ }
+
+ void SetMathMLEnabled()
+ {
+ mMathMLEnabled = true;
+ }
+
+ /**
+ * Ask this document whether it's the initial document in its window.
+ */
+ bool IsInitialDocument() const
+ {
+ return mIsInitialDocumentInWindow;
+ }
+
+ /**
+ * 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)
+ {
+ mIsInitialDocumentInWindow = aIsInitialDocument;
+ }
+
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Get the has mixed active content loaded flag for this document.
+ */
+ bool GetHasMixedActiveContentLoaded()
+ {
+ return mHasMixedActiveContentLoaded;
+ }
+
+ /**
+ * Set the has mixed active content loaded flag for this document.
+ */
+ void SetHasMixedActiveContentLoaded(bool aHasMixedActiveContentLoaded)
+ {
+ mHasMixedActiveContentLoaded = aHasMixedActiveContentLoaded;
+ }
+
+ /**
+ * Get mixed active content blocked flag for this document.
+ */
+ bool GetHasMixedActiveContentBlocked()
+ {
+ return mHasMixedActiveContentBlocked;
+ }
+
+ /**
+ * Set the mixed active content blocked flag for this document.
+ */
+ void SetHasMixedActiveContentBlocked(bool aHasMixedActiveContentBlocked)
+ {
+ mHasMixedActiveContentBlocked = aHasMixedActiveContentBlocked;
+ }
+
+ /**
+ * Get the has mixed display content loaded flag for this document.
+ */
+ bool GetHasMixedDisplayContentLoaded()
+ {
+ return mHasMixedDisplayContentLoaded;
+ }
+
+ /**
+ * Set the has mixed display content loaded flag for this document.
+ */
+ void SetHasMixedDisplayContentLoaded(bool aHasMixedDisplayContentLoaded)
+ {
+ mHasMixedDisplayContentLoaded = aHasMixedDisplayContentLoaded;
+ }
+
+ /**
+ * Get mixed display content blocked flag for this document.
+ */
+ bool GetHasMixedDisplayContentBlocked()
+ {
+ return mHasMixedDisplayContentBlocked;
+ }
+
+ /**
+ * Set the mixed display content blocked flag for this document.
+ */
+ void SetHasMixedDisplayContentBlocked(bool aHasMixedDisplayContentBlocked)
+ {
+ mHasMixedDisplayContentBlocked = aHasMixedDisplayContentBlocked;
+ }
+
+ /**
+ * Set the mixed content object subrequest flag for this document.
+ */
+ void SetHasMixedContentObjectSubrequest(bool aHasMixedContentObjectSubrequest)
+ {
+ mHasMixedContentObjectSubrequest = aHasMixedContentObjectSubrequest;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Get tracking content blocked flag for this document.
+ */
+ bool GetHasTrackingContentBlocked()
+ {
+ return mHasTrackingContentBlocked;
+ }
+
+ /**
+ * Set the tracking content blocked flag for this document.
+ */
+ void SetHasTrackingContentBlocked(bool aHasTrackingContentBlocked)
+ {
+ mHasTrackingContentBlocked = aHasTrackingContentBlocked;
+ }
+
+ /**
+ * Get tracking content loaded flag for this document.
+ */
+ bool GetHasTrackingContentLoaded()
+ {
+ return mHasTrackingContentLoaded;
+ }
+
+ /**
+ * Set the tracking content loaded flag for this document.
+ */
+ void SetHasTrackingContentLoaded(bool aHasTrackingContentLoaded)
+ {
+ mHasTrackingContentLoaded = aHasTrackingContentLoaded;
+ }
+
+ /**
+ * Get the sandbox flags for this document.
+ * @see nsSandboxFlags.h for the possible flags
+ */
+ uint32_t GetSandboxFlags() const
+ {
+ return mSandboxFlags;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Access HTTP header data (this may also get set from other
+ * sources, like HTML META tags).
+ */
+ virtual void GetHeaderData(nsIAtom* aHeaderField, nsAString& aData) const = 0;
+ virtual void SetHeaderData(nsIAtom* aheaderField, const nsAString& aData) = 0;
+
+ /**
+ * 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.
+ */
+ virtual already_AddRefed<nsIPresShell> CreateShell(
+ nsPresContext* aContext,
+ nsViewManager* aViewManager,
+ mozilla::StyleSetHandle aStyleSet) = 0;
+ virtual void DeleteShell() = 0;
+
+ nsIPresShell* GetShell() const
+ {
+ return GetBFCacheEntry() ? nullptr : mPresShell;
+ }
+
+ // Instead using this method, what you probably want is
+ // RemoveFromBFCacheSync() as we do in MessagePort and BroadcastChannel.
+ void DisallowBFCaching()
+ {
+ NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
+ mBFCacheDisallowed = true;
+ }
+
+ bool IsBFCachingAllowed() const
+ {
+ return !mBFCacheDisallowed;
+ }
+
+ void SetBFCacheEntry(nsIBFCacheEntry* aEntry)
+ {
+ NS_ASSERTION(IsBFCachingAllowed() || !aEntry,
+ "You should have checked!");
+
+ mBFCacheEntry = aEntry;
+ }
+
+ nsIBFCacheEntry* GetBFCacheEntry() const
+ {
+ return mBFCacheEntry;
+ }
+
+ /**
+ * 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.
+ */
+ nsIDocument *GetParentDocument() const
+ {
+ return mParentDocument;
+ }
+
+ /**
+ * Set the parent document of this document.
+ */
+ void SetParentDocument(nsIDocument* aParent)
+ {
+ mParentDocument = aParent;
+ }
+
+ /**
+ * Are plugins allowed in this document ?
+ */
+ virtual nsresult GetAllowPlugins (bool* aAllowPlugins) = 0;
+
+ /**
+ * Set the sub document for aContent to aSubDoc.
+ */
+ virtual nsresult SetSubDocumentFor(Element* aContent,
+ nsIDocument* aSubDoc) = 0;
+
+ /**
+ * Get the sub document for aContent
+ */
+ virtual nsIDocument *GetSubDocumentFor(nsIContent *aContent) const = 0;
+
+ /**
+ * Find the content node for which aDocument is a sub document.
+ */
+ virtual Element* FindContentForSubDocument(nsIDocument* aDocument) const = 0;
+
+ /**
+ * Return the doctype for this document.
+ */
+ mozilla::dom::DocumentType* GetDoctype() const;
+
+ /**
+ * Return the root element for this document.
+ */
+ Element* GetRootElement() const;
+
+ /**
+ * 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.
+ */
+ virtual nsViewportInfo GetViewportInfo(const mozilla::ScreenIntSize& aDisplaySize) = 0;
+
+ /**
+ * 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);
+
+ bool DidDocumentOpen() {
+ return mDidDocumentOpen;
+ }
+
+ already_AddRefed<mozilla::dom::AnonymousContent>
+ InsertAnonymousContent(mozilla::dom::Element& aElement,
+ mozilla::ErrorResult& aError);
+ void RemoveAnonymousContent(mozilla::dom::AnonymousContent& aContent,
+ mozilla::ErrorResult& aError);
+ /**
+ * 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<mozilla::dom::AnonymousContent>>& GetAnonymousContents() {
+ return mAnonymousContents;
+ }
+
+ static nsresult GenerateDocumentId(nsAString& aId);
+ nsresult GetOrCreateId(nsAString& aId);
+ void SetId(const nsAString& aId);
+
+ mozilla::TimeStamp GetPageUnloadingEventTimeStamp() const
+ {
+ if (!mParentDocument) {
+ return mPageUnloadingEventTimeStamp;
+ }
+
+ mozilla::TimeStamp parentTimeStamp(mParentDocument->GetPageUnloadingEventTimeStamp());
+ if (parentTimeStamp.IsNull()) {
+ return mPageUnloadingEventTimeStamp;
+ }
+
+ if (!mPageUnloadingEventTimeStamp ||
+ parentTimeStamp < mPageUnloadingEventTimeStamp) {
+ return parentTimeStamp;
+ }
+
+ return mPageUnloadingEventTimeStamp;
+ }
+
+ virtual void NotifyLayerManagerRecreated() = 0;
+
+protected:
+ virtual Element *GetRootElementInternal() const = 0;
+
+ void SetPageUnloadingEventTimeStamp()
+ {
+ MOZ_ASSERT(!mPageUnloadingEventTimeStamp);
+ mPageUnloadingEventTimeStamp = mozilla::TimeStamp::NowLoRes();
+ }
+
+ void CleanUnloadEventsTimeStamp()
+ {
+ MOZ_ASSERT(mPageUnloadingEventTimeStamp);
+ mPageUnloadingEventTimeStamp = mozilla::TimeStamp();
+ }
+
+private:
+ class SelectorCacheKey
+ {
+ public:
+ explicit SelectorCacheKey(const nsAString& aString) : mKey(aString)
+ {
+ MOZ_COUNT_CTOR(SelectorCacheKey);
+ }
+
+ nsString mKey;
+ nsExpirationState mState;
+
+ nsExpirationState* GetExpirationState() { return &mState; }
+
+ ~SelectorCacheKey()
+ {
+ MOZ_COUNT_DTOR(SelectorCacheKey);
+ }
+ };
+
+ class SelectorCacheKeyDeleter;
+
+public:
+ class SelectorCache final
+ : public nsExpirationTracker<SelectorCacheKey, 4>
+ {
+ public:
+ SelectorCache();
+
+ // CacheList takes ownership of aSelectorList.
+ void CacheList(const nsAString& aSelector, nsCSSSelectorList* aSelectorList);
+
+ virtual void NotifyExpired(SelectorCacheKey* aSelector) override;
+
+ // 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 *aList is null, that indicates that aSelector
+ // has already been parsed and is not a syntactically valid selector.
+ bool GetList(const nsAString& aSelector, nsCSSSelectorList** aList)
+ {
+ return mTable.Get(aSelector, aList);
+ }
+
+ ~SelectorCache()
+ {
+ AgeAllGenerations();
+ }
+
+ private:
+ nsClassHashtable<nsStringHashKey, nsCSSSelectorList> mTable;
+ };
+
+ SelectorCache& GetSelectorCache()
+ {
+ 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(nsIAtom* 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)
+ mozilla::dom::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);
+ }
+
+ /**
+ * Accessors to the collection of stylesheets owned by this document.
+ * Style sheets are ordered, most significant last.
+ */
+
+ /**
+ * These exists to allow us to on-demand load user-agent style sheets that
+ * would otherwise be loaded by nsDocumentViewer::CreateStyleSet. This allows
+ * us to keep the memory used by a document's rule cascade data (the stuff in
+ * its nsStyleSet's nsCSSRuleProcessors) - which can be considerable - lower
+ * than it would be if we loaded all built-in user-agent style sheets up
+ * front.
+ *
+ * By "built-in" user-agent style sheets we mean the user-agent style sheets
+ * that gecko itself supplies (such as html.css and svg.css) as opposed to
+ * user-agent level style sheets inserted by add-ons or the like.
+ *
+ * This function prepends the given style sheet to the document's style set
+ * in order to make sure that it does not override user-agent style sheets
+ * supplied by add-ons or by the app (Firefox OS or Firefox Mobile, for
+ * example), since their sheets should override built-in sheets.
+ *
+ * TODO We can get rid of the whole concept of delayed loading if we fix
+ * bug 77999.
+ */
+ virtual void EnsureOnDemandBuiltInUASheet(mozilla::StyleSheet* aSheet) = 0;
+
+ /**
+ * Get the number of (document) stylesheets
+ *
+ * @return the number of stylesheets
+ * @throws no exceptions
+ */
+ virtual int32_t GetNumberOfStyleSheets() const = 0;
+
+ /**
+ * Get a particular stylesheet
+ * @param aIndex the index the stylesheet lives at. This is zero-based
+ * @return the stylesheet at aIndex. Null if aIndex is out of range.
+ * @throws no exceptions
+ */
+ virtual mozilla::StyleSheet* GetStyleSheetAt(int32_t aIndex) const = 0;
+
+ /**
+ * Insert a sheet at a particular spot in the stylesheet list (zero-based)
+ * @param aSheet the sheet to insert
+ * @param aIndex the index to insert at. This index will be
+ * adjusted for the "special" sheets.
+ * @throws no exceptions
+ */
+ virtual void InsertStyleSheetAt(mozilla::StyleSheet* aSheet,
+ int32_t aIndex) = 0;
+
+ /**
+ * Get the index of a particular stylesheet. This will _always_
+ * consider the "special" sheets as part of the sheet list.
+ * @param aSheet the sheet to get the index of
+ * @return aIndex the index of the sheet in the full list
+ */
+ virtual int32_t GetIndexOfStyleSheet(
+ const mozilla::StyleSheet* aSheet) const = 0;
+
+ /**
+ * Replace the stylesheets in aOldSheets with the stylesheets in
+ * aNewSheets. The two lists must have equal length, and the sheet
+ * at positon J in the first list will be replaced by the sheet at
+ * position J in the second list. Some sheets in the second list
+ * may be null; if so the corresponding sheets in the first list
+ * will simply be removed.
+ */
+ virtual void UpdateStyleSheets(
+ nsTArray<RefPtr<mozilla::StyleSheet>>& aOldSheets,
+ nsTArray<RefPtr<mozilla::StyleSheet>>& aNewSheets) = 0;
+
+ /**
+ * Add a stylesheet to the document
+ */
+ virtual void AddStyleSheet(mozilla::StyleSheet* aSheet) = 0;
+
+ /**
+ * Remove a stylesheet from the document
+ */
+ virtual void RemoveStyleSheet(mozilla::StyleSheet* aSheet) = 0;
+
+ /**
+ * Notify the document that the applicable state of the sheet changed
+ * and that observers should be notified and style sets updated
+ */
+ virtual void SetStyleSheetApplicableState(mozilla::StyleSheet* aSheet,
+ bool aApplicable) = 0;
+
+ enum additionalSheetType {
+ eAgentSheet,
+ eUserSheet,
+ eAuthorSheet,
+ AdditionalSheetTypeCount
+ };
+
+ virtual nsresult LoadAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* aSheetURI) = 0;
+ virtual nsresult AddAdditionalStyleSheet(additionalSheetType aType,
+ mozilla::StyleSheet* aSheet) = 0;
+ virtual void RemoveAdditionalStyleSheet(additionalSheetType aType,
+ nsIURI* sheetURI) = 0;
+ virtual mozilla::StyleSheet* GetFirstAdditionalAuthorSheet() = 0;
+
+ /**
+ * Assuming that aDocSheets is an array of document-level style
+ * sheets for this document, returns the index that aSheet should
+ * be inserted at to maintain document ordering.
+ *
+ * Defined in nsIDocumentInlines.h.
+ */
+ template<typename T>
+ size_t FindDocStyleSheetInsertionPoint(const nsTArray<RefPtr<T>>& aDocSheets,
+ T* aSheet);
+
+ /**
+ * Get this document's CSSLoader. This is guaranteed to not return null.
+ */
+ mozilla::css::Loader* CSSLoader() const {
+ return mCSSLoader;
+ }
+
+ mozilla::StyleBackendType GetStyleBackendType() const {
+ if (mStyleBackendType == mozilla::StyleBackendType(0)) {
+ const_cast<nsIDocument*>(this)->UpdateStyleBackendType();
+ }
+ MOZ_ASSERT(mStyleBackendType != mozilla::StyleBackendType(0));
+ return mStyleBackendType;
+ }
+
+ void UpdateStyleBackendType();
+
+ bool IsStyledByServo() const {
+ return GetStyleBackendType() == mozilla::StyleBackendType::Servo;
+ }
+
+ /**
+ * Get this document's StyleImageLoader. This is guaranteed to not return null.
+ */
+ mozilla::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)
+ */
+ virtual nsIChannel* GetChannel() const = 0;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Get this document's SVG Animation rule processor. May return null
+ * if there isn't one.
+ */
+ mozilla::SVGAttrAnimationRuleProcessor*
+ GetSVGAttrAnimationRuleProcessor() const
+ {
+ return mSVGAttrAnimationRuleProcessor;
+ }
+
+ virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aGlobalObject) = 0;
+
+ /**
+ * 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();
+ }
+ virtual void SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) = 0;
+
+ /**
+ * 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.
+ */
+ virtual nsIGlobalObject* GetScopeObject() const = 0;
+ virtual void SetScopeObject(nsIGlobalObject* aGlobal) = 0;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Get the script loader for this document
+ */
+ virtual nsScriptLoader* ScriptLoader() = 0;
+
+ /**
+ * Add/Remove an element to the document's id and name hashes
+ */
+ virtual void AddToIdTable(Element* aElement, nsIAtom* aId) = 0;
+ virtual void RemoveFromIdTable(Element* aElement, nsIAtom* aId) = 0;
+ virtual void AddToNameTable(Element* aElement, nsIAtom* aName) = 0;
+ virtual void RemoveFromNameTable(Element* aElement, nsIAtom* aName) = 0;
+
+ /**
+ * Returns all elements in the fullscreen stack in the insertion order.
+ */
+ virtual nsTArray<Element*> GetFullscreenStack() const = 0;
+
+ /**
+ * 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 fullscreen element stack, and it can be
+ * returned to fullscreen status by calling RestorePreviousFullScreenState().
+ *
+ * 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.
+ */
+ virtual void AsyncRequestFullScreen(
+ mozilla::UniquePtr<FullscreenRequest>&& aRequest) = 0;
+
+ /**
+ * 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.
+ */
+ virtual nsresult
+ RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement) = 0;
+
+ /**
+ * Called when a frame in a remote child document has rolled back fullscreen
+ * so that all its fullscreen element stacks 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.
+ */
+ virtual nsresult RemoteFrameFullscreenReverted() = 0;
+
+ /**
+ * Restores the previous full-screen element to full-screen status. If there
+ * is no former full-screen element, this exits full-screen, moving the
+ * top-level browser window out of full-screen mode.
+ */
+ virtual void RestorePreviousFullScreenState() = 0;
+
+ /**
+ * Returns true if this document is a fullscreen leaf document, i.e. it
+ * is in fullscreen mode and has no fullscreen children.
+ */
+ virtual bool IsFullscreenLeaf() = 0;
+
+ /**
+ * 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.
+ */
+ virtual nsIDocument* GetFullscreenRoot() = 0;
+
+ /**
+ * Sets the fullscreen root to aRoot. This stores a weak reference to aRoot
+ * in this document.
+ */
+ virtual void SetFullscreenRoot(nsIDocument* aRoot) = 0;
+
+ /**
+ * 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(nsIDocument* 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(nsIDocument* aDocument);
+
+ /**
+ * Handles any pending fullscreen in aDocument or its subdocuments.
+ *
+ * Returns whether there is any fullscreen request handled.
+ */
+ static bool HandlePendingFullscreenRequests(nsIDocument* aDocument);
+
+ /**
+ * Dispatch fullscreenerror event and report the failure message to
+ * the console.
+ */
+ void DispatchFullscreenError(const char* aMessage);
+
+ virtual void RequestPointerLock(Element* aElement) = 0;
+
+ static void UnlockPointer(nsIDocument* aDoc = nullptr);
+
+ // ScreenOrientation related APIs
+
+ virtual void SetCurrentOrientation(mozilla::dom::OrientationType aType,
+ uint16_t aAngle) = 0;
+ virtual uint16_t CurrentOrientationAngle() const = 0;
+ virtual mozilla::dom::OrientationType CurrentOrientationType() const = 0;
+ virtual void SetOrientationPendingPromise(mozilla::dom::Promise* aPromise) = 0;
+ virtual mozilla::dom::Promise* GetOrientationPendingPromise() const = 0;
+
+ //----------------------------------------------------------------------
+
+ // 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.
+ */
+ virtual void AddObserver(nsIDocumentObserver* aObserver) = 0;
+
+ /**
+ * Remove an observer of document change notifications. This will
+ * return false if the observer cannot be found.
+ */
+ virtual bool RemoveObserver(nsIDocumentObserver* aObserver) = 0;
+
+ // 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.
+ virtual void BeginUpdate(nsUpdateType aUpdateType) = 0;
+ virtual void EndUpdate(nsUpdateType aUpdateType) = 0;
+ virtual void BeginLoad() = 0;
+ virtual void EndLoad() = 0;
+
+ enum ReadyState { READYSTATE_UNINITIALIZED = 0, READYSTATE_LOADING = 1, READYSTATE_INTERACTIVE = 3, READYSTATE_COMPLETE = 4};
+ virtual void SetReadyStateInternal(ReadyState rs) = 0;
+ ReadyState GetReadyStateEnum()
+ {
+ return mReadyState;
+ }
+
+ // notify that a content node changed state. This must happen under
+ // a scriptblocker but NOT within a begin/end update.
+ virtual void ContentStateChanged(nsIContent* aContent,
+ mozilla::EventStates aStateMask) = 0;
+
+ // Notify that a document state has changed.
+ // This should only be called by callers whose state is also reflected in the
+ // implementation of nsDocument::GetDocumentState.
+ virtual void DocumentStatesChanged(mozilla::EventStates aStateMask) = 0;
+
+ // Observation hooks for style data to propagate notifications
+ // to document observers
+ virtual void StyleRuleChanged(mozilla::StyleSheet* aStyleSheet,
+ mozilla::css::Rule* aStyleRule) = 0;
+ virtual void StyleRuleAdded(mozilla::StyleSheet* aStyleSheet,
+ mozilla::css::Rule* aStyleRule) = 0;
+ virtual void StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet,
+ mozilla::css::Rule* aStyleRule) = 0;
+
+ /**
+ * Flush notifications for this document and its parent documents
+ * (since those may affect the layout of this one).
+ */
+ virtual void FlushPendingNotifications(mozFlushType aType) = 0;
+
+ /**
+ * 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 >= Flush_Style.
+ */
+ virtual void FlushExternalResources(mozFlushType aType) = 0;
+
+ nsBindingManager* BindingManager() const
+ {
+ return mNodeInfoManager->GetBindingManager();
+ }
+
+ /**
+ * 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) = 0;
+
+ /**
+ * Reset this document to aURI, aLoadGroup, and aPrincipal. aURI must not be
+ * null. If aPrincipal is null, a codebase principal based on aURI will be
+ * used.
+ */
+ virtual void ResetToURI(nsIURI *aURI, nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal) = 0;
+
+ /**
+ * 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.
+ */
+ virtual 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.
+ */
+ virtual void SetXMLDeclaration(const char16_t *aVersion,
+ const char16_t *aEncoding,
+ const int32_t aStandalone) = 0;
+ virtual void GetXMLDeclaration(nsAString& aVersion,
+ nsAString& aEncoding,
+ nsAString& Standalone) = 0;
+
+ /**
+ * 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 IsXMLDocument() const
+ {
+ return !IsHTMLDocument();
+ }
+ bool IsSVGDocument() const
+ {
+ return mType == eSVG;
+ }
+ bool IsXULDocument() const
+ {
+ return mType == eXUL;
+ }
+ bool IsUnstyledDocument()
+ {
+ return IsLoadedAsData() || IsLoadedAsInteractiveData();
+ }
+ bool LoadsFullXULStyleSheetUpFront()
+ {
+ return IsXULDocument() || AllowXULXBL();
+ }
+
+ virtual bool IsScriptEnabled() = 0;
+
+ /**
+ * Create an element with the specified name, prefix and namespace ID.
+ * Returns null if element name parsing failed.
+ */
+ virtual already_AddRefed<Element> CreateElem(const nsAString& aName,
+ nsIAtom* aPrefix,
+ int32_t aNamespaceID,
+ const nsAString* aIs = nullptr) = 0;
+
+ /**
+ * 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
+ */
+ nsISupports *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.
+ */
+ virtual nsIChannel* GetFailedChannel() const = 0;
+
+ /**
+ * Set the channel that failed to load and resulted in an error page.
+ * This is only relevant to error pages.
+ */
+ virtual void SetFailedChannel(nsIChannel* aChannel) = 0;
+
+ /**
+ * Returns the default namespace ID used for elements created in this
+ * document.
+ */
+ int32_t GetDefaultNamespaceID() const
+ {
+ return mDefaultElementType;
+ }
+
+ void DeleteAllProperties();
+ void DeleteAllPropertiesFor(nsINode* aNode);
+
+ nsPropertyTable* PropertyTable(uint16_t aCategory) {
+ if (aCategory == 0)
+ return &mPropertyTable;
+ return GetExtraPropertyTable(aCategory);
+ }
+ uint32_t GetPropertyTableCount()
+ { return mExtraPropertyTables.Length() + 1; }
+
+ /**
+ * 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.
+ */
+ virtual void Sanitize() = 0;
+
+ /**
+ * Enumerate all subdocuments.
+ * The enumerator callback should return true to continue enumerating, or
+ * false to stop. This will never get passed a null aDocument.
+ */
+ typedef bool (*nsSubDocEnumFunc)(nsIDocument *aDocument, void *aData);
+ virtual void EnumerateSubDocuments(nsSubDocEnumFunc aCallback,
+ void *aData) = 0;
+
+ /**
+ * Check whether it is safe to cache the presentation of this document
+ * and all of its subdocuments. 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.
+ */
+ virtual bool CanSavePresentation(nsIRequest *aNewRequest) = 0;
+
+ /**
+ * 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() = 0;
+
+ /**
+ * 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.
+ */
+ virtual void RemovedFromDocShell() = 0;
+
+ /**
+ * 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.
+ */
+ virtual already_AddRefed<nsILayoutHistoryState> GetLayoutHistoryState() const = 0;
+
+ /**
+ * 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).
+ */
+ virtual void BlockOnload() = 0;
+ /**
+ * @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.
+ */
+ virtual void UnblockOnload(bool aFireSync) = 0;
+
+ void BlockDOMContentLoaded()
+ {
+ ++mBlockDOMContentLoaded;
+ }
+
+ virtual void UnblockDOMContentLoaded() = 0;
+
+ /**
+ * 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.
+ * Note: if aDispatchStartTarget isn't null, the showing state of the
+ * document won't be altered.
+ */
+ virtual void OnPageShow(bool aPersisted,
+ mozilla::dom::EventTarget* aDispatchStartTarget) = 0;
+
+ /**
+ * 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.
+ * Note: if aDispatchStartTarget isn't null, the showing state of the
+ * document won't be altered.
+ */
+ virtual void OnPageHide(bool aPersisted,
+ mozilla::dom::EventTarget* aDispatchStartTarget) = 0;
+
+ /*
+ * 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.
+ */
+ virtual void AddStyleRelevantLink(mozilla::dom::Link* aLink) = 0;
+ /**
+ * 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.
+ */
+ virtual void ForgetLink(mozilla::dom::Link* aLink) = 0;
+
+ /**
+ * Resets and removes a box object from the document's box object cache
+ *
+ * @param aElement canonical nsIContent pointer of the box object's element
+ */
+ virtual void ClearBoxObjectFor(nsIContent *aContent) = 0;
+
+ /**
+ * Get the box object for an element. This is not exposed through a
+ * scriptable interface except for XUL documents.
+ */
+ virtual already_AddRefed<mozilla::dom::BoxObject>
+ GetBoxObjectFor(mozilla::dom::Element* aElement,
+ mozilla::ErrorResult& aRv) = 0;
+
+ /**
+ * Support for window.matchMedia()
+ */
+
+ already_AddRefed<mozilla::dom::MediaQueryList>
+ MatchMedia(const nsAString& aMediaQueryList);
+
+ const PRCList* MediaQueryLists() const {
+ 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;
+ }
+
+ /**
+ * See GetAnonymousElementByAttribute on nsIDOMDocumentXBL.
+ */
+ virtual Element*
+ GetAnonymousElementByAttribute(nsIContent* aElement,
+ nsIAtom* aAttrName,
+ const nsAString& aAttrValue) const = 0;
+
+ /**
+ * Helper for nsIDOMDocument::elementFromPoint implementation that allows
+ * ignoring the scroll frame and/or avoiding layout flushes.
+ *
+ * @see nsIDOMWindowUtils::elementFromPoint
+ */
+ virtual Element* ElementFromPointHelper(float aX, float aY,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout) = 0;
+
+ enum ElementsFromPointFlags {
+ IGNORE_ROOT_SCROLL_FRAME = 1,
+ FLUSH_LAYOUT = 2,
+ IS_ELEMENT_FROM_POINT = 4
+ };
+
+ virtual void ElementsFromPointHelper(float aX, float aY,
+ uint32_t aFlags,
+ nsTArray<RefPtr<mozilla::dom::Element>>& aElements) = 0;
+
+ virtual nsresult NodesFromRectHelper(float aX, float aY,
+ float aTopSize, float aRightSize,
+ float aBottomSize, float aLeftSize,
+ bool aIgnoreRootScrollFrame,
+ bool aFlushLayout,
+ nsIDOMNodeList** aReturn) = 0;
+
+ /**
+ * See FlushSkinBindings on nsBindingManager
+ */
+ virtual void FlushSkinBindings() = 0;
+
+ /**
+ * 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;
+ }
+
+ bool IsLoadedAsData()
+ {
+ return mLoadedAsData;
+ }
+
+ bool IsLoadedAsInteractiveData()
+ {
+ return mLoadedAsInteractiveData;
+ }
+
+ bool MayStartLayout()
+ {
+ return mMayStartLayout;
+ }
+
+ void SetMayStartLayout(bool aMayStartLayout)
+ {
+ mMayStartLayout = aMayStartLayout;
+ }
+
+ already_AddRefed<nsIDocumentEncoder> GetCachedEncoder();
+
+ void SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder);
+
+ // In case of failure, the document really can't initialize the frame loader.
+ virtual nsresult InitializeFrameLoader(nsFrameLoader* aLoader) = 0;
+ // In case of failure, the caller must handle the error, for example by
+ // finalizing frame loader asynchronously.
+ virtual nsresult FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) = 0;
+ // Removes the frame loader of aShell from the initialization list.
+ virtual void TryCancelFrameLoaderInitialization(nsIDocShell* aShell) = 0;
+
+ /**
+ * Check whether this document is a root document that is not an
+ * external resource.
+ */
+ bool IsRootDisplayDocument() const
+ {
+ return !mParentDocument && !mDisplayDocument;
+ }
+
+ bool IsBeingUsedAsImage() const {
+ return mIsBeingUsedAsImage;
+ }
+
+ void SetIsBeingUsedAsImage() {
+ mIsBeingUsedAsImage = 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.
+ */
+ nsIDocument* GetDisplayDocument() const
+ {
+ return mDisplayDocument;
+ }
+
+ /**
+ * Set the display document for this document. aDisplayDocument must not be
+ * null.
+ */
+ void SetDisplayDocument(nsIDocument* aDisplayDocument)
+ {
+ MOZ_ASSERT(!GetShell() &&
+ !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;
+ }
+
+ /**
+ * 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() {}
+
+ 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;
+ };
+
+ /**
+ * 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 aRequestingNode the node making the request
+ * @param aPendingLoad the pending load for this request, if any
+ */
+ virtual nsIDocument*
+ RequestExternalResource(nsIURI* aURI,
+ nsINode* aRequestingNode,
+ ExternalResourceLoad** aPendingLoad) = 0;
+
+ /**
+ * Enumerate the external resource documents associated with this document.
+ * The enumerator callback should return true to continue enumerating, or
+ * false to stop. This callback will never get passed a null aDocument.
+ */
+ virtual void EnumerateExternalResources(nsSubDocEnumFunc aCallback,
+ void* aData) = 0;
+
+ /**
+ * 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; }
+
+ /**
+ * Return whether the document and all its ancestors are visible in the sense of
+ * pageshow / hide.
+ */
+ bool IsVisibleConsideringAncestors() const;
+
+ /**
+ * Return true when this document is active, i.e., an active document
+ * in a content viewer. Note that this will return true for bfcached
+ * documents, so this does NOT match the "active document" concept in
+ * the WHATWG spec - see IsCurrentActiveDocument.
+ */
+ bool IsActive() const { return mDocumentContainer && !mRemovedFromDocShell; }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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.
+ typedef void (* ActivityObserverEnumerator)(nsISupports*, void*);
+ void EnumerateActivityObservers(ActivityObserverEnumerator aEnumerator,
+ void* aData);
+
+ // 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.
+ virtual nsSMILAnimationController* GetAnimationController() = 0;
+
+ // 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.
+ virtual mozilla::PendingAnimationTracker* GetPendingAnimationTracker() = 0;
+
+ // 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.
+ virtual mozilla::PendingAnimationTracker*
+ GetOrCreatePendingAnimationTracker() = 0;
+
+ enum SuppressionType {
+ eAnimationsOnly = 0x1,
+
+ // Note that suppressing events also suppresses animation frames, so
+ // there's no need to split out events in its own bitmask.
+ eEvents = 0x3,
+ };
+
+ /**
+ * Prevents user initiated events from being dispatched to the document and
+ * subdocuments.
+ */
+ virtual void SuppressEventHandling(SuppressionType aWhat,
+ uint32_t aIncrease = 1) = 0;
+
+ /**
+ * Unsuppress event handling.
+ * @param aFireEvents If true, delayed events (focus/blur) will be fired
+ * asynchronously.
+ */
+ virtual void UnsuppressEventHandlingAndFireEvents(SuppressionType aWhat,
+ bool aFireEvents) = 0;
+
+ uint32_t EventHandlingSuppressed() const { return mEventsSuppressed; }
+ uint32_t AnimationsPaused() const { return mAnimationsPaused; }
+
+ bool IsEventHandlingEnabled() {
+ return !EventHandlingSuppressed() && mScriptGlobalObject;
+ }
+
+ /**
+ * Increment the number of external scripts being evaluated.
+ */
+ void BeginEvaluatingExternalScript() { ++mExternalScriptsBeingEvaluated; }
+
+ /**
+ * Decrement the number of external scripts being evaluated.
+ */
+ void EndEvaluatingExternalScript() { --mExternalScriptsBeingEvaluated; }
+
+ 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();
+ }
+
+ void ForceEnableXULXBL() {
+ mAllowXULXBL = eTriTrue;
+ }
+
+ /**
+ * Returns the template content owner document that owns the content of
+ * HTMLTemplateElement.
+ */
+ virtual nsIDocument* GetTemplateContentsOwner() = 0;
+
+ /**
+ * 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() { 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.
+ */
+ virtual already_AddRefed<nsIDocument>
+ CreateStaticClone(nsIDocShell* aCloneContainer);
+
+ /**
+ * If this document is a static clone, this returns the original
+ * document.
+ */
+ nsIDocument* GetOriginalDocument()
+ {
+ 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.
+ */
+
+ virtual void PreloadPictureOpened() = 0;
+
+ virtual void PreloadPictureClosed() = 0;
+
+ virtual void PreloadPictureImageSource(const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr,
+ const nsAString& aTypeAttr,
+ const nsAString& aMediaAttr) = 0;
+
+ /**
+ * 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.
+ */
+ virtual already_AddRefed<nsIURI>
+ ResolvePreloadImage(nsIURI *aBaseURI,
+ const nsAString& aSrcAttr,
+ const nsAString& aSrcsetAttr,
+ const nsAString& aSizesAttr) = 0;
+ /**
+ * 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.
+ */
+ virtual void MaybePreLoadImage(nsIURI* uri,
+ const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy) = 0;
+
+ /**
+ * Called by images to forget an image preload when they start doing
+ * the real load.
+ */
+ virtual void ForgetImagePreload(nsIURI* aURI) = 0;
+
+ /**
+ * Called by nsParser to preload style sheets. Can also be merged into the
+ * parser if and when the parser is merged with libgklayout. aCrossOriginAttr
+ * should be a void string if the attr is not present.
+ */
+ virtual void PreloadStyle(nsIURI* aURI, const nsAString& aCharset,
+ const nsAString& aCrossOriginAttr,
+ ReferrerPolicyEnum aReferrerPolicy,
+ const nsAString& aIntegrity) = 0;
+
+ /**
+ * Called by the chrome registry to load style sheets. Can be put
+ * back there if and when when that module is merged with libgklayout.
+ *
+ * This always does a synchronous load. If aIsAgentSheet is true,
+ * it also uses the system principal and enables unsafe rules.
+ * DO NOT USE FOR UNTRUSTED CONTENT.
+ */
+ virtual nsresult LoadChromeSheetSync(nsIURI* aURI, bool aIsAgentSheet,
+ RefPtr<mozilla::StyleSheet>* aSheet) = 0;
+
+ /**
+ * 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.
+ */
+ virtual bool IsDocumentRightToLeft() { return false; }
+
+ /**
+ * Called by Parser for link rel=preconnect
+ */
+ virtual void MaybePreconnect(nsIURI* uri,
+ mozilla::CORSMode aCORSMode) = 0;
+
+ enum DocumentTheme {
+ Doc_Theme_Uninitialized, // not determined yet
+ Doc_Theme_None,
+ Doc_Theme_Neutral,
+ Doc_Theme_Dark,
+ Doc_Theme_Bright
+ };
+
+ /**
+ * Set the document's pending state object (as serialized using structured
+ * clone).
+ */
+ void SetStateObject(nsIStructuredCloneContainer *scContainer);
+
+ /**
+ * Returns Doc_Theme_None if there is no lightweight theme specified,
+ * Doc_Theme_Dark for a dark theme, Doc_Theme_Bright for a light theme, and
+ * Doc_Theme_Neutral for any other theme. This is used to determine the state
+ * of the pseudoclasses :-moz-lwtheme and :-moz-lwtheme-text.
+ */
+ virtual int GetDocumentLWTheme() { return Doc_Theme_None; }
+
+ /**
+ * Returns the document state.
+ * Document state bits have the form NS_DOCUMENT_STATE_* and are declared in
+ * nsIDocument.h.
+ */
+ virtual mozilla::EventStates GetDocumentState() = 0;
+
+ virtual nsISupports* GetCurrentContentSink() = 0;
+
+ virtual void SetScrollToRef(nsIURI *aDocumentURI) = 0;
+ virtual void ScrollToRef() = 0;
+ virtual void ResetScrolledToRefAlready() = 0;
+ virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) = 0;
+
+ /**
+ * This method is similar to GetElementById() from nsIDOMDocument but it
+ * returns a mozilla::dom::Element instead of a nsIDOMElement.
+ * It prevents converting nsIDOMElement to mozilla::dom::Element which is
+ * already converted from mozilla::dom::Element.
+ */
+ virtual Element* GetElementById(const nsAString& aElementId) = 0;
+
+ /**
+ * This method returns _all_ the elements in this document which
+ * have id aElementId, if there are any. Otherwise it returns null.
+ */
+ virtual const nsTArray<Element*>* GetAllElementsForId(const nsAString& aElementId) const = 0;
+
+ /**
+ * 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|
+ */
+ virtual Element* LookupImageElement(const nsAString& aElementId) = 0;
+
+ virtual mozilla::dom::DocumentTimeline* Timeline() = 0;
+ virtual mozilla::LinkedList<mozilla::dom::DocumentTimeline>& Timelines() = 0;
+
+ virtual void GetAnimations(
+ nsTArray<RefPtr<mozilla::dom::Animation>>& aAnimations) = 0;
+
+ mozilla::dom::SVGSVGElement* GetSVGRootElement() const;
+
+ nsresult ScheduleFrameRequestCallback(mozilla::dom::FrameRequestCallback& aCallback,
+ int32_t *aHandle);
+ void CancelFrameRequestCallback(int32_t aHandle);
+
+ typedef nsTArray<RefPtr<mozilla::dom::FrameRequestCallback>> FrameRequestCallbackList;
+ /**
+ * Put this document's frame request callbacks into the provided
+ * list, and forget about them.
+ */
+ void TakeFrameRequestCallbacks(FrameRequestCallbackList& 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();
+
+ // This returns true when the document tree is being teared down.
+ bool InUnlinkOrDeletion() { return mInUnlinkOrDeletion; }
+
+ mozilla::dom::ImageTracker* ImageTracker();
+
+ virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin) = 0;
+ virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin) = 0;
+ virtual void GetPlugins(nsTArray<nsIObjectLoadingContent*>& aPlugins) = 0;
+
+ virtual nsresult AddResponsiveContent(nsIContent* aContent) = 0;
+ virtual void RemoveResponsiveContent(nsIContent* aContent) = 0;
+ virtual void NotifyMediaFeatureValuesChanged() = 0;
+
+ virtual nsresult GetStateObject(nsIVariant** aResult) = 0;
+
+ virtual nsDOMNavigationTiming* GetNavigationTiming() const = 0;
+
+ virtual nsresult SetNavigationTiming(nsDOMNavigationTiming* aTiming) = 0;
+
+ virtual Element* FindImageMap(const nsAString& aNormalizedMapName) = 0;
+
+ // Add aLink to the set of links that need their status resolved.
+ void RegisterPendingLinkUpdate(mozilla::dom::Link* aLink);
+
+ // Remove aLink from the set of links that need their status resolved.
+ // This function must be called when links are removed from the document.
+ void UnregisterPendingLinkUpdate(mozilla::dom::Link* aElement);
+
+ // Update state on links in mLinksToUpdate. This function must
+ // be called prior to selector matching.
+ void FlushPendingLinkUpdates();
+
+#define DEPRECATED_OPERATION(_op) e##_op,
+ enum DeprecatedOperations {
+#include "nsDeprecatedOperationList.h"
+ eDeprecatedOperationCount
+ };
+#undef DEPRECATED_OPERATION
+ bool HasWarnedAbout(DeprecatedOperations aOperation) const;
+ void WarnOnceAbout(DeprecatedOperations aOperation,
+ bool asError = false) 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 char16_t **aParams = nullptr,
+ uint32_t aParamsLength = 0) const;
+
+ virtual void PostVisibilityUpdateEvent() = 0;
+
+ bool IsSyntheticDocument() const { return mIsSyntheticDocument; }
+
+ void SetNeedLayoutFlush() {
+ mNeedLayoutFlush = true;
+ if (mDisplayDocument) {
+ mDisplayDocument->SetNeedLayoutFlush();
+ }
+ }
+
+ void SetNeedStyleFlush() {
+ mNeedStyleFlush = true;
+ if (mDisplayDocument) {
+ mDisplayDocument->SetNeedStyleFlush();
+ }
+ }
+
+ // Note: nsIDocument is a sub-class of nsINode, which has a
+ // SizeOfExcludingThis function. However, because nsIDocument 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 nsIDocument inherits from nsINode; see the comment above the
+ // declaration of nsINode::SizeOfIncludingThis.
+ virtual void DocAddSizeOfIncludingThis(nsWindowSizes* aWindowSizes) const;
+
+ 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)
+ {
+ if (aSync) {
+ ++mInSyncOperationCount;
+ } else {
+ --mInSyncOperationCount;
+ }
+ }
+
+ bool CreatingStaticClone() const
+ {
+ return mCreatingStaticClone;
+ }
+
+ /**
+ * Creates a new element in the HTML namespace with a local name given by
+ * aTag.
+ */
+ already_AddRefed<Element> CreateHTMLElement(nsIAtom* aTag);
+
+ // WebIDL API
+ nsIGlobalObject* GetParentObject() const
+ {
+ return GetScopeObject();
+ }
+ static already_AddRefed<nsIDocument>
+ Constructor(const GlobalObject& aGlobal,
+ mozilla::ErrorResult& rv);
+ virtual mozilla::dom::DOMImplementation*
+ GetImplementation(mozilla::ErrorResult& rv) = 0;
+ MOZ_MUST_USE nsresult GetURL(nsString& retval) const;
+ MOZ_MUST_USE 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,
+ mozilla::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();
+ }
+
+ enum ElementCallbackType {
+ eCreated,
+ eAttached,
+ eDetached,
+ eAttributeChanged
+ };
+
+ nsIDocument* GetTopLevelContentDocument();
+
+ virtual void
+ RegisterElement(JSContext* aCx, const nsAString& aName,
+ const mozilla::dom::ElementRegistrationOptions& aOptions,
+ JS::MutableHandle<JSObject*> aRetval,
+ mozilla::ErrorResult& rv) = 0;
+ virtual already_AddRefed<mozilla::dom::CustomElementRegistry>
+ GetCustomElementRegistry() = 0;
+
+ already_AddRefed<nsContentList>
+ GetElementsByTagName(const nsAString& aTagName)
+ {
+ return NS_GetContentList(this, kNameSpaceID_Unknown, aTagName);
+ }
+ already_AddRefed<nsContentList>
+ GetElementsByTagNameNS(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName,
+ mozilla::ErrorResult& aResult);
+ already_AddRefed<nsContentList>
+ GetElementsByClassName(const nsAString& aClasses);
+ // GetElementById defined above
+ virtual already_AddRefed<Element>
+ CreateElement(const nsAString& aTagName,
+ const mozilla::dom::ElementCreationOptionsOrString& aOptions,
+ mozilla::ErrorResult& rv) = 0;
+ virtual already_AddRefed<Element>
+ CreateElementNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ const mozilla::dom::ElementCreationOptionsOrString& aOptions,
+ mozilla::ErrorResult& rv) = 0;
+ already_AddRefed<mozilla::dom::DocumentFragment>
+ CreateDocumentFragment() const;
+ already_AddRefed<nsTextNode> CreateTextNode(const nsAString& aData) const;
+ already_AddRefed<mozilla::dom::Comment>
+ CreateComment(const nsAString& aData) const;
+ already_AddRefed<mozilla::dom::ProcessingInstruction>
+ CreateProcessingInstruction(const nsAString& target, const nsAString& data,
+ mozilla::ErrorResult& rv) const;
+ already_AddRefed<nsINode>
+ ImportNode(nsINode& aNode, bool aDeep, mozilla::ErrorResult& rv) const;
+ nsINode* AdoptNode(nsINode& aNode, mozilla::ErrorResult& rv);
+ already_AddRefed<mozilla::dom::Event>
+ CreateEvent(const nsAString& aEventType, mozilla::ErrorResult& rv) const;
+ already_AddRefed<nsRange> CreateRange(mozilla::ErrorResult& rv);
+ already_AddRefed<mozilla::dom::NodeIterator>
+ CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow,
+ mozilla::dom::NodeFilter* aFilter,
+ mozilla::ErrorResult& rv) const;
+ already_AddRefed<mozilla::dom::NodeIterator>
+ CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow,
+ mozilla::dom::NodeFilterHolder aFilter,
+ mozilla::ErrorResult& rv) const;
+ already_AddRefed<mozilla::dom::TreeWalker>
+ CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow,
+ mozilla::dom::NodeFilter* aFilter, mozilla::ErrorResult& rv) const;
+ already_AddRefed<mozilla::dom::TreeWalker>
+ CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow,
+ mozilla::dom::NodeFilterHolder aFilter,
+ mozilla::ErrorResult& rv) const;
+
+ // Deprecated WebIDL bits
+ already_AddRefed<mozilla::dom::CDATASection>
+ CreateCDATASection(const nsAString& aData, mozilla::ErrorResult& rv);
+ already_AddRefed<mozilla::dom::Attr>
+ CreateAttribute(const nsAString& aName, mozilla::ErrorResult& rv);
+ already_AddRefed<mozilla::dom::Attr>
+ CreateAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ mozilla::ErrorResult& rv);
+ void GetInputEncoding(nsAString& aInputEncoding) const;
+ already_AddRefed<mozilla::dom::Location> GetLocation() const;
+ void GetReferrer(nsAString& aReferrer) const;
+ void GetLastModified(nsAString& aLastModified) const;
+ void GetReadyState(nsAString& aReadyState) const;
+ // Not const because otherwise the compiler can't figure out whether to call
+ // this GetTitle or the nsAString version from non-const methods, since
+ // neither is an exact match.
+ virtual void GetTitle(nsString& aTitle) = 0;
+ virtual void SetTitle(const nsAString& aTitle, mozilla::ErrorResult& rv) = 0;
+ void GetDir(nsAString& aDirection) const;
+ void SetDir(const nsAString& aDirection);
+ nsPIDOMWindowOuter* GetDefaultView() const
+ {
+ return GetWindow();
+ }
+ Element* GetActiveElement();
+ bool HasFocus(mozilla::ErrorResult& rv) const;
+ // Event handlers are all on nsINode already
+ bool MozSyntheticDocument() const
+ {
+ return IsSyntheticDocument();
+ }
+ Element* GetCurrentScript();
+ void ReleaseCapture() const;
+ virtual void MozSetImageElement(const nsAString& aImageElementId,
+ Element* aElement) = 0;
+ nsIURI* GetDocumentURIObject() const;
+ // Not const because all the full-screen goop is not const
+ virtual bool FullscreenEnabled() = 0;
+ virtual Element* GetFullscreenElement() = 0;
+ bool Fullscreen()
+ {
+ return !!GetFullscreenElement();
+ }
+ void ExitFullscreen();
+ Element* GetPointerLockElement();
+ void ExitPointerLock()
+ {
+ UnlockPointer(this);
+ }
+#ifdef MOZILLA_INTERNAL_API
+ bool Hidden() const
+ {
+ return mVisibilityState != mozilla::dom::VisibilityState::Visible;
+ }
+ mozilla::dom::VisibilityState VisibilityState() const
+ {
+ return mVisibilityState;
+ }
+#endif
+ virtual mozilla::dom::StyleSheetList* StyleSheets() = 0;
+ void GetSelectedStyleSheetSet(nsAString& aSheetSet);
+ virtual void SetSelectedStyleSheetSet(const nsAString& aSheetSet) = 0;
+ virtual void GetLastStyleSheetSet(nsString& aSheetSet) = 0;
+ void GetPreferredStyleSheetSet(nsAString& aSheetSet);
+ virtual mozilla::dom::DOMStringList* StyleSheetSets() = 0;
+ virtual void EnableStyleSheetsForSet(const nsAString& aSheetSet) = 0;
+ Element* ElementFromPoint(float aX, float aY);
+ void ElementsFromPoint(float aX,
+ float aY,
+ nsTArray<RefPtr<mozilla::dom::Element>>& aElements);
+
+ /**
+ * 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();
+
+ // QuerySelector and QuerySelectorAll already defined on nsINode
+ nsINodeList* GetAnonymousNodes(Element& aElement);
+ Element* GetAnonymousElementByAttribute(Element& aElement,
+ const nsAString& aAttrName,
+ const nsAString& aAttrValue);
+ Element* GetBindingParent(nsINode& aNode);
+ void LoadBindingDocument(const nsAString& aURI,
+ nsIPrincipal& aSubjectPrincipal,
+ mozilla::ErrorResult& rv);
+ void LoadBindingDocument(const nsAString& aURI,
+ const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
+ mozilla::ErrorResult& rv);
+ mozilla::dom::XPathExpression*
+ CreateExpression(const nsAString& aExpression,
+ mozilla::dom::XPathNSResolver* aResolver,
+ mozilla::ErrorResult& rv);
+ nsINode* CreateNSResolver(nsINode& aNodeResolver);
+ already_AddRefed<mozilla::dom::XPathResult>
+ Evaluate(JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
+ mozilla::dom::XPathNSResolver* aResolver, uint16_t aType,
+ JS::Handle<JSObject*> aResult, mozilla::ErrorResult& rv);
+ // Touch event handlers already on nsINode
+ already_AddRefed<mozilla::dom::Touch>
+ CreateTouch(nsGlobalWindow* aView, mozilla::dom::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<mozilla::dom::TouchList> CreateTouchList();
+ already_AddRefed<mozilla::dom::TouchList>
+ CreateTouchList(mozilla::dom::Touch& aTouch,
+ const mozilla::dom::Sequence<mozilla::OwningNonNull<mozilla::dom::Touch> >& aTouches);
+ already_AddRefed<mozilla::dom::TouchList>
+ CreateTouchList(const mozilla::dom::Sequence<mozilla::OwningNonNull<mozilla::dom::Touch> >& aTouches);
+
+ void SetStyleSheetChangeEventsEnabled(bool aValue)
+ {
+ mStyleSheetChangeEventsEnabled = aValue;
+ }
+
+ bool StyleSheetChangeEventsEnabled() const
+ {
+ return mStyleSheetChangeEventsEnabled;
+ }
+
+ void ObsoleteSheet(nsIURI *aSheetURI, mozilla::ErrorResult& rv);
+
+ void ObsoleteSheet(const nsAString& aSheetURI, mozilla::ErrorResult& rv);
+
+ already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
+
+ // ParentNode
+ nsIHTMLCollection* Children();
+ uint32_t ChildElementCount();
+
+ virtual nsHTMLDocument* AsHTMLDocument() { return nullptr; }
+ virtual mozilla::dom::SVGDocument* AsSVGDocument() { return nullptr; }
+
+ // The root document of the import tree. If this document is not an import
+ // this will return the document itself.
+ virtual nsIDocument* MasterDocument() = 0;
+ virtual void SetMasterDocument(nsIDocument* master) = 0;
+ virtual bool IsMasterDocument() = 0;
+ virtual mozilla::dom::ImportManager* ImportManager() = 0;
+ // We keep track of the order of sub imports were added to the document.
+ virtual bool HasSubImportLink(nsINode* aLink) = 0;
+ virtual uint32_t IndexOfSubImportLink(nsINode* aLink) = 0;
+ virtual void AddSubImportLink(nsINode* aLink) = 0;
+ virtual nsINode* GetSubImportLink(uint32_t aIdx) = 0;
+
+ /*
+ * Given a node, get a weak reference to it and append that reference to
+ * mBlockedTrackingNodes. Can be used later on to look up a node in it.
+ * (e.g., by the UI)
+ */
+ void AddBlockedTrackingNode(nsINode *node)
+ {
+ if (!node) {
+ return;
+ }
+
+ nsWeakPtr weakNode = do_GetWeakReference(node);
+
+ if (weakNode) {
+ mBlockedTrackingNodes.AppendElement(weakNode);
+ }
+ }
+
+ gfxUserFontSet* GetUserFontSet(bool aFlushUserFontSet = true);
+ void FlushUserFontSet();
+ void RebuildUserFontSet(); // asynchronously
+ mozilla::dom::FontFaceSet* GetFonts() { return mFontFaceSet; }
+
+ // FontFaceSource
+ mozilla::dom::FontFaceSet* Fonts();
+
+ bool DidFireDOMContentLoaded() const { return mDidFireDOMContentLoaded; }
+
+ void SetDocumentUseCounter(mozilla::UseCounter aUseCounter)
+ {
+ if (!mUseCounters[aUseCounter]) {
+ mUseCounters[aUseCounter] = true;
+ }
+ }
+
+ void SetPageUseCounter(mozilla::UseCounter aUseCounter);
+
+ void SetDocumentAndPageUseCounter(mozilla::UseCounter aUseCounter)
+ {
+ SetDocumentUseCounter(aUseCounter);
+ SetPageUseCounter(aUseCounter);
+ }
+
+ void PropagateUseCounters(nsIDocument* aParentDocument);
+
+ void SetUserHasInteracted(bool aUserHasInteracted)
+ {
+ mUserHasInteracted = aUserHasInteracted;
+ }
+
+ bool UserHasInteracted()
+ {
+ return mUserHasInteracted;
+ }
+
+ bool HasScriptsBlockedBySandbox();
+
+ bool InlineScriptAllowedByCSP();
+
+ void ReportHasScrollLinkedEffect();
+ bool HasScrollLinkedEffect() const
+ {
+ return mHasScrollLinkedEffect;
+ }
+
+ mozilla::dom::DocGroup* GetDocGroup();
+
+ virtual void AddIntersectionObserver(
+ mozilla::dom::DOMIntersectionObserver* aObserver) = 0;
+ virtual void RemoveIntersectionObserver(
+ mozilla::dom::DOMIntersectionObserver* aObserver) = 0;
+
+ virtual void UpdateIntersectionObservations() = 0;
+ virtual void ScheduleIntersectionObserverNotification() = 0;
+ virtual void NotifyIntersectionObservers() = 0;
+
+protected:
+ bool GetUseCounter(mozilla::UseCounter aUseCounter)
+ {
+ return mUseCounters[aUseCounter];
+ }
+
+ void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
+ {
+ if (!mChildDocumentUseCounters[aUseCounter]) {
+ mChildDocumentUseCounters[aUseCounter] = true;
+ }
+ }
+
+ bool GetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
+ {
+ return mChildDocumentUseCounters[aUseCounter];
+ }
+
+private:
+ mutable std::bitset<eDeprecatedOperationCount> mDeprecationWarnedAbout;
+ mutable std::bitset<eDocumentWarningCount> mDocWarningWarnedAbout;
+ SelectorCache mSelectorCache;
+
+protected:
+ ~nsIDocument();
+ nsPropertyTable* GetExtraPropertyTable(uint16_t aCategory);
+
+ // Never ever call this. Only call GetWindow!
+ virtual nsPIDOMWindowOuter* GetWindowInternal() const = 0;
+
+ // Never ever call this. Only call GetScriptHandlingObject!
+ virtual nsIScriptGlobalObject* GetScriptHandlingObjectInternal() const = 0;
+
+ // Never ever call this. Only call AllowXULXBL!
+ virtual bool InternalAllowXULXBL() = 0;
+
+ /**
+ * These methods should be called before and after dispatching
+ * a mutation event.
+ * To make this easy and painless, use the mozAutoSubtreeModified helper class.
+ */
+ virtual void WillDispatchMutationEvent(nsINode* aTarget) = 0;
+ virtual void MutationEventDispatched(nsINode* aTarget) = 0;
+ friend class mozAutoSubtreeModified;
+
+ virtual Element* GetNameSpaceElement() override
+ {
+ return GetRootElement();
+ }
+
+ void SetContentTypeInternal(const nsACString& aType);
+
+ nsCString GetContentTypeInternal() const
+ {
+ return mContentType;
+ }
+
+ mozilla::dom::XPathEvaluator* XPathEvaluator();
+
+ void HandleRebuildUserFontSet() {
+ mPostedFlushUserFontSet = false;
+ FlushUserFontSet();
+ }
+
+ const nsString& GetId() const
+ {
+ return mId;
+ }
+
+ // 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(nsIPresShell* aOldShell = nullptr);
+
+ nsCString mReferrer;
+ nsString mLastModified;
+
+ nsCOMPtr<nsIURI> mDocumentURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mChromeXHRDocURI;
+ nsCOMPtr<nsIURI> mDocumentBaseURI;
+ nsCOMPtr<nsIURI> mChromeXHRDocBaseURI;
+
+ nsWeakPtr mDocumentLoadGroup;
+
+ bool mReferrerPolicySet;
+ ReferrerPolicyEnum mReferrerPolicy;
+
+ bool mBlockAllMixedContent;
+ bool mBlockAllMixedContentPreloads;
+ bool mUpgradeInsecureRequests;
+ bool mUpgradeInsecurePreloads;
+
+ // if nsMixedContentBlocker requires sending an HSTS priming request,
+ // temporarily store that in the document so that it can be propogated to the
+ // LoadInfo and eventually the HTTP Channel
+ nsDataHashtable<nsURIHashKey, HSTSPrimingState> mHSTSPrimingURIList;
+
+ mozilla::WeakPtr<nsDocShell> mDocumentContainer;
+
+ nsCString mCharacterSet;
+ int32_t mCharacterSetSource;
+
+ // This is just a weak pointer; the parent document owns its children.
+ nsIDocument* mParentDocument;
+
+ // A reference to the element last returned from GetRootElement().
+ mozilla::dom::Element* mCachedRootElement;
+
+ // 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<mozilla::css::Loader> mCSSLoader;
+ RefPtr<mozilla::css::ImageLoader> mStyleImageLoader;
+ RefPtr<nsHTMLStyleSheet> mAttrStyleSheet;
+ RefPtr<nsHTMLCSSStyleSheet> mStyleAttrStyleSheet;
+ RefPtr<mozilla::SVGAttrAnimationRuleProcessor> mSVGAttrAnimationRuleProcessor;
+
+ // Tracking for images in the document.
+ RefPtr<mozilla::dom::ImageTracker> mImageTracker;
+
+ // The set of all object, embed, applet, video/audio elements or
+ // nsIObjectLoadingContent or nsIDocumentActivity 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.
+ nsAutoPtr<nsTHashtable<nsPtrHashKey<nsISupports> > > mActivityObservers;
+
+ // The set of all links that need their status resolved. Links must add themselves
+ // to this set by calling RegisterPendingLinkUpdate when added to a document and must
+ // remove themselves by calling UnregisterPendingLinkUpdate when removed from a document.
+ nsTHashtable<nsPtrHashKey<mozilla::dom::Link> > mLinksToUpdate;
+
+ // SMIL Animation Controller, lazily-initialized in GetAnimationController
+ RefPtr<nsSMILAnimationController> mAnimationController;
+
+ // Table of element properties for this document.
+ nsPropertyTable mPropertyTable;
+ nsTArray<nsAutoPtr<nsPropertyTable> > mExtraPropertyTables;
+
+ // Our cached .children collection
+ nsCOMPtr<nsIHTMLCollection> mChildrenCollection;
+
+ // container for per-context fonts (downloadable, SVG, etc.)
+ RefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
+
+ // Compatibility mode
+ nsCompatibility mCompatMode;
+
+ // Our readyState
+ ReadyState mReadyState;
+
+#ifdef MOZILLA_INTERNAL_API
+ // Our visibility state
+ mozilla::dom::VisibilityState mVisibilityState;
+ static_assert(sizeof(mozilla::dom::VisibilityState) == sizeof(uint32_t), "Error size of mVisibilityState and mDummy");
+#else
+ uint32_t mDummy;
+#endif
+
+ // Whether this document has (or will have, once we have a pres shell) a
+ // Gecko- or Servo-backed style system.
+ mozilla::StyleBackendType mStyleBackendType;
+
+ // True if BIDI is enabled.
+ bool mBidiEnabled : 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 we're loaded as data and therefor has any dangerous stuff, such
+ // as scripts and plugins, disabled.
+ bool mLoadedAsData : 1;
+
+ // This flag is only set in XMLDocument, for e.g. documents used in XBL. We
+ // don't want animations to play in such documents, so we need to store the
+ // flag here so that we can check it in nsDocument::GetAnimationController.
+ bool mLoadedAsInteractiveData : 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 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 is this document is synthetic : stand alone image, video, audio
+ // file, etc.
+ bool mIsSyntheticDocument : 1;
+
+ // True if this document has links whose state needs updating
+ bool mHasLinksToUpdate : 1;
+
+ // True if a layout flush might not be a no-op
+ bool mNeedLayoutFlush : 1;
+
+ // True if a style flush might not be a no-op
+ bool mNeedStyleFlush : 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 has loaded Mixed Active Script (see nsMixedContentBlocker.cpp)
+ bool mHasMixedActiveContentLoaded : 1;
+
+ // True if a document has blocked Mixed Active Script (see nsMixedContentBlocker.cpp)
+ bool mHasMixedActiveContentBlocked : 1;
+
+ // True if a document has loaded Mixed Display/Passive Content (see nsMixedContentBlocker.cpp)
+ bool mHasMixedDisplayContentLoaded : 1;
+
+ // True if a document has blocked Mixed Display/Passive Content (see nsMixedContentBlocker.cpp)
+ bool mHasMixedDisplayContentBlocked : 1;
+
+ // True if a document loads a plugin object that attempts to load mixed content subresources through necko(see nsMixedContentBlocker.cpp)
+ bool mHasMixedContentObjectSubrequest : 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 a document has blocked Tracking Content
+ bool mHasTrackingContentBlocked : 1;
+
+ // True if a document has loaded Tracking Content
+ bool mHasTrackingContentLoaded : 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 the document was created by a srcdoc iframe.
+ bool mIsSrcdocDocument : 1;
+
+ // Records whether we've done a document.open. If this is true, it's possible
+ // for nodes from this document to have outdated wrappers in their wrapper
+ // caches.
+ bool mDidDocumentOpen : 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;
+
+ // Has GetUserFontSet() been called?
+ bool mGetUserFontSetCalled : 1;
+
+ // Do we currently have an event posted to call FlushUserFontSet?
+ bool mPostedFlushUserFontSet : 1;
+
+ // True is document has ever been in a foreground window.
+ bool mEverInForeground : 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 ReportHasScrollLinkedEffect() has been called.
+ bool mHasScrollLinkedEffect : 1;
+
+ // True if we have frame request callbacks scheduled with the refresh driver.
+ // This should generally be updated only via
+ // UpdateFrameRequestCallbackSchedulingState.
+ bool mFrameRequestCallbacksScheduled : 1;
+
+ enum Type {
+ eUnknown, // should never be used
+ eHTML,
+ eXHTML,
+ eGenericXML,
+ eSVG,
+ eXUL
+ };
+
+ Type mType;
+
+ uint8_t mDefaultElementType;
+
+ enum Tri {
+ eTriUnset = 0,
+ eTriFalse,
+ eTriTrue
+ };
+
+ Tri mAllowXULXBL;
+
+#ifdef DEBUG
+ /**
+ * This is true while FlushPendingLinkUpdates executes. Calls to
+ * [Un]RegisterPendingLinkUpdate will assert when this is true.
+ */
+ bool mIsLinkUpdateRegistrationsForbidden;
+#endif
+
+ // 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.
+ nsCOMPtr<nsIDocument> 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;
+
+ nsCString mContentLanguage;
+
+ // The channel that got passed to nsDocument::StartDocumentLoad(), if any.
+ nsCOMPtr<nsIChannel> mChannel;
+private:
+ nsCString mContentType;
+ nsString mId;
+protected:
+
+ // The document's security info
+ nsCOMPtr<nsISupports> 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;
+
+ nsIPresShell* mPresShell;
+
+ nsCOMArray<nsINode> mSubtreeModifiedTargets;
+ uint32_t mSubtreeModifiedDepth;
+
+ // 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.
+ nsCOMPtr<nsIDocument> mDisplayDocument;
+
+ uint32_t mEventsSuppressed;
+
+ uint32_t mAnimationsPaused;
+
+ /**
+ * The number number of external scripts (ones with the src attribute) that
+ * have this document as their owner and that are being evaluated right now.
+ */
+ uint32_t mExternalScriptsBeingEvaluated;
+
+ /**
+ * The current frame request callback handle
+ */
+ int32_t mFrameRequestCallbackCounter;
+
+ // Count of live static clones of this document.
+ uint32_t mStaticCloneCount;
+
+ // 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> mBlockedTrackingNodes;
+
+ // Weak reference to mScriptGlobalObject QI:d to nsPIDOMWindow,
+ // updated on every set of mScriptGlobalObject.
+ nsPIDOMWindowInner* mWindow;
+
+ nsCOMPtr<nsIDocumentEncoder> mCachedEncoder;
+
+ struct FrameRequest;
+
+ nsTArray<FrameRequest> mFrameRequestCallbacks;
+
+ // 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;
+ nsCOMPtr<nsIVariant> mStateObjectCached;
+
+ uint32_t mInSyncOperationCount;
+
+ RefPtr<mozilla::dom::XPathEvaluator> mXPathEvaluator;
+
+ nsTArray<RefPtr<mozilla::dom::AnonymousContent>> mAnonymousContents;
+
+ uint32_t mBlockDOMContentLoaded;
+
+ // Our live MediaQueryLists
+ PRCList mDOMMediaQueryLists;
+
+ // Flags for use counters used directly by this document.
+ std::bitset<mozilla::eUseCounter_Count> mUseCounters;
+ // Flags for use counters used by any child documents of this document.
+ std::bitset<mozilla::eUseCounter_Count> mChildDocumentUseCounters;
+ // Flags for whether we've notified our top-level "page" of a use counter
+ // for this child document.
+ std::bitset<mozilla::eUseCounter_Count> mNotifiedPageForUseCounter;
+
+ // Whether the user has interacted with the document or not:
+ bool mUserHasInteracted;
+
+ mozilla::TimeStamp mPageUnloadingEventTimeStamp;
+
+ RefPtr<mozilla::dom::DocGroup> mDocGroup;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, 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(nsIDocument* aSubtreeOwner, nsINode* aTarget)
+ {
+ UpdateTarget(aSubtreeOwner, aTarget);
+ }
+
+ ~mozAutoSubtreeModified()
+ {
+ UpdateTarget(nullptr, nullptr);
+ }
+
+ void UpdateTarget(nsIDocument* aSubtreeOwner, nsINode* aTarget)
+ {
+ if (mSubtreeOwner) {
+ mSubtreeOwner->MutationEventDispatched(mTarget);
+ }
+
+ mTarget = aTarget;
+ mSubtreeOwner = aSubtreeOwner;
+ if (mSubtreeOwner) {
+ mSubtreeOwner->WillDispatchMutationEvent(mTarget);
+ }
+ }
+
+private:
+ nsCOMPtr<nsINode> mTarget;
+ nsCOMPtr<nsIDocument> mSubtreeOwner;
+};
+
+class MOZ_STACK_CLASS nsAutoSyncOperation
+{
+public:
+ explicit nsAutoSyncOperation(nsIDocument* aDocument);
+ ~nsAutoSyncOperation();
+private:
+ nsCOMArray<nsIDocument> mDocuments;
+ uint32_t mMicroTaskLevel;
+};
+
+// XXX These belong somewhere else
+nsresult
+NS_NewHTMLDocument(nsIDocument** aInstancePtrResult, bool aLoadedAsData = false);
+
+nsresult
+NS_NewXMLDocument(nsIDocument** aInstancePtrResult, bool aLoadedAsData = false,
+ bool aIsPlainDocument = false);
+
+nsresult
+NS_NewSVGDocument(nsIDocument** aInstancePtrResult);
+
+nsresult
+NS_NewImageDocument(nsIDocument** aInstancePtrResult);
+
+nsresult
+NS_NewVideoDocument(nsIDocument** aInstancePtrResult);
+
+// 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(nsIDOMDocument** aInstancePtrResult,
+ const nsAString& aNamespaceURI,
+ const nsAString& aQualifiedName,
+ nsIDOMDocumentType* aDoctype,
+ nsIURI* aDocumentURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aPrincipal,
+ bool aLoadedAsData,
+ nsIGlobalObject* aEventObject,
+ DocumentFlavor aFlavor);
+
+// This is used only for xbl documents created from the startup cache.
+// Non-cached documents are created in the same manner as xml documents.
+nsresult
+NS_NewXBLDocument(nsIDOMDocument** aInstancePtrResult,
+ nsIURI* aDocumentURI,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aPrincipal);
+
+nsresult
+NS_NewPluginDocument(nsIDocument** aInstancePtrResult);
+
+inline nsIDocument*
+nsINode::GetOwnerDocument() const
+{
+ nsIDocument* ownerDoc = OwnerDoc();
+
+ return ownerDoc != this ? ownerDoc : nullptr;
+}
+
+inline nsINode*
+nsINode::OwnerDocAsNode() const
+{
+ return OwnerDoc();
+}
+
+inline mozilla::dom::ParentObject
+nsINode::GetParentObject() const
+{
+ mozilla::dom::ParentObject p(OwnerDoc());
+ // Note that mUseXBLScope is a no-op for chrome, and other places where we
+ // don't use XBL scopes.
+ p.mUseXBLScope = IsInAnonymousSubtree() && !IsAnonymousContentInSVGUseSubtree();
+ return p;
+}
+
+#endif /* nsIDocument_h___ */
diff --git a/dom/base/nsIDocumentEncoder.idl b/dom/base/nsIDocumentEncoder.idl
new file mode 100644
index 000000000..4be2c108c
--- /dev/null
+++ b/dom/base/nsIDocumentEncoder.idl
@@ -0,0 +1,372 @@
+/* -*- 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 nsIDOMDocument;
+interface nsIDOMRange;
+interface nsISelection;
+interface nsIDOMNode;
+interface nsIOutputStream;
+
+%{ C++
+class nsINode;
+class nsIDocument;
+%}
+[ptr] native nsINodePtr(nsINode);
+[ptr] native nsIDocumentPtr(nsIDocument);
+
+[scriptable, uuid(3d9371d8-a2ad-403e-8b0e-8885ad3562e3)]
+interface nsIDocumentEncoderNodeFixup : nsISupports
+{
+ /**
+ * Create a fixed up version of a node. This method is called before
+ * each node in a document is about to be persisted. The implementor
+ * may return a new node with fixed up attributes or null. If null is
+ * returned the node should be used as-is.
+ * @param aNode Node to fixup.
+ * @param [OUT] aSerializeCloneKids True if the document encoder should
+ * apply recursive serialization to the children of the fixed up node
+ * instead of the children of the original node.
+ * @return The resulting fixed up node.
+ */
+ nsIDOMNode fixupNode(in nsIDOMNode aNode, out boolean aSerializeCloneKids);
+};
+
+[scriptable, uuid(21f112df-d96f-47da-bfcb-5331273003d1)]
+interface nsIDocumentEncoder : nsISupports
+{
+ // Output methods flag bits. There are a frightening number of these,
+ // because everyone wants something a little bit different
+
+
+ /**
+ * Output only the selection (as opposed to the whole document).
+ */
+ const unsigned long OutputSelectionOnly = (1 << 0);
+
+ /** Plaintext output: Convert html to plaintext that looks like the html.
+ * Implies wrap (except inside <pre>), since html wraps.
+ * HTML, XHTML and XML output: do prettyprinting, ignoring existing formatting.
+ * XML output : it doesn't implicitly wrap
+ */
+ const unsigned long OutputFormatted = (1 << 1);
+
+ /** Don't do prettyprinting. Don't do any wrapping that's not in the existing
+ * HTML/XML source. This option overrides OutputFormatted if both are set.
+ * HTML/XHTML output: If neither are set, there won't be prettyprinting too, but
+ * long lines will be wrapped.
+ * Supported also in XML and Plaintext output.
+ * @note This option does not affect entity conversion.
+ */
+ const unsigned long OutputRaw = (1 << 2);
+
+ /**
+ * Do not print html head tags.
+ * XHTML/HTML output only.
+ */
+ const unsigned long OutputBodyOnly = (1 << 3);
+
+ /**
+ * Output as though the content is preformatted
+ * (e.g. maybe it's wrapped in a PRE or PRE_WRAP style tag)
+ * Plaintext output only.
+ * XXXbz How does this interact with
+ * OutputFormatted/OutputRaw/OutputPreformatted/OutputFormatFlowed?
+ */
+ const unsigned long OutputPreformatted = (1 << 4);
+
+ /**
+ * Wrap even if we're not doing formatted output (e.g. for text fields).
+ * Supported in XML, XHTML, HTML and Plaintext output.
+ * Set implicitly in HTML/XHTML output when no OutputRaw.
+ * Ignored when OutputRaw.
+ * XXXLJ: set implicitly in HTML/XHTML output, to keep compatible behaviors
+ * for old callers of this interface
+ * XXXbz How does this interact with OutputFormatFlowed?
+ */
+ const unsigned long OutputWrap = (1 << 5);
+
+ /**
+ * Output for format flowed (RFC 2646). This is used when converting
+ * to text for mail sending. This differs just slightly
+ * but in an important way from normal formatted, and that is that
+ * lines are space stuffed. This can't (correctly) be done later.
+ * PlainText output only.
+ * XXXbz How does this interact with
+ * OutputFormatted/OutputRaw/OutputPreformatted/OutputWrap?
+ */
+ const unsigned long OutputFormatFlowed = (1 << 6);
+
+ /**
+ * Convert links, image src, and script src to absolute URLs when possible.
+ * XHTML/HTML output only.
+ */
+ const unsigned long OutputAbsoluteLinks = (1 << 7);
+
+ /**
+ * Attempt to encode entities standardized at W3C (HTML, MathML, etc).
+ * This is a catch-all flag for documents with mixed contents. Beware of
+ * interoperability issues. See below for other flags which might likely
+ * do what you want.
+ * HTML output only.
+ */
+ const unsigned long OutputEncodeW3CEntities = (1 << 8);
+
+ /**
+ * LineBreak processing: if this flag is set than CR line breaks will
+ * be written. If neither this nor OutputLFLineBreak is set, then we
+ * will use platform line breaks. The combination of the two flags will
+ * cause CRLF line breaks to be written.
+ */
+ const unsigned long OutputCRLineBreak = (1 << 9);
+
+ /**
+ * LineBreak processing: if this flag is set than LF line breaks will
+ * be written. If neither this nor OutputCRLineBreak is set, then we
+ * will use platform line breaks. The combination of the two flags will
+ * cause CRLF line breaks to be written.
+ */
+ const unsigned long OutputLFLineBreak = (1 << 10);
+
+ /**
+ * Output the content of noscript elements (only for serializing
+ * to plaintext).
+ */
+ const unsigned long OutputNoScriptContent = (1 << 11);
+
+ /**
+ * Output the content of noframes elements (only for serializing
+ * to plaintext). (Used only internally in the plain text serializer;
+ * ignored if passed by the caller.)
+ */
+ const unsigned long OutputNoFramesContent = (1 << 12);
+
+ /**
+ * Don't allow any formatting nodes (e.g. <br>, <b>) inside a <pre>.
+ * This is used primarily by mail. XHTML/HTML output only.
+ */
+ const unsigned long OutputNoFormattingInPre = (1 << 13);
+
+ /**
+ * Encode entities when outputting to a string.
+ * E.g. If set, we'll output &nbsp; if clear, we'll output 0xa0.
+ * The basic set is just &nbsp; &amp; &lt; &gt; &quot; for interoperability
+ * with older products that don't support &alpha; and friends.
+ * HTML output only.
+ */
+ const unsigned long OutputEncodeBasicEntities = (1 << 14);
+
+ /**
+ * Encode entities when outputting to a string.
+ * The Latin1 entity set additionally includes 8bit accented letters
+ * between 128 and 255.
+ * HTML output only.
+ */
+ const unsigned long OutputEncodeLatin1Entities = (1 << 15);
+
+ /**
+ * Encode entities when outputting to a string.
+ * The HTML entity set additionally includes accented letters, greek
+ * letters, and other special markup symbols as defined in HTML4.
+ * HTML output only.
+ */
+ const unsigned long OutputEncodeHTMLEntities = (1 << 16);
+
+ /**
+ * Normally &nbsp; is replaced with a space character when
+ * encoding data as plain text, set this flag if that's
+ * not desired.
+ * Plaintext output only.
+ */
+ const unsigned long OutputPersistNBSP = (1 << 17);
+
+ /**
+ * Normally when serializing the whole document using the HTML or
+ * XHTML serializer, the encoding declaration is rewritten to match.
+ * This flag suppresses that behavior.
+ */
+ const unsigned long OutputDontRewriteEncodingDeclaration = (1 << 18);
+
+ /**
+ * When using the HTML or XHTML serializer, skip elements that are not
+ * visible when this flag is set. Elements are not visible when they
+ * have CSS style display:none or visibility:collapse, for example.
+ */
+ const unsigned long SkipInvisibleContent = (1 << 19);
+
+ /**
+ * Output for delsp=yes (RFC 3676). This is used with OutputFormatFlowed
+ * when converting to text for mail sending.
+ * PlainText output only.
+ */
+ const unsigned long OutputFormatDelSp = (1 << 20);
+
+ /**
+ * Drop <br> elements considered "invisible" by the editor. OutputPreformatted
+ * implies this flag.
+ */
+ const unsigned long OutputDropInvisibleBreak = (1 << 21);
+
+ /**
+ * Don't check for _moz_dirty attributes when deciding whether to
+ * pretty-print if this flag is set (bug 599983).
+ */
+ const unsigned long OutputIgnoreMozDirty = (1 << 22);
+
+ /**
+ * Output the content of non-text elements as the placehodler character
+ * U+FFFC (OBJECT REPLACEMENT CHARACTER, only for serializing to plaintext).
+ */
+ const unsigned long OutputNonTextContentAsPlaceholder = (1 << 23);
+
+ /**
+ * Don't Strip ending spaces from a line (only for serializing to plaintext).
+ */
+ const unsigned long OutputDontRemoveLineEndingSpaces = (1 << 24);
+
+ /**
+ * Serialize in a way that is suitable for copying a plaintext version of the
+ * document to the clipboard. This can for example cause line endings to be
+ * injected at preformatted block element boundaries.
+ */
+ const unsigned long OutputForPlainTextClipboardCopy = (1 << 25);
+
+ /**
+ * Include ruby annotations and ruby parentheses in the output.
+ * PlainText output only.
+ */
+ const unsigned long OutputRubyAnnotation = (1 << 26);
+
+ /**
+ * Disallow breaking of long character strings. This is important
+ * for serializing e-mail which contains CJK strings. These must
+ * not be broken just as "normal" longs strings aren't broken.
+ */
+ const unsigned long OutputDisallowLineBreaking = (1 << 27);
+
+ /**
+ * Initialize with a pointer to the document and the mime type.
+ * @param aDocument Document to encode.
+ * @param aMimeType MimeType to use. May also be set by SetMimeType.
+ * @param aFlags Flags to use while encoding. May also be set by SetFlags.
+ */
+ void init(in nsIDOMDocument aDocument,
+ in AString aMimeType,
+ in unsigned long aFlags);
+ [noscript] void nativeInit(in nsIDocumentPtr aDocument,
+ in AString aMimeType,
+ in unsigned long aFlags);
+
+ /**
+ * If the selection is set to a non-null value, then the
+ * selection is used for encoding, otherwise the entire
+ * document is encoded.
+ * @param aSelection The selection to encode.
+ */
+ void setSelection(in nsISelection aSelection);
+
+ /**
+ * If the range is set to a non-null value, then the
+ * range is used for encoding, otherwise the entire
+ * document or selection is encoded.
+ * @param aRange The range to encode.
+ */
+ void setRange(in nsIDOMRange aRange);
+
+ /**
+ * If the node is set to a non-null value, then the
+ * node is used for encoding, otherwise the entire
+ * document or range or selection is encoded.
+ * @param aNode The node to encode.
+ */
+ void setNode(in nsIDOMNode aNode);
+ [noscript] void setNativeNode(in nsINodePtr aNode);
+
+ /**
+ * If the container is set to a non-null value, then its
+ * child nodes are used for encoding, otherwise the entire
+ * document or range or selection or node is encoded.
+ * @param aContainer The node which child nodes will be encoded.
+ */
+ void setContainerNode(in nsIDOMNode aContainer);
+ [noscript] void setNativeContainerNode(in nsINodePtr aContainer);
+
+ /**
+ * Documents typically have an intrinsic character set,
+ * but if no intrinsic value is found, the platform character set
+ * is used. This function overrides both the intrinisc and platform
+ * charset.
+ * @param aCharset Overrides the both the intrinsic or platform
+ * character set when encoding the document.
+ *
+ * Possible result codes: NS_ERROR_NO_CHARSET_CONVERTER
+ */
+ void setCharset(in ACString aCharset);
+
+ /**
+ * Set a wrap column. This may have no effect in some types of encoders.
+ * @param aWrapColumn Column to which to wrap.
+ */
+ void setWrapColumn(in unsigned long aWrapColumn);
+
+ /**
+ * The mime type preferred by the encoder. This piece of api was
+ * added because the copy encoder may need to switch mime types on you
+ * if you ask it to copy html that really represents plaintext content.
+ * Call this AFTER Init() and SetSelection() have both been called.
+ */
+ readonly attribute AString mimeType;
+
+ /**
+ * Encode the document and send the result to the nsIOutputStream.
+ *
+ * Possible result codes are the stream errors which might have
+ * been encountered.
+ * @param aStream Stream into which to encode.
+ */
+ void encodeToStream(in nsIOutputStream aStream);
+
+ /**
+ * Encode the document into a string.
+ *
+ * @return The document encoded into a string.
+ */
+ AString encodeToString();
+
+ /**
+ * Encode the document into a string. Stores the extra context information
+ * into the two arguments.
+ * @param [OUT] aContextString The string where the parent hierarchy
+ * information will be stored.
+ * @param [OUT] aInfoString The string where extra context info will
+ * be stored.
+ * @return The document encoded as a string.
+ *
+ */
+ AString encodeToStringWithContext( out AString aContextString,
+ out AString aInfoString);
+
+ /**
+ * Encode the document into a string of limited size.
+ * @param aMaxLength After aMaxLength characters, the encoder will stop
+ * encoding new data.
+ * Only values > 0 will be considered.
+ * The returned string may be slightly larger than
+ * aMaxLength because some serializers (eg. HTML)
+ * may need to close some tags after they stop
+ * encoding new data, or finish a line (72 columns
+ * by default for the plain text serializer).
+ *
+ * @return The document encoded into a string.
+ */
+ AString encodeToStringWithMaxLength(in unsigned long aMaxLength);
+
+ /**
+ * Set the fixup object associated with node persistence.
+ * @param aFixup The fixup object.
+ */
+ void setNodeFixup(in nsIDocumentEncoderNodeFixup aFixup);
+};
diff --git a/dom/base/nsIDocumentInlines.h b/dom/base/nsIDocumentInlines.h
new file mode 100644
index 000000000..4ba21dfe6
--- /dev/null
+++ b/dom/base/nsIDocumentInlines.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 nsIDocumentInlines_h
+#define nsIDocumentInlines_h
+
+#include "nsIDocument.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "nsStyleSheetService.h"
+
+inline mozilla::dom::HTMLBodyElement*
+nsIDocument::GetBodyElement()
+{
+ return static_cast<mozilla::dom::HTMLBodyElement*>(GetHtmlChildElement(nsGkAtoms::body));
+}
+
+template<typename T>
+size_t
+nsIDocument::FindDocStyleSheetInsertionPoint(
+ const nsTArray<RefPtr<T>>& aDocSheets,
+ T* aSheet)
+{
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+
+ // lowest index first
+ int32_t newDocIndex = GetIndexOfStyleSheet(aSheet);
+
+ int32_t count = aDocSheets.Length();
+ int32_t index;
+ for (index = 0; index < count; index++) {
+ T* sheet = aDocSheets[index];
+ int32_t sheetDocIndex = GetIndexOfStyleSheet(sheet);
+ if (sheetDocIndex > newDocIndex)
+ break;
+
+ mozilla::StyleSheet* sheetHandle = sheet;
+
+ // 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(sheetHandle) != authorSheets.NoIndex) {
+ break;
+ }
+ }
+ if (sheetHandle == GetFirstAdditionalAuthorSheet()) {
+ break;
+ }
+ }
+ }
+
+ return size_t(index);
+}
+
+#endif // nsIDocumentInlines_h
diff --git a/dom/base/nsIDocumentObserver.h b/dom/base/nsIDocumentObserver.h
new file mode 100644
index 000000000..ba73dd8cd
--- /dev/null
+++ b/dom/base/nsIDocumentObserver.h
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/EventStates.h"
+#include "mozilla/StyleSheet.h"
+#include "nsISupports.h"
+#include "nsIMutationObserver.h"
+
+class nsIContent;
+class nsIDocument;
+
+namespace mozilla {
+namespace css {
+class Rule;
+} // namespace css
+} // namespace mozilla
+
+#define NS_IDOCUMENT_OBSERVER_IID \
+{ 0x71041fa3, 0x6dd7, 0x4cde, \
+ { 0xbb, 0x76, 0xae, 0xcc, 0x69, 0xe1, 0x75, 0x78 } }
+
+typedef uint32_t nsUpdateType;
+
+#define UPDATE_CONTENT_MODEL 0x00000001
+#define UPDATE_STYLE 0x00000002
+#define UPDATE_ALL (UPDATE_CONTENT_MODEL | UPDATE_STYLE)
+
+// 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(nsIDocument *aDocument,
+ nsUpdateType aUpdateType) = 0;
+
+ /**
+ * Notify that a content model update is finished. This call can be
+ * nested.
+ */
+ virtual void EndUpdate(nsIDocument *aDocument, nsUpdateType aUpdateType) = 0;
+
+ /**
+ * Notify the observer that a document load is beginning.
+ */
+ virtual void BeginLoad(nsIDocument *aDocument) = 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(nsIDocument *aDocument) = 0;
+
+ /**
+ * Notification that the state of a content node has changed.
+ * (ie: gained or lost focus, became active or hovered over)
+ * This method is called automatically by content objects
+ * when their state is changed (therefore there is normally
+ * no need to invoke this method directly). The notification
+ * is passed to any IDocumentObservers. The notification is
+ * passed on to all of the document observers. <p>
+ *
+ * This notification is not sent when a piece of content is
+ * added/removed from the document or the content itself changed
+ * (the other notifications are used for that).
+ *
+ * @param aDocument The document being observed
+ * @param aContent the piece of content that changed
+ */
+ virtual void ContentStateChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ mozilla::EventStates aStateMask) = 0;
+
+ /**
+ * Notification that the state of the document has changed.
+ *
+ * @param aDocument The document being observed
+ * @param aStateMask the state that changed
+ */
+ virtual void DocumentStatesChanged(nsIDocument* aDocument,
+ mozilla::EventStates aStateMask) = 0;
+
+ /**
+ * A StyleSheet has just been added to the document. This method is
+ * called automatically when a StyleSheet gets added to the
+ * document, even if the stylesheet is not applicable. The
+ * notification is passed on to all of the document observers.
+ *
+ * @param aStyleSheet the StyleSheet that has been added
+ * @param aDocumentSheet True if sheet is in document's style sheet list,
+ * false if sheet is not (i.e., UA or user sheet)
+ */
+ virtual void StyleSheetAdded(mozilla::StyleSheet* aStyleSheet,
+ bool aDocumentSheet) = 0;
+
+ /**
+ * A StyleSheet has just been removed from the document. This
+ * method is called automatically when a StyleSheet gets removed
+ * from the document, even if the stylesheet is not applicable. The
+ * notification is passed on to all of the document observers.
+ *
+ * @param aStyleSheet the StyleSheet that has been removed
+ * @param aDocumentSheet True if sheet is in document's style sheet list,
+ * false if sheet is not (i.e., UA or user sheet)
+ */
+ virtual void StyleSheetRemoved(mozilla::StyleSheet* aStyleSheet,
+ bool aDocumentSheet) = 0;
+
+ /**
+ * A StyleSheet has just changed its applicable state.
+ * This method is called automatically when the applicable state
+ * of a StyleSheet gets changed. The style sheet passes this
+ * notification to the document. The notification is passed on
+ * to all of the document observers.
+ *
+ * @param aStyleSheet the StyleSheet that has changed state
+ */
+ virtual void StyleSheetApplicableStateChanged(mozilla::StyleSheet* aStyleSheet) = 0;
+
+ /**
+ * A StyleRule has just been modified within a style sheet.
+ * This method is called automatically when the rule gets
+ * modified. The style sheet passes this notification to
+ * the document. The notification is passed on to all of
+ * the document observers.
+ *
+ * @param aStyleSheet the StyleSheet that contians the rule
+ */
+ virtual void StyleRuleChanged(mozilla::StyleSheet* aStyleSheet) = 0;
+
+ /**
+ * A StyleRule has just been added to a style sheet.
+ * This method is called automatically when the rule gets
+ * added to the sheet. The style sheet passes this
+ * notification to the document. The notification is passed on
+ * to all of the document observers.
+ *
+ * @param aStyleSheet the StyleSheet that has been modified
+ */
+ virtual void StyleRuleAdded(mozilla::StyleSheet* aStyleSheet) = 0;
+
+ /**
+ * A StyleRule has just been removed from a style sheet.
+ * This method is called automatically when the rule gets
+ * removed from the sheet. The style sheet passes this
+ * notification to the document. The notification is passed on
+ * to all of the document observers.
+ *
+ * @param aStyleSheet the StyleSheet that has been modified
+ */
+ virtual void StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumentObserver, NS_IDOCUMENT_OBSERVER_IID)
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_BEGINUPDATE \
+ virtual void BeginUpdate(nsIDocument* aDocument, \
+ nsUpdateType aUpdateType) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_ENDUPDATE \
+ virtual void EndUpdate(nsIDocument* aDocument, nsUpdateType aUpdateType) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_BEGINLOAD \
+ virtual void BeginLoad(nsIDocument* aDocument) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_ENDLOAD \
+ virtual void EndLoad(nsIDocument* aDocument) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_CONTENTSTATECHANGED \
+ virtual void ContentStateChanged(nsIDocument* aDocument, \
+ nsIContent* aContent, \
+ mozilla::EventStates aStateMask) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_DOCUMENTSTATESCHANGED \
+ virtual void DocumentStatesChanged(nsIDocument* aDocument, \
+ mozilla::EventStates aStateMask) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETADDED \
+ virtual void StyleSheetAdded(mozilla::StyleSheet* aStyleSheet, \
+ bool aDocumentSheet) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETREMOVED \
+ virtual void StyleSheetRemoved(mozilla::StyleSheet* aStyleSheet, \
+ bool aDocumentSheet) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETAPPLICABLESTATECHANGED \
+ virtual void StyleSheetApplicableStateChanged( \
+ mozilla::StyleSheet* aStyleSheet) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULECHANGED \
+ virtual void StyleRuleChanged(mozilla::StyleSheet* aStyleSheet) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEADDED \
+ virtual void StyleRuleAdded(mozilla::StyleSheet* aStyleSheet) override;
+
+#define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEREMOVED \
+ virtual void StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet) 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_NSIDOCUMENTOBSERVER_DOCUMENTSTATESCHANGED \
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETADDED \
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETREMOVED \
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLESHEETAPPLICABLESTATECHANGED \
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLERULECHANGED \
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEADDED \
+ NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEREMOVED \
+ NS_DECL_NSIMUTATIONOBSERVER
+
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(_class) \
+void \
+_class::BeginUpdate(nsIDocument* aDocument, nsUpdateType aUpdateType) \
+{ \
+} \
+void \
+_class::EndUpdate(nsIDocument* aDocument, nsUpdateType aUpdateType) \
+{ \
+} \
+NS_IMPL_NSIMUTATIONOBSERVER_CORE_STUB(_class)
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(_class) \
+void \
+_class::BeginLoad(nsIDocument* aDocument) \
+{ \
+} \
+void \
+_class::EndLoad(nsIDocument* aDocument) \
+{ \
+}
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_STATE_STUB(_class) \
+void \
+_class::ContentStateChanged(nsIDocument* aDocument, \
+ nsIContent* aContent, \
+ mozilla::EventStates aStateMask) \
+{ \
+} \
+ \
+void \
+_class::DocumentStatesChanged(nsIDocument* aDocument, \
+ mozilla::EventStates aStateMask) \
+{ \
+}
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_CONTENT(_class) \
+NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(_class)
+
+#define NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(_class) \
+void \
+_class::StyleSheetAdded(mozilla::StyleSheet* aStyleSheet, \
+ bool aDocumentSheet) \
+{ \
+} \
+void \
+_class::StyleSheetRemoved(mozilla::StyleSheet* aStyleSheet, \
+ bool aDocumentSheet) \
+{ \
+} \
+void \
+_class::StyleSheetApplicableStateChanged(mozilla::StyleSheet* aStyleSheet)\
+{ \
+} \
+void \
+_class::StyleRuleChanged(mozilla::StyleSheet* aStyleSheet) \
+{ \
+} \
+void \
+_class::StyleRuleAdded(mozilla::StyleSheet* aStyleSheet) \
+{ \
+} \
+void \
+_class::StyleRuleRemoved(mozilla::StyleSheet* aStyleSheet) \
+{ \
+}
+
+#endif /* nsIDocumentObserver_h___ */
diff --git a/dom/base/nsIDroppedLinkHandler.idl b/dom/base/nsIDroppedLinkHandler.idl
new file mode 100644
index 000000000..9f23b25a7
--- /dev/null
+++ b/dom/base/nsIDroppedLinkHandler.idl
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMDragEvent;
+
+[scriptable, uuid(69E14F91-2E09-4CA6-A511-A715C99A2804)]
+interface nsIDroppedLinkItem : nsISupports
+{
+ /**
+ * Returns the URL of the link.
+ */
+ readonly attribute DOMString url;
+
+ /**
+ * Returns the link name.
+ */
+ readonly attribute DOMString name;
+
+ /**
+ * Returns the MIME-Type.
+ */
+ readonly attribute DOMString 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 nsIDOMDragEvent aEvent, in boolean aAllowSameDocument);
+
+ /**
+ * Given a drop event aEvent, determines the link being dragged and returns
+ * it. If a uri is returned the caller can, for instance, load it. If null
+ * is returned, 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).
+ *
+ * aName is filled in with the link title if it exists, or an empty string
+ * otherwise.
+ */
+ AString dropLink(in nsIDOMDragEvent aEvent, out AString aName,
+ [optional] in boolean aDisallowInherit);
+
+ /**
+ * 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 count of links is 0, 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).
+ */
+ void dropLinks(in nsIDOMDragEvent aEvent,
+ [optional] in boolean aDisallowInherit,
+ [optional] out unsigned long aCount,
+ [retval, array, size_is(aCount)] out nsIDroppedLinkItem aLinks);
+};
diff --git a/dom/base/nsIFrameLoader.idl b/dom/base/nsIFrameLoader.idl
new file mode 100644
index 000000000..d840cdce6
--- /dev/null
+++ b/dom/base/nsIFrameLoader.idl
@@ -0,0 +1,288 @@
+/* -*- 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 mozIApplication;
+interface nsFrameLoader;
+interface nsIDocShell;
+interface nsIURI;
+interface nsIFrame;
+interface nsSubDocumentFrame;
+interface nsIMessageSender;
+interface nsIVariant;
+interface nsIDOMElement;
+interface nsITabParent;
+interface nsILoadContext;
+interface nsIPrintSettings;
+interface nsIWebProgressListener;
+interface nsIGroupedSHistory;
+interface nsIPartialSHistory;
+
+[scriptable, builtinclass, uuid(1645af04-1bc7-4363-8f2c-eb9679220ab1)]
+interface nsIFrameLoader : nsISupports
+{
+ /**
+ * Get the docshell from the frame loader.
+ */
+ readonly attribute nsIDocShell docShell;
+
+ /**
+ * Get this frame loader's TabParent, if it has a remote frame. Otherwise,
+ * returns null.
+ */
+ readonly attribute nsITabParent tabParent;
+
+ /**
+ * Get an nsILoadContext for the top-level docshell. For remote
+ * frames, a shim is returned that contains private browsing and app
+ * information.
+ */
+ readonly attribute nsILoadContext loadContext;
+
+ /**
+ * Start loading the frame. This method figures out what to load
+ * from the owner content in the frame loader.
+ */
+ void loadFrame();
+
+ /**
+ * Loads the specified URI in this frame. Behaves identically to loadFrame,
+ * except that this method allows specifying the URI to load.
+ */
+ void loadURI(in nsIURI aURI);
+
+ /**
+ * Puts the frameloader in prerendering mode.
+ */
+ void setIsPrerendered();
+
+ /**
+ * Make the prerendered frameloader being active (and clear isPrerendered flag).
+ */
+ void makePrerenderedLoaderActive();
+
+ /**
+ * Append partial session history from another frame loader.
+ */
+ void appendPartialSessionHistoryAndSwap(in nsIFrameLoader aOther);
+
+ /**
+ * If grouped session history is applied, use this function to navigate to
+ * an entry of session history object of another frameloader.
+ */
+ void requestGroupedHistoryNavigation(in unsigned long aGlobalIndex);
+
+ /**
+ * Destroy the frame loader and everything inside it. This will
+ * clear the weak owner content reference.
+ */
+ void destroy();
+
+ /**
+ * Find out whether the loader's frame is at too great a depth in
+ * the frame tree. This can be used to decide what operations may
+ * or may not be allowed on the loader's docshell.
+ */
+ readonly attribute boolean depthTooGreat;
+
+ /**
+ * Updates the position and size of the subdocument loaded by this frameloader.
+ *
+ * @param aIFrame The nsIFrame for the content node that owns this frameloader
+ */
+ [noscript] void updatePositionAndSize(in nsSubDocumentFrame aIFrame);
+
+ /**
+ * Activate remote frame.
+ * Throws an exception with non-remote frames.
+ */
+ void activateRemoteFrame();
+
+ /**
+ * Deactivate remote frame.
+ * Throws an exception with non-remote frames.
+ */
+ void deactivateRemoteFrame();
+
+ /**
+ * @see nsIDOMWindowUtils sendMouseEvent.
+ */
+ void sendCrossProcessMouseEvent(in AString aType,
+ in float aX,
+ in float aY,
+ in long aButton,
+ in long aClickCount,
+ in long aModifiers,
+ [optional] in boolean aIgnoreRootScrollFrame);
+
+ /**
+ * Activate event forwarding from client (remote frame) to parent.
+ */
+ void activateFrameEvent(in AString aType, in boolean capture);
+
+ // Note, when frameloaders are swapped, also messageManagers are swapped.
+ readonly attribute nsIMessageSender messageManager;
+
+ /**
+ * @see nsIDOMWindowUtils sendKeyEvent.
+ */
+ void sendCrossProcessKeyEvent(in AString aType,
+ in long aKeyCode,
+ in long aCharCode,
+ in long aModifiers,
+ [optional] in boolean aPreventDefault);
+
+ /**
+ * Request that the next time a remote layer transaction has been
+ * received by the Compositor, a MozAfterRemoteFrame event be sent
+ * to the window.
+ */
+ void requestNotifyAfterRemotePaint();
+
+ /**
+ * Close the window through the ownerElement.
+ */
+ void requestFrameLoaderClose();
+
+ /**
+ * Print the current document.
+ *
+ * @param aOuterWindowID the ID of the outer window to print
+ * @param aPrintSettings optional print settings to use; printSilent can be
+ * set to prevent prompting.
+ * @param aProgressListener optional print progress listener.
+ */
+ void print(in unsigned long long aOuterWindowID,
+ in nsIPrintSettings aPrintSettings,
+ in nsIWebProgressListener aProgressListener);
+
+ /**
+ * The default event mode automatically forwards the events
+ * handled in EventStateManager::HandleCrossProcessEvent to
+ * the child content process when these events are targeted to
+ * the remote browser element.
+ *
+ * Used primarly for input events (mouse, keyboard)
+ */
+ const unsigned long EVENT_MODE_NORMAL_DISPATCH = 0x00000000;
+
+ /**
+ * With this event mode, it's the application's responsability to
+ * convert and forward events to the content process
+ */
+ const unsigned long EVENT_MODE_DONT_FORWARD_TO_CHILD = 0x00000001;
+
+ attribute unsigned long eventMode;
+
+ /**
+ * If false, then the subdocument is not clipped to its CSS viewport, and the
+ * subdocument's viewport scrollbar(s) are not rendered.
+ * Defaults to true.
+ */
+ attribute boolean clipSubdocument;
+
+ /**
+ * If false, then the subdocument's scroll coordinates will not be clamped
+ * to their scroll boundaries.
+ * Defaults to true.
+ */
+ attribute boolean clampScrollPosition;
+
+ /**
+ * The element which owns this frame loader.
+ *
+ * For example, if this is a frame loader for an <iframe>, this attribute
+ * returns the iframe element.
+ */
+ readonly attribute nsIDOMElement ownerElement;
+
+
+ /**
+ * Cached childID of the ContentParent owning the TabParent in this frame
+ * loader. This can be used to obtain the childID after the TabParent died.
+ */
+ readonly attribute unsigned long long childID;
+
+ /**
+ * Get or set this frame loader's visibility.
+ *
+ * The notion of "visibility" here is separate from the notion of a
+ * window/docshell's visibility. This field is mostly here so that we can
+ * have a notion of visibility in the parent process when frames are OOP.
+ */
+ [infallible] attribute boolean visible;
+
+ /**
+ * Find out whether the owner content really is a mozbrowser or app frame
+ * Especially, a widget frame is regarded as an app frame. <xul:browser> is
+ * not considered to be a mozbrowser frame.
+ */
+ readonly attribute boolean ownerIsMozBrowserOrAppFrame;
+
+ /**
+ * The last known width of the frame. Reading this property will not trigger
+ * a reflow, and therefore may not reflect the current state of things. It
+ * should only be used in asynchronous APIs where values are not guaranteed
+ * to be up-to-date when received.
+ */
+ readonly attribute unsigned long lazyWidth;
+
+ /**
+ * The last known height of the frame. Reading this property will not trigger
+ * a reflow, and therefore may not reflect the current state of things. It
+ * should only be used in asynchronous APIs where values are not guaranteed
+ * to be up-to-date when received.
+ */
+ readonly attribute unsigned long lazyHeight;
+
+ /**
+ * The partial session history.
+ */
+ readonly attribute nsIPartialSHistory partialSessionHistory;
+
+ /**
+ * The grouped session history composed of multiple session history objects
+ * across root docshells.
+ */
+ readonly attribute nsIGroupedSHistory groupedSessionHistory;
+};
+
+%{C++
+class nsFrameLoader;
+%}
+
+native alreadyAddRefed_nsFrameLoader(already_AddRefed<nsFrameLoader>);
+
+[scriptable, uuid(adc1b3ba-8deb-4943-8045-e6de0044f2ce)]
+interface nsIFrameLoaderOwner : nsISupports
+{
+ /**
+ * The frame loader owned by this nsIFrameLoaderOwner
+ */
+ [binaryname(FrameLoaderXPCOM)] readonly attribute nsIFrameLoader frameLoader;
+ [noscript, notxpcom] alreadyAddRefed_nsFrameLoader GetFrameLoader();
+
+ /**
+ * The principal of parent mozIApplication in case of nested mozbrowser
+ * iframes.
+ */
+ readonly attribute mozIApplication parentApplication;
+
+ /**
+ * Puts the FrameLoaderOwner in prerendering mode.
+ */
+ void setIsPrerendered();
+
+ /**
+ * This method is used internally by SwapFrameLoaders to set the frame loader
+ * on the target nsFrameLoader.
+ *
+ * Avoid using this method outside of that context, and instead prefer using
+ * SwapFrameLoaders.
+ */
+ [noscript, notxpcom] void
+ internalSetFrameLoader(in nsIFrameLoader aNewFrameLoader);
+};
diff --git a/dom/base/nsIGlobalObject.cpp b/dom/base/nsIGlobalObject.cpp
new file mode 100644
index 000000000..6479c947b
--- /dev/null
+++ b/dom/base/nsIGlobalObject.cpp
@@ -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/. */
+
+#include "nsIGlobalObject.h"
+#include "nsContentUtils.h"
+#include "nsThreadUtils.h"
+#include "nsHostObjectProtocolHandler.h"
+
+nsIGlobalObject::~nsIGlobalObject()
+{
+ UnlinkHostObjectURIs();
+}
+
+nsIPrincipal*
+nsIGlobalObject::PrincipalOrNull()
+{
+ JSObject *global = GetGlobalJSObject();
+ 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)
+ {
+ mURIs.SwapElements(aURIs);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (uint32_t index = 0; index < mURIs.Length(); ++index) {
+ nsHostObjectProtocolHandler::RemoveDataEntry(mURIs[index]);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ ~UnlinkHostObjectURIsRunnable() {}
+
+ nsTArray<nsCString> mURIs;
+};
+
+} // namespace
+
+void
+nsIGlobalObject::UnlinkHostObjectURIs()
+{
+ if (mHostObjectURIs.IsEmpty()) {
+ return;
+ }
+
+ if (NS_IsMainThread()) {
+ for (uint32_t index = 0; index < mHostObjectURIs.Length(); ++index) {
+ nsHostObjectProtocolHandler::RemoveDataEntry(mHostObjectURIs[index]);
+ }
+
+ mHostObjectURIs.Clear();
+ return;
+ }
+
+ // nsHostObjectProtocolHandler is main-thread only.
+
+ RefPtr<UnlinkHostObjectURIsRunnable> runnable =
+ new UnlinkHostObjectURIsRunnable(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.");
+ }
+}
+
+void
+nsIGlobalObject::TraverseHostObjectURIs(nsCycleCollectionTraversalCallback &aCb)
+{
+ if (mHostObjectURIs.IsEmpty()) {
+ return;
+ }
+
+ // Currently we only store BlobImpl objects off the the main-thread and they
+ // are not CCed.
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ for (uint32_t index = 0; index < mHostObjectURIs.Length(); ++index) {
+ nsHostObjectProtocolHandler::Traverse(mHostObjectURIs[index], aCb);
+ }
+}
diff --git a/dom/base/nsIGlobalObject.h b/dom/base/nsIGlobalObject.h
new file mode 100644
index 000000000..f94e5c8d0
--- /dev/null
+++ b/dom/base/nsIGlobalObject.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 nsIGlobalObject_h__
+#define nsIGlobalObject_h__
+
+#include "nsISupports.h"
+#include "nsTArray.h"
+#include "js/TypeDecls.h"
+
+#define NS_IGLOBALOBJECT_IID \
+{ 0x11afa8be, 0xd997, 0x4e07, \
+{ 0xa6, 0xa3, 0x6f, 0x87, 0x2e, 0xc3, 0xee, 0x7f } }
+
+class nsACString;
+class nsCString;
+class nsCycleCollectionTraversalCallback;
+class nsIPrincipal;
+
+class nsIGlobalObject : public nsISupports
+{
+ nsTArray<nsCString> mHostObjectURIs;
+ bool mIsDying;
+
+protected:
+ nsIGlobalObject()
+ : mIsDying(false)
+ {}
+
+public:
+ 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;
+ }
+
+ // GetGlobalJSObject may return a gray object. If this ever changes so that
+ // it stops doing that, please simplify the code in FindAssociatedGlobal in
+ // BindingUtils.h that does JS::ExposeObjectToActiveJS on the return value of
+ // GetGlobalJSObject. Also, in that case the JS::ExposeObjectToActiveJS in
+ // AutoJSAPI::InitInternal can probably be removed. And also the similar
+ // calls in XrayWrapper and nsGlobalWindow.
+ virtual JSObject* GetGlobalJSObject() = 0;
+
+ // This method is not meant to be overridden.
+ nsIPrincipal* PrincipalOrNull();
+
+ void RegisterHostObjectURI(const nsACString& aURI);
+
+ void UnregisterHostObjectURI(const nsACString& aURI);
+
+ // Any CC class inheriting nsIGlobalObject should call these 2 methods if it
+ // exposes the URL API.
+ void UnlinkHostObjectURIs();
+ void TraverseHostObjectURIs(nsCycleCollectionTraversalCallback &aCb);
+
+protected:
+ virtual ~nsIGlobalObject();
+
+ void
+ StartDying()
+ {
+ mIsDying = true;
+ }
+};
+
+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 000000000..fea261a34
--- /dev/null
+++ b/dom/base/nsIImageLoadingContent.idl
@@ -0,0 +1,193 @@
+/* -*- 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 nsIDocument;
+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
+ * interface to mirror this interface when changing it.
+ */
+
+[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;
+
+ /**
+ * loadingEnabled 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.
+ */
+ attribute boolean loadingEnabled;
+
+ /**
+ * Returns the image blocking status (@see nsIContentPolicy). This
+ * will always be an nsIContentPolicy REJECT_* status for cases when
+ * the image was blocked. This status always refers to the
+ * CURRENT_REQUEST load.
+ */
+ readonly attribute short imageBlockingStatus;
+
+ /**
+ * 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
+ *
+ * @throws NS_ERROR_OUT_OF_MEMORY
+ */
+ void addObserver(in imgINotificationObserver aObserver);
+
+ /**
+ * Used to unregister an image decoder observer.
+ *
+ * @param aObserver the observer to unregister
+ */
+ void removeObserver(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
+ */
+ imgIRequest getRequest(in long aRequestType);
+
+ /**
+ * @return true if the current request's size is available.
+ */
+ [noscript, notxpcom] boolean currentRequestHasSize();
+
+ /**
+ * 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
+ */
+ 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.
+ */
+ readonly attribute nsIURI currentURI;
+
+ /**
+ * 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
+ */
+ nsIStreamListener loadImageWithChannel(in nsIChannel aChannel);
+
+ /**
+ * forceReload forces reloading of the image pointed to by currentURI
+ *
+ * @param aNotify [optional] request should notify, defaults to true
+ * @throws NS_ERROR_NOT_AVAILABLE if there is no current URI to reload
+ */
+ [optional_argc] void forceReload([optional] in boolean aNotify /* = true */);
+
+ /**
+ * Enables/disables image state forcing. When |aForce| is PR_TRUE, we force
+ * nsImageLoadingContent::ImageState() to return |aState|. Call again with |aForce|
+ * as PR_FALSE to revert ImageState() to its original behaviour.
+ */
+ void forceImageState(in boolean aForce, in unsigned long long aState);
+
+ /**
+ * The intrinsic size and width of this content. May differ from actual image
+ * size due to things like responsive image density.
+ */
+ readonly attribute unsigned long naturalWidth;
+ readonly attribute unsigned long naturalHeight;
+
+ /**
+ * 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 000000000..7c64bd222
--- /dev/null
+++ b/dom/base/nsIMessageManager.idl
@@ -0,0 +1,550 @@
+/* -*- 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 mozIDOMWindowProxy;
+interface nsIDocShell;
+interface nsIContent;
+interface nsIFrameLoader;
+interface nsIPrincipal;
+
+/**
+ * Message managers provide a way for chrome-privileged JS code to
+ * communicate with each other, even across process boundaries.
+ *
+ * Message managers are separated into "parent side" and "child side".
+ * These don't always correspond to process boundaries, but can. For
+ * each child-side message manager, there is always exactly one
+ * corresponding parent-side message manager that it sends messages
+ * to. However, for each parent-side message manager, there may be
+ * either one or many child-side managers it can message.
+ *
+ * Message managers that always have exactly one "other side" are of
+ * type nsIMessageSender. Parent-side message managers that have many
+ * "other sides" are of type nsIMessageBroadcaster.
+ *
+ * Child-side message managers can send synchronous messages to their
+ * parent side, but not the other way around.
+ *
+ * There are two realms of message manager hierarchies. One realm
+ * approximately corresponds to DOM elements, the other corresponds to
+ * process boundaries.
+ *
+ * Message managers corresponding to DOM elements
+ * ==============================================
+ *
+ * In this realm of message managers, there are
+ * - "frame message managers" which correspond to frame elements
+ * - "window message managers" which correspond to top-level chrome
+ * windows
+ * - "group message managers" which correspond to named message
+ * managers with a specific window MM as the parent
+ * - the "global message manager", on the parent side. See below.
+ *
+ * The DOM-realm message managers can communicate in the ways shown by
+ * the following diagram. The parent side and child side can
+ * correspond to process boundaries, but don't always.
+ *
+ * Parent side Child side
+ * ------------- ------------
+ * global MMg
+ * |
+ * +-->window MMw1
+ * | |
+ * | +-->frame MMp1_1<------------>frame MMc1_1
+ * | |
+ * | +-->frame MMp1_2<------------>frame MMc1_2
+ * | |
+ * | +-->group MMgr1
+ * | | |
+ * | | +-->frame MMp2_1<------->frame MMc2_1
+ * | | |
+ * | | +-->frame MMp2_2<------->frame MMc2_2
+ * | |
+ * | +-->group MMgr2
+ * | | ...
+ * | |
+ * | ...
+ * |
+ * +-->window MMw2
+ * ...
+ *
+ * For example: a message sent from MMc1_1, from the child side, is
+ * sent only to MMp1_1 on the parent side. However, note that all
+ * message managers in the hierarchy above MMp1_1, in this diagram
+ * MMw1 and MMg, will also notify their message listeners when the
+ * message arrives.
+ *
+ * A message sent from MMc2_1 will be sent to MMp2_1 and also notify
+ * all message managers in the hierarchy above that, including the
+ * group message manager MMgr1.
+
+ * For example: a message broadcast through the global MMg on the
+ * parent side would be broadcast to MMw1, which would transitively
+ * broadcast it to MMp1_1, MM1p_2. The message would next be
+ * broadcast to MMgr1, which would broadcast it to MMp2_1 and MMp2_2.
+ * After that it would broadcast to MMgr2 and then to MMw2, and so
+ * on down the hierarchy.
+ *
+ * ***** PERFORMANCE AND SECURITY WARNING *****
+ * Messages broadcast through the global MM and window or group MMs
+ * can result in messages being dispatched across many OS processes,
+ * and to many processes with different permissions. Great care
+ * should be taken when broadcasting.
+ *
+ * Interfaces
+ * ----------
+ *
+ * The global MMg and window MMw's are message broadcasters implementing
+ * nsIMessageBroadcaster while the frame MMp's are simple message senders
+ * (nsIMessageSender). Their counterparts in the content processes are
+ * message senders implementing nsIContentFrameMessageManager.
+ *
+ * nsIMessageListenerManager
+ * / \
+ * nsIMessageSender nsIMessageBroadcaster
+ * |
+ * nsISyncMessageSender (content process/in-process only)
+ * |
+ * nsIContentFrameMessageManager (content process/in-process only)
+ * |
+ * nsIInProcessContentFrameMessageManager (in-process only)
+ *
+ *
+ * Message managers in the chrome process can also be QI'ed to nsIFrameScriptLoader.
+ *
+ *
+ * Message managers corresponding to process boundaries
+ * ====================================================
+ *
+ * The second realm of message managers is the "process message
+ * managers". With one exception, these always correspond to process
+ * boundaries. The picture looks like
+ *
+ * Parent process Child processes
+ * ---------------- -----------------
+ * global (GPPMM)
+ * |
+ * +-->parent in-process PIPMM<-->child in-process CIPPMM
+ * |
+ * +-->parent (PPMM1)<------------------>child (CPMM1)
+ * |
+ * +-->parent (PPMM2)<------------------>child (CPMM2)
+ * ...
+ *
+ * Note, PIPMM and CIPPMM both run in the parent process.
+ *
+ * For example: the parent-process PPMM1 sends messages to the
+ * child-process CPMM1.
+ *
+ * For example: CPMM1 sends messages directly to PPMM1. The global GPPMM
+ * will also notify their message listeners when the message arrives.
+ *
+ * For example: messages sent through the global GPPMM will be
+ * dispatched to the listeners of the same-process, CIPPMM, CPMM1,
+ * CPMM2, etc.
+ *
+ * ***** PERFORMANCE AND SECURITY WARNING *****
+ * Messages broadcast through the GPPMM can result in messages
+ * being dispatched across many OS processes, and to many processes
+ * with different permissions. Great care should be taken when
+ * broadcasting.
+ *
+ * Requests sent to parent-process message listeners should usually
+ * have replies scoped to the requesting CPMM. The following pattern
+ * is common
+ *
+ * const ParentProcessListener = {
+ * receiveMessage: function(aMessage) {
+ * let childMM = aMessage.target.QueryInterface(Ci.nsIMessageSender);
+ * switch (aMessage.name) {
+ * case "Foo:Request":
+ * // service request
+ * childMM.sendAsyncMessage("Foo:Response", { data });
+ * }
+ * }
+ * };
+ */
+
+[scriptable, function, uuid(2b44eb57-a9c6-4773-9a1e-fe0818739a4c)]
+interface nsIMessageListener : nsISupports
+{
+ /**
+ * This is for JS only.
+ * receiveMessage is called with one parameter, which has the following
+ * properties:
+ * {
+ * target: %the target of the message. Either an element owning
+ * the message manager, or message manager itself if no
+ * element owns it%
+ * name: %message name%,
+ * sync: %true or false%.
+ * data: %structured clone of the sent message data%,
+ * json: %same as .data, deprecated%,
+ * objects: %named table of jsvals/objects, or null%
+ * principal: %principal for the window app
+ * }
+ *
+ * Each listener is invoked with its own copy of the message
+ * parameter.
+ *
+ * When the listener is called, 'this' value is the target of the message.
+ *
+ * If the message is synchronous, the possible return value is
+ * returned as JSON (will be changed to use structured clones).
+ * When there are multiple listeners to sync messages, each
+ * listener's return value is sent back as an array. |undefined|
+ * return values show up as undefined values in the array.
+ */
+ void receiveMessage();
+};
+
+[scriptable, builtinclass, uuid(b949bfec-bb7d-47bc-b387-ac6a9b655072)]
+interface nsIMessageListenerManager : nsISupports
+{
+ /**
+ * Register |listener| to receive |messageName|. All listener
+ * callbacks for a particular message are invoked when that message
+ * is received.
+ *
+ * The message manager holds a strong ref to |listener|.
+ *
+ * If the same listener registers twice for the same message, the
+ * second registration is ignored.
+ *
+ * Pass true for listenWhenClosed if you want to receive messages
+ * during the short period after a frame has been removed from the
+ * DOM and before its frame script has finished unloading. This
+ * parameter only has an effect for frame message managers in
+ * the main process. Default is false.
+ */
+ void addMessageListener(in AString messageName,
+ in nsIMessageListener listener,
+ [optional] in boolean listenWhenClosed);
+
+ /**
+ * Undo an |addMessageListener| call -- that is, calling this causes us to no
+ * longer invoke |listener| when |messageName| is received.
+ *
+ * removeMessageListener does not remove a message listener added via
+ * addWeakMessageListener; use removeWeakMessageListener for that.
+ */
+ void removeMessageListener(in AString messageName,
+ in nsIMessageListener listener);
+
+ /**
+ * This is just like addMessageListener, except the message manager holds a
+ * weak ref to |listener|.
+ *
+ * If you have two weak message listeners for the same message, they may be
+ * called in any order.
+ */
+ void addWeakMessageListener(in AString messageName,
+ in nsIMessageListener listener);
+
+ /**
+ * This undoes an |addWeakMessageListener| call.
+ */
+ void removeWeakMessageListener(in AString messageName,
+ in nsIMessageListener listener);
+
+ [notxpcom] boolean markForCC();
+};
+
+/**
+ * Message "senders" have a single "other side" to which messages are
+ * sent. For example, a child-process message manager will send
+ * messages that are only delivered to its one parent-process message
+ * manager.
+ */
+[scriptable, builtinclass, uuid(bb5d79e4-e73c-45e7-9651-4d718f4b994c)]
+interface nsIMessageSender : nsIMessageListenerManager
+{
+ /**
+ * Send |messageName| and |obj| to the "other side" of this message
+ * manager. This invokes listeners who registered for
+ * |messageName|.
+ *
+ * See nsIMessageListener::receiveMessage() for the format of the
+ * data delivered to listeners.
+ * @throws NS_ERROR_NOT_INITIALIZED if the sender is not initialized. For
+ * example, we will throw NS_ERROR_NOT_INITIALIZED if we try to send
+ * a message to a cross-process frame but the other process has not
+ * yet been set up.
+ * @throws NS_ERROR_FAILURE when the message receiver cannot be found. For
+ * example, we will throw NS_ERROR_FAILURE if we try to send a message
+ * to a cross-process frame whose process has crashed.
+ */
+ [implicit_jscontext, optional_argc]
+ void sendAsyncMessage([optional] in AString messageName,
+ [optional] in jsval obj,
+ [optional] in jsval objects,
+ [optional] in nsIPrincipal principal,
+ [optional] in jsval transfers);
+
+ /**
+ * For remote browsers there is always a corresponding process message
+ * manager. The intention of this attribute is to link leaf level frame
+ * message managers on the parent side with the corresponding process
+ * message managers (if there is one). For any other cases this property
+ * is null.
+ */
+ readonly attribute nsIMessageSender processMessageManager;
+};
+
+/**
+ * Message "broadcasters" don't have a single "other side" that they
+ * send messages to, but rather a set of subordinate message managers.
+ * For example, broadcasting a message through a window message
+ * manager will broadcast the message to all frame message managers
+ * within its window.
+ */
+[scriptable, builtinclass, uuid(4d7d62ad-4725-4f39-86cf-8fb22bf9c1d8)]
+interface nsIMessageBroadcaster : nsIMessageListenerManager
+{
+ /**
+ * Like |sendAsyncMessage()|, but also broadcasts this message to
+ * all "child" message managers of this message manager. See long
+ * comment above for details.
+ *
+ * WARNING: broadcasting messages can be very expensive and leak
+ * sensitive data. Use with extreme caution.
+ */
+ [implicit_jscontext, optional_argc]
+ void broadcastAsyncMessage([optional] in AString messageName,
+ [optional] in jsval obj,
+ [optional] in jsval objects);
+
+ /**
+ * Number of subordinate message managers.
+ */
+ readonly attribute unsigned long childCount;
+
+ /**
+ * Return a single subordinate message manager.
+ */
+ nsIMessageListenerManager getChildAt(in unsigned long aIndex);
+};
+
+[scriptable, builtinclass, uuid(0e602c9e-1977-422a-a8e4-fe0d4a4f78d0)]
+interface nsISyncMessageSender : nsIMessageSender
+{
+ /**
+ * Like |sendAsyncMessage()|, except blocks the sender until all
+ * listeners of the message have been invoked. Returns an array
+ * containing return values from each listener invoked.
+ */
+ [implicit_jscontext, optional_argc]
+ jsval sendSyncMessage([optional] in AString messageName,
+ [optional] in jsval obj,
+ [optional] in jsval objects,
+ [optional] in nsIPrincipal principal);
+
+ /**
+ * Like |sendSyncMessage()|, except re-entrant. New RPC messages may be
+ * issued even if, earlier on the call stack, we are waiting for a reply
+ * to an earlier sendRpcMessage() call.
+ *
+ * Both sendSyncMessage and sendRpcMessage will block until a reply is
+ * received, but they may be temporarily interrupted to process an urgent
+ * incoming message (such as a CPOW request).
+ */
+ [implicit_jscontext, optional_argc]
+ jsval sendRpcMessage([optional] in AString messageName,
+ [optional] in jsval obj,
+ [optional] in jsval objects,
+ [optional] in nsIPrincipal principal);
+};
+
+[scriptable, builtinclass, uuid(13f3555f-769e-44ea-b607-5239230c3162)]
+interface nsIMessageManagerGlobal : nsISyncMessageSender
+{
+ /**
+ * Print a string to stdout.
+ */
+ void dump(in DOMString aStr);
+
+ /**
+ * If leak detection is enabled, print a note to the leak log that this
+ * process will intentionally crash.
+ */
+ void privateNoteIntentionalCrash();
+
+ /**
+ * Ascii base64 data to binary data and vice versa
+ */
+ DOMString atob(in DOMString aAsciiString);
+ DOMString btoa(in DOMString aBase64Data);
+};
+
+[scriptable, builtinclass, uuid(694e367c-aa25-4446-8499-2c527c4bd838)]
+interface nsIContentFrameMessageManager : nsIMessageManagerGlobal
+{
+ /**
+ * The current top level window in the frame or null.
+ */
+ readonly attribute mozIDOMWindowProxy content;
+
+ /**
+ * The top level docshell or null.
+ */
+ readonly attribute nsIDocShell docShell;
+};
+
+[uuid(b39a3324-b574-4f85-8cdb-274d04f807ef)]
+interface nsIInProcessContentFrameMessageManager : nsIContentFrameMessageManager
+{
+ [notxpcom] nsIContent getOwnerContent();
+ [notxpcom] void cacheFrameLoader(in nsIFrameLoader aFrameLoader);
+};
+
+[scriptable, builtinclass, uuid(6d12e467-2446-46db-9965-e4e93cb87ca5)]
+interface nsIContentProcessMessageManager : nsIMessageManagerGlobal
+{
+ /**
+ * Read out a copy of the object that was initialized in the parent
+ * process via nsIProcessScriptLoader.initialProcessData.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval initialProcessData;
+};
+
+[scriptable, builtinclass, uuid(bf61446b-ba24-4b1d-88c7-4f94724b9ce1)]
+interface nsIFrameScriptLoader : nsISupports
+{
+ /**
+ * Load a script in the (remote) frame. aURL must be the absolute URL.
+ * data: URLs are also supported. For example data:,dump("foo\n");
+ * If aAllowDelayedLoad is true, script will be loaded when the
+ * remote frame becomes available. Otherwise the script will be loaded
+ * only if the frame is already available.
+ */
+ void loadFrameScript(in AString aURL, in boolean aAllowDelayedLoad,
+ [optional] in boolean aRunInGlobalScope);
+
+ /**
+ * Removes aURL from the list of scripts which support delayed load.
+ */
+ void removeDelayedFrameScript(in AString aURL);
+
+ /**
+ * Returns all delayed scripts that will be loaded once a (remote)
+ * frame becomes available. The return value is a list of pairs
+ * [<URL>, <WasLoadedInGlobalScope>].
+ */
+ [implicit_jscontext]
+ jsval getDelayedFrameScripts();
+};
+
+[scriptable, builtinclass, uuid(7e1e1a20-b24f-11e4-ab27-0800200c9a66)]
+interface nsIProcessScriptLoader : nsISupports
+{
+ /**
+ * Load a script in the (possibly remote) process. aURL must be the absolute URL.
+ * data: URLs are also supported. For example data:,dump("foo\n");
+ * If aAllowDelayedLoad is true, script will be loaded when the
+ * remote frame becomes available. Otherwise the script will be loaded
+ * only if the frame is already available.
+ */
+ void loadProcessScript(in AString aURL, in boolean aAllowDelayedLoad);
+
+ /**
+ * Removes aURL from the list of scripts which support delayed load.
+ */
+ void removeDelayedProcessScript(in AString aURL);
+
+ /**
+ * Returns all delayed scripts that will be loaded once a (remote)
+ * frame becomes available. The return value is a list of URLs.
+ */
+ [implicit_jscontext]
+ jsval getDelayedProcessScripts();
+};
+
+[scriptable, builtinclass, uuid(5b390753-abb3-49b0-ae3b-b803dab58144)]
+interface nsIGlobalProcessScriptLoader : nsIProcessScriptLoader
+{
+ /**
+ * Allows the parent process to set the initial process data for
+ * new, not-yet-created child processes. This attribute should only
+ * be used by the global parent process message manager. When a new
+ * process is created, it gets a copy of this data (via structured
+ * cloning). It can access the data via the initialProcessData
+ * attribute of its childprocessmessagemanager.
+ *
+ * This value will always be a JS object. Different users are
+ * expected to set properties on this object. The property name
+ * should be unique enough that other Gecko consumers won't
+ * accidentally choose it.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval initialProcessData;
+};
+
+[scriptable, builtinclass, uuid(637e8538-4f8f-4a3d-8510-e74386233e19)]
+interface nsIProcessChecker : nsISupports
+{
+ bool killChild();
+
+ /**
+ * Return true if the "remote" process has |aPermission|. This is
+ * intended to be used by JS implementations of cross-process DOM
+ * APIs, like so
+ *
+ * recvFooRequest: function(message) {
+ * if (!message.target.assertPermission("foo")) {
+ * return false;
+ * }
+ * // service foo request
+ *
+ * This interface only returns meaningful data when our content is
+ * in a separate process. If it shares the same OS process as us,
+ * then applying this permission check doesn't add any security,
+ * though it doesn't hurt anything either.
+ *
+ * Note: If the remote content process does *not* have |aPermission|,
+ * it will be killed as a precaution.
+ */
+ boolean assertPermission(in DOMString aPermission);
+
+ /**
+ * Return true if the "remote" process has |aManifestURL|. This is
+ * intended to be used by JS implementations of cross-process DOM
+ * APIs, like so
+ *
+ * recvFooRequest: function(message) {
+ * if (!message.target.assertContainApp("foo")) {
+ * return false;
+ * }
+ * // service foo request
+ *
+ * This interface only returns meaningful data when our content is
+ * in a separate process. If it shares the same OS process as us,
+ * then applying this manifest URL check doesn't add any security,
+ * though it doesn't hurt anything either.
+ *
+ * Note: If the remote content process does *not* contain |aManifestURL|,
+ * it will be killed as a precaution.
+ */
+ boolean assertContainApp(in DOMString aManifestURL);
+
+ boolean assertAppHasPermission(in DOMString aPermission);
+
+ /**
+ * Return true if the "remote" process' principal has an appStatus equal to
+ * |aStatus|.
+ *
+ * This interface only returns meaningful data when our content is
+ * in a separate process. If it shares the same OS process as us,
+ * then applying this permission check doesn't add any security,
+ * though it doesn't hurt anything either.
+ *
+ * Note: If the remote content process does *not* has the |aStatus|,
+ * it will be killed as a precaution.
+ */
+ boolean assertAppHasStatus(in unsigned short aStatus);
+
+};
diff --git a/dom/base/nsIMutationObserver.h b/dom/base/nsIMutationObserver.h
new file mode 100644
index 000000000..d034dfd6a
--- /dev/null
+++ b/dom/base/nsIMutationObserver.h
@@ -0,0 +1,482 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+
+class nsAttrValue;
+class nsIAtom;
+class nsIContent;
+class nsIDocument;
+class nsINode;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+#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 or append, this is equal to
+ * mChangeStart.
+ */
+ uint32_t mChangeEnd;
+
+ /**
+ * 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.
+ *
+ * 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
+ * expection 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
+{
+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 aDocument The owner-document of aContent. Can be null.
+ * @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(nsIDocument *aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo) = 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 aDocument The owner-document of aContent. Can be null.
+ * @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(nsIDocument *aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo) = 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 aDocument The owner-document of aContent. Can be null.
+ * @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
+ * nsIDOMMutationEvent.h.
+ * @param aNewValue The new value, IF it has been preparsed by
+ * BeforeSetAttr, otherwise null.
+ *
+ * @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(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue) = 0;
+
+ /**
+ * Notification that an attribute of an element has changed.
+ *
+ * @param aDocument The owner-document of aContent. Can be null.
+ * @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
+ * nsIDOMMutationEvent.h.
+ * @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(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) = 0;
+
+ /**
+ * Notification that the root of a native anonymous has been added
+ * or removed.
+ *
+ * @param aDocument Owner doc of aContent
+ * @param aContent Anonymous node that's been added or removed
+ * @param aIsRemove True if it's a removal, false if an addition
+ */
+ virtual void NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove) {}
+
+ /**
+ * Notification that an attribute of an element has been
+ * set to the value it already had.
+ *
+ * @param aDocument The owner-document of aContent.
+ * @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(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute) {}
+
+ /**
+ * Notification that one or more content nodes have been appended to the
+ * child list of another node in the tree.
+ *
+ * @param aDocument The owner-document of aContent. Can be null.
+ * @param aContainer The container that had new children appended. Is never
+ * null.
+ * @param aFirstNewContent the node at aIndexInContainer in aContainer.
+ * @param aNewIndexInContainer the index in the container of the first
+ * new 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 ContentAppended(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer) = 0;
+
+ /**
+ * Notification that a content node has been inserted as child to another
+ * node in the tree.
+ *
+ * @param aDocument The owner-document of aContent, or, when aContainer
+ * is null, the container that had the child inserted.
+ * Can be null.
+ * @param aContainer The container that had new a child inserted. Can be
+ * null to indicate that the child was inserted into
+ * aDocument
+ * @param aChild The newly inserted child.
+ * @param aIndexInContainer The index in the container of the new 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(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer) = 0;
+
+ /**
+ * Notification that a content node has been removed from the child list of
+ * another node in the tree.
+ *
+ * @param aDocument The owner-document of aContent, or, when aContainer
+ * is null, the container that had the child removed.
+ * Can be null.
+ * @param aContainer The container that had new a child removed. Can be
+ * null to indicate that the child was removed from
+ * aDocument.
+ * @param aChild The child that was removed.
+ * @param aIndexInContainer The index in the container which the child used
+ * to have.
+ * @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(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ 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 IsNodeOfType(eCONTENT) 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(const 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;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID)
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE \
+ virtual void CharacterDataWillChange(nsIDocument* aDocument, \
+ nsIContent* aContent, \
+ CharacterDataChangeInfo* aInfo) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED \
+ virtual void CharacterDataChanged(nsIDocument* aDocument, \
+ nsIContent* aContent, \
+ CharacterDataChangeInfo* aInfo) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE \
+ virtual void AttributeWillChange(nsIDocument* aDocument, \
+ mozilla::dom::Element* aElement, \
+ int32_t aNameSpaceID, \
+ nsIAtom* aAttribute, \
+ int32_t aModType, \
+ const nsAttrValue* aNewValue) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE \
+ virtual void NativeAnonymousChildListChange(nsIDocument* aDocument, \
+ nsIContent* aContent, \
+ bool aIsRemove) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
+ virtual void AttributeChanged(nsIDocument* aDocument, \
+ mozilla::dom::Element* aElement, \
+ int32_t aNameSpaceID, \
+ nsIAtom* aAttribute, \
+ int32_t aModType, \
+ const nsAttrValue* aOldValue) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED \
+ virtual void ContentAppended(nsIDocument* aDocument, \
+ nsIContent* aContainer, \
+ nsIContent* aFirstNewContent, \
+ int32_t aNewIndexInContainer) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED \
+ virtual void ContentInserted(nsIDocument* aDocument, \
+ nsIContent* aContainer, \
+ nsIContent* aChild, \
+ int32_t aIndexInContainer) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED \
+ virtual void ContentRemoved(nsIDocument* aDocument, \
+ nsIContent* aContainer, \
+ nsIContent* aChild, \
+ int32_t aIndexInContainer, \
+ nsIContent* aPreviousSibling) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED \
+ virtual void NodeWillBeDestroyed(const nsINode* aNode) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED \
+ virtual void ParentChainChanged(nsIContent *aContent) override;
+
+#define NS_DECL_NSIMUTATIONOBSERVER \
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE \
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED \
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE \
+ NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE \
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED \
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED \
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED \
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED \
+ NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
+
+#define NS_IMPL_NSIMUTATIONOBSERVER_CORE_STUB(_class) \
+void \
+_class::NodeWillBeDestroyed(const nsINode* aNode) \
+{ \
+}
+
+#define NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(_class) \
+void \
+_class::CharacterDataWillChange(nsIDocument* aDocument, \
+ nsIContent* aContent, \
+ CharacterDataChangeInfo* aInfo) \
+{ \
+} \
+void \
+_class::CharacterDataChanged(nsIDocument* aDocument, \
+ nsIContent* aContent, \
+ CharacterDataChangeInfo* aInfo) \
+{ \
+} \
+void \
+_class::AttributeWillChange(nsIDocument* aDocument, \
+ mozilla::dom::Element* aElement, \
+ int32_t aNameSpaceID, \
+ nsIAtom* aAttribute, \
+ int32_t aModType, \
+ const nsAttrValue* aNewValue) \
+{ \
+} \
+void \
+_class::NativeAnonymousChildListChange(nsIDocument* aDocument, \
+ nsIContent* aContent, \
+ bool aIsRemove) \
+{ \
+} \
+void \
+_class::AttributeChanged(nsIDocument* aDocument, \
+ mozilla::dom::Element* aElement, \
+ int32_t aNameSpaceID, \
+ nsIAtom* aAttribute, \
+ int32_t aModType, \
+ const nsAttrValue* aOldValue) \
+{ \
+} \
+void \
+_class::ContentAppended(nsIDocument* aDocument, \
+ nsIContent* aContainer, \
+ nsIContent* aFirstNewContent, \
+ int32_t aNewIndexInContainer) \
+{ \
+} \
+void \
+_class::ContentInserted(nsIDocument* aDocument, \
+ nsIContent* aContainer, \
+ nsIContent* aChild, \
+ int32_t aIndexInContainer) \
+{ \
+} \
+void \
+_class::ContentRemoved(nsIDocument* aDocument, \
+ nsIContent* aContainer, \
+ nsIContent* aChild, \
+ int32_t aIndexInContainer, \
+ nsIContent* aPreviousSibling) \
+{ \
+} \
+void \
+_class::ParentChainChanged(nsIContent *aContent) \
+{ \
+}
+
+
+#endif /* nsIMutationObserver_h */
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
new file mode 100644
index 000000000..3a649a61d
--- /dev/null
+++ b/dom/base/nsINode.cpp
@@ -0,0 +1,3111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozAutoDocUpdate.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/css/StyleRule.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "nsAttrValueOrString.h"
+#include "nsBindingManager.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDocument.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 "nsIAtom.h"
+#include "nsIBaseWindow.h"
+#include "nsICategoryManager.h"
+#include "nsIContentIterator.h"
+#include "nsIControllers.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMDocumentType.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsIDOMNodeList.h"
+#include "nsIEditor.h"
+#include "nsIEditorIMESupport.h"
+#include "nsILinkHandler.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/NodeInfoInlines.h"
+#include "nsIPresShell.h"
+#include "nsIScriptError.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScrollableFrame.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIWebNavigation.h"
+#include "nsIWidget.h"
+#include "nsLayoutUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsNodeInfoManager.h"
+#include "nsNodeUtils.h"
+#include "nsPIBoxObject.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+#include "nsRuleProcessorData.h"
+#include "nsString.h"
+#include "nsStyleConsts.h"
+#include "nsSVGUtils.h"
+#include "nsTextNode.h"
+#include "nsUnicharUtils.h"
+#include "nsXBLBinding.h"
+#include "nsXBLPrototypeBinding.h"
+#include "mozilla/Preferences.h"
+#include "prprf.h"
+#include "xpcpublic.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsCSSParser.h"
+#include "HTMLLegendElement.h"
+#include "nsWrapperCacheInlines.h"
+#include "WrapperFactory.h"
+#include "DocumentType.h"
+#include <algorithm>
+#include "nsGlobalWindow.h"
+#include "nsDOMMutationObserver.h"
+#include "GeometryUtils.h"
+#include "nsIAnimationObserver.h"
+#include "nsChildContentList.h"
+
+#ifdef ACCESSIBILITY
+#include "mozilla/dom/AccessibleNode.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsINode::nsSlots::nsSlots()
+ : mWeakReference(nullptr),
+ mEditableDescendantCount(0)
+{
+}
+
+nsINode::nsSlots::~nsSlots()
+{
+ if (mChildNodes) {
+ mChildNodes->DropReference();
+ }
+
+ 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()
+{
+ if (mChildNodes) {
+ mChildNodes->DropReference();
+ }
+}
+
+//----------------------------------------------------------------------
+
+nsINode::~nsINode()
+{
+ MOZ_ASSERT(!HasSlots(), "nsNodeUtils::LastRelease was not called?");
+ MOZ_ASSERT(mSubtreeRoot == this, "Didn't restore state properly?");
+#ifdef MOZ_STYLO
+ ClearServoData();
+#endif
+}
+
+void*
+nsINode::GetProperty(uint16_t aCategory, nsIAtom *aPropertyName,
+ nsresult *aStatus) const
+{
+ return OwnerDoc()->PropertyTable(aCategory)->GetProperty(this, aPropertyName,
+ aStatus);
+}
+
+nsresult
+nsINode::SetProperty(uint16_t aCategory, nsIAtom *aPropertyName, void *aValue,
+ NSPropertyDtorFunc aDtor, bool aTransfer,
+ void **aOldValue)
+{
+ nsresult rv = OwnerDoc()->PropertyTable(aCategory)->SetProperty(this,
+ aPropertyName,
+ aValue, aDtor,
+ nullptr,
+ aTransfer,
+ aOldValue);
+ if (NS_SUCCEEDED(rv)) {
+ SetFlags(NODE_HAS_PROPERTIES);
+ }
+
+ return rv;
+}
+
+void
+nsINode::DeleteProperty(uint16_t aCategory, nsIAtom *aPropertyName)
+{
+ OwnerDoc()->PropertyTable(aCategory)->DeleteProperty(this, aPropertyName);
+}
+
+void*
+nsINode::UnsetProperty(uint16_t aCategory, nsIAtom *aPropertyName,
+ nsresult *aStatus)
+{
+ return OwnerDoc()->PropertyTable(aCategory)->UnsetProperty(this,
+ aPropertyName,
+ aStatus);
+}
+
+nsINode::nsSlots*
+nsINode::CreateSlots()
+{
+ return new nsSlots();
+}
+
+bool
+nsINode::IsEditableInternal() const
+{
+ if (HasFlag(NODE_IS_EDITABLE)) {
+ // The node is in an editable contentEditable subtree.
+ return true;
+ }
+
+ nsIDocument *doc = GetUncomposedDoc();
+
+ // Check if the node is in a document and the document is in designMode.
+ return doc && doc->HasFlag(NODE_IS_EDITABLE);
+}
+
+static nsIContent* GetEditorRootContent(nsIEditor* aEditor)
+{
+ nsCOMPtr<nsIDOMElement> rootElement;
+ aEditor->GetRootElement(getter_AddRefs(rootElement));
+ nsCOMPtr<nsIContent> rootContent(do_QueryInterface(rootElement));
+ return rootContent;
+}
+
+nsIContent*
+nsINode::GetTextEditorRootContent(nsIEditor** aEditor)
+{
+ if (aEditor)
+ *aEditor = nullptr;
+ for (nsINode* node = this; node; node = node->GetParentNode()) {
+ if (!node->IsElement() ||
+ !node->IsHTMLElement())
+ continue;
+
+ nsCOMPtr<nsIEditor> editor =
+ static_cast<nsGenericHTMLElement*>(node)->GetEditorInternal();
+ if (!editor)
+ continue;
+
+ nsIContent* rootContent = GetEditorRootContent(editor);
+ if (aEditor)
+ editor.swap(*aEditor);
+ return rootContent;
+ }
+ return nullptr;
+}
+
+nsINode*
+nsINode::SubtreeRoot() const
+{
+ // There are four cases of interest here. nsINodes that are really:
+ // 1. nsIDocument 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. nsIAttribute 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;
+ } else {
+ node = mSubtreeRoot;
+ }
+ NS_ASSERTION(node, "Should always have a node here!");
+#ifdef DEBUG
+ {
+ const nsINode* slowNode = this;
+ const nsINode* iter = slowNode;
+ while ((iter = iter->GetParentNode())) {
+ slowNode = iter;
+ }
+
+ NS_ASSERTION(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.
+ ShadowRoot* containingShadow = aContent->GetContainingShadow();
+ if (containingShadow) {
+ return containingShadow;
+ }
+
+ nsIContent* stop = aContent->GetBindingParent();
+ while (aContent) {
+ nsIContent* parent = aContent->GetParent();
+ if (parent == stop) {
+ break;
+ }
+ aContent = parent;
+ }
+ return aContent;
+}
+
+nsIContent*
+nsINode::GetSelectionRootContent(nsIPresShell* aPresShell)
+{
+ NS_ENSURE_TRUE(aPresShell, nullptr);
+
+ if (IsNodeOfType(eDOCUMENT))
+ return static_cast<nsIDocument*>(this)->GetRootElement();
+ if (!IsNodeOfType(eCONTENT))
+ return nullptr;
+
+ if (GetComposedDoc() != aPresShell->GetDocument()) {
+ return nullptr;
+ }
+
+ if (static_cast<nsIContent*>(this)->HasIndependentSelection()) {
+ // This node should be a descendant of input/textarea editor.
+ nsIContent* content = GetTextEditorRootContent();
+ if (content)
+ return content;
+ }
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+ if (presContext) {
+ nsIEditor* editor = nsContentUtils::GetHTMLEditor(presContext);
+ if (editor) {
+ // This node is in HTML editor.
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc || doc->HasFlag(NODE_IS_EDITABLE) ||
+ !HasFlag(NODE_IS_EDITABLE)) {
+ nsIContent* editorRoot = GetEditorRootContent(editor);
+ NS_ENSURE_TRUE(editorRoot, nullptr);
+ return nsContentUtils::IsInSameAnonymousTree(this, editorRoot) ?
+ editorRoot :
+ GetRootForContentSubtree(static_cast<nsIContent*>(this));
+ }
+ // 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) {
+ nsIDocument* 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(static_cast<nsIContent*>(this));
+ // Fixup for ShadowRoot because the ShadowRoot itself does not have a frame.
+ // Use the host as the root.
+ ShadowRoot* shadowRoot = ShadowRoot::FromNode(content);
+ if (shadowRoot) {
+ content = shadowRoot->GetHost();
+ }
+ }
+
+ return content;
+}
+
+nsINodeList*
+nsINode::ChildNodes()
+{
+ nsSlots* slots = Slots();
+ if (!slots->mChildNodes) {
+ slots->mChildNodes = new nsChildContentList(this);
+ }
+
+ return slots->mChildNodes;
+}
+
+void
+nsINode::GetTextContentInternal(nsAString& aTextContent, ErrorResult& aError)
+{
+ SetDOMStringToNull(aTextContent);
+}
+
+nsIDocument*
+nsINode::GetComposedDocInternal() const
+{
+ MOZ_ASSERT(HasFlag(NODE_IS_IN_SHADOW_TREE) && IsContent(),
+ "Should only be caled on nodes in the shadow tree.");
+
+ ShadowRoot* containingShadow = AsContent()->GetContainingShadow();
+ return containingShadow->IsComposedDocParticipant() ? OwnerDoc() : nullptr;
+}
+
+#ifdef DEBUG
+void
+nsINode::CheckNotNativeAnonymous() const
+{
+ if (!IsNodeOfType(eCONTENT))
+ return;
+ nsIContent* content = static_cast<const nsIContent *>(this)->GetBindingParent();
+ while (content) {
+ if (content->IsRootOfNativeAnonymousSubtree()) {
+ NS_ERROR("Element not marked to be in native anonymous subtree!");
+ break;
+ }
+ content = content->GetBindingParent();
+ }
+}
+#endif
+
+bool
+nsINode::IsInAnonymousSubtree() const
+{
+ if (!IsContent()) {
+ return false;
+ }
+
+ return AsContent()->IsInAnonymousSubtree();
+}
+
+std::ostream&
+operator<<(std::ostream& aStream, const nsINode& aNode)
+{
+ nsAutoString elemDesc;
+ const nsINode* curr = &aNode;
+ while (curr) {
+ const nsString& localName = curr->LocalName();
+ nsString id;
+ if (curr->IsElement()) {
+ curr->AsElement()->GetId(id);
+ }
+
+ if (!elemDesc.IsEmpty()) {
+ elemDesc = elemDesc + NS_LITERAL_STRING(".");
+ }
+
+ elemDesc = elemDesc + localName;
+
+ if (!id.IsEmpty()) {
+ elemDesc = elemDesc + NS_LITERAL_STRING("['") + id +
+ NS_LITERAL_STRING("']");
+ }
+
+ curr = curr->GetParentNode();
+ }
+
+ NS_ConvertUTF16toUTF8 str(elemDesc);
+ return aStream << str.get();
+}
+
+bool
+nsINode::IsAnonymousContentInSVGUseSubtree() const
+{
+ MOZ_ASSERT(IsInAnonymousSubtree());
+ nsIContent* parent = AsContent()->GetBindingParent();
+ // Watch out for parentless native-anonymous subtrees.
+ return parent && parent->IsSVGElement(nsGkAtoms::use);
+}
+
+nsresult
+nsINode::GetParentNode(nsIDOMNode** aParentNode)
+{
+ *aParentNode = nullptr;
+
+ nsINode *parent = GetParentNode();
+
+ return parent ? CallQueryInterface(parent, aParentNode) : NS_OK;
+}
+
+nsresult
+nsINode::GetParentElement(nsIDOMElement** aParentElement)
+{
+ *aParentElement = nullptr;
+ nsINode* parent = GetParentElement();
+ return parent ? CallQueryInterface(parent, aParentElement) : NS_OK;
+}
+
+nsresult
+nsINode::GetChildNodes(nsIDOMNodeList** aChildNodes)
+{
+ NS_ADDREF(*aChildNodes = ChildNodes());
+
+ return NS_OK;
+}
+
+nsresult
+nsINode::GetFirstChild(nsIDOMNode** aNode)
+{
+ nsIContent* child = GetFirstChild();
+ if (child) {
+ return CallQueryInterface(child, aNode);
+ }
+
+ *aNode = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+nsINode::GetLastChild(nsIDOMNode** aNode)
+{
+ nsIContent* child = GetLastChild();
+ if (child) {
+ return CallQueryInterface(child, aNode);
+ }
+
+ *aNode = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+nsINode::GetPreviousSibling(nsIDOMNode** aPrevSibling)
+{
+ *aPrevSibling = nullptr;
+
+ nsIContent *sibling = GetPreviousSibling();
+
+ return sibling ? CallQueryInterface(sibling, aPrevSibling) : NS_OK;
+}
+
+nsresult
+nsINode::GetNextSibling(nsIDOMNode** aNextSibling)
+{
+ *aNextSibling = nullptr;
+
+ nsIContent *sibling = GetNextSibling();
+
+ return sibling ? CallQueryInterface(sibling, aNextSibling) : NS_OK;
+}
+
+nsresult
+nsINode::GetOwnerDocument(nsIDOMDocument** aOwnerDocument)
+{
+ *aOwnerDocument = nullptr;
+
+ nsIDocument *ownerDoc = GetOwnerDocument();
+
+ return ownerDoc ? CallQueryInterface(ownerDoc, aOwnerDocument) : NS_OK;
+}
+
+void
+nsINode::GetNodeValueInternal(nsAString& aNodeValue)
+{
+ SetDOMStringToNull(aNodeValue);
+}
+
+nsINode*
+nsINode::RemoveChild(nsINode& aOldChild, ErrorResult& aError)
+{
+ if (IsNodeOfType(eDATA_NODE)) {
+ // aOldChild can't be one of our children.
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ if (aOldChild.GetParentNode() == this) {
+ nsContentUtils::MaybeFireNodeRemoved(&aOldChild, this, OwnerDoc());
+ }
+
+ int32_t index = IndexOf(&aOldChild);
+ if (index == -1) {
+ // aOldChild isn't one of our children.
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ RemoveChildAt(index, true);
+ return &aOldChild;
+}
+
+nsresult
+nsINode::RemoveChild(nsIDOMNode* aOldChild, nsIDOMNode** aReturn)
+{
+ nsCOMPtr<nsINode> oldChild = do_QueryInterface(aOldChild);
+ if (!oldChild) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ ErrorResult rv;
+ RemoveChild(*oldChild, rv);
+ if (!rv.Failed()) {
+ NS_ADDREF(*aReturn = aOldChild);
+ }
+ return rv.StealNSResult();
+}
+
+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() != nsIDOMNode::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.
+ nsIDocument* 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 (uint32_t i = 0; i < nodes.Length(); ++i) {
+ nsINode* parentNode = nodes[i]->GetParentNode();
+ if (parentNode) { // Node may have already been removed.
+ nsContentUtils::MaybeFireNodeRemoved(nodes[i], parentNode,
+ doc);
+ }
+ }
+ }
+
+ mozAutoDocUpdate batch(doc, UPDATE_CONTENT_MODEL, 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() == nsIDOMNode::TEXT_NODE) ||
+ hasRemoveListeners,
+ "Should always have a previous text sibling unless "
+ "mutation events messed us up");
+ if (!hasRemoveListeners ||
+ (target && target->NodeType() == nsIDOMNode::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->RemoveChildAt(parent->IndexOf(node), true);
+ }
+ }
+}
+
+nsresult
+nsINode::GetBaseURI(nsAString &aURI) const
+{
+ nsCOMPtr<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, ErrorResult& aRv) const
+{
+ nsCOMPtr<nsIURI> baseURI = GetBaseURI(nsContentUtils::IsCallerChrome());
+ nsAutoCString spec;
+ if (baseURI) {
+ nsresult res = baseURI->GetSpec(spec);
+ if (NS_FAILED(res)) {
+ aRv.Throw(res);
+ return;
+ }
+ }
+ CopyUTF8toUTF16(spec, aURI);
+}
+
+already_AddRefed<nsIURI>
+nsINode::GetBaseURIObject() const
+{
+ return GetBaseURI(true);
+}
+
+void
+nsINode::LookupPrefix(const nsAString& aNamespaceURI, nsAString& aPrefix)
+{
+ Element *element = GetNameSpaceElement();
+ if (element) {
+ // 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 (nsIContent* content = element; content;
+ content = content->GetParent()) {
+ uint32_t attrCount = content->GetAttrCount();
+
+ for (uint32_t i = 0; i < attrCount; ++i) {
+ const nsAttrName* name = content->GetAttrNameAt(i);
+
+ if (name->NamespaceEquals(kNameSpaceID_XMLNS) &&
+ content->AttrValueIs(kNameSpaceID_XMLNS, name->LocalName(),
+ aNamespaceURI, eCaseMatters)) {
+ // If the localName is "xmlns", the prefix we output should be
+ // null.
+ nsIAtom *localName = name->LocalName();
+
+ if (localName != nsGkAtoms::xmlns) {
+ localName->ToString(aPrefix);
+ }
+ else {
+ SetDOMStringToNull(aPrefix);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ SetDOMStringToNull(aPrefix);
+}
+
+static nsresult
+SetUserDataProperty(uint16_t aCategory, nsINode *aNode, nsIAtom *aKey,
+ nsISupports* aValue, void** aOldValue)
+{
+ nsresult rv = aNode->SetProperty(aCategory, aKey, aValue,
+ nsPropertyTable::SupportsDtorFunc, true,
+ aOldValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Property table owns it now.
+ NS_ADDREF(aValue);
+
+ return NS_OK;
+}
+
+nsresult
+nsINode::SetUserData(const nsAString &aKey, nsIVariant *aData, nsIVariant **aResult)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eGetSetUserData);
+ *aResult = nullptr;
+
+ nsCOMPtr<nsIAtom> key = NS_Atomize(aKey);
+ if (!key) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv;
+ void *data;
+ if (aData) {
+ rv = SetUserDataProperty(DOM_USER_DATA, this, key, aData, &data);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ data = UnsetProperty(DOM_USER_DATA, key);
+ }
+
+ // Take over ownership of the old data from the property table.
+ nsCOMPtr<nsIVariant> oldData = dont_AddRef(static_cast<nsIVariant*>(data));
+ oldData.swap(*aResult);
+ return NS_OK;
+}
+
+void
+nsINode::SetUserData(JSContext* aCx, const nsAString& aKey,
+ JS::Handle<JS::Value> aData,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError)
+{
+ nsCOMPtr<nsIVariant> data;
+ aError = nsContentUtils::XPConnect()->JSValToVariant(aCx, aData, getter_AddRefs(data));
+ if (aError.Failed()) {
+ return;
+ }
+
+ nsCOMPtr<nsIVariant> oldData;
+ aError = SetUserData(aKey, data, getter_AddRefs(oldData));
+ if (aError.Failed()) {
+ return;
+ }
+
+ if (!oldData) {
+ aRetval.setNull();
+ return;
+ }
+
+ JSAutoCompartment ac(aCx, GetWrapper());
+ aError = nsContentUtils::XPConnect()->VariantToJS(aCx, GetWrapper(), oldData,
+ aRetval);
+}
+
+nsIVariant*
+nsINode::GetUserData(const nsAString& aKey)
+{
+ OwnerDoc()->WarnOnceAbout(nsIDocument::eGetSetUserData);
+ nsCOMPtr<nsIAtom> key = NS_Atomize(aKey);
+ if (!key) {
+ return nullptr;
+ }
+
+ return static_cast<nsIVariant*>(GetProperty(DOM_USER_DATA, key));
+}
+
+void
+nsINode::GetUserData(JSContext* aCx, const nsAString& aKey,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError)
+{
+ nsIVariant* data = GetUserData(aKey);
+ if (!data) {
+ aRetval.setNull();
+ return;
+ }
+
+ JSAutoCompartment ac(aCx, GetWrapper());
+ aError = nsContentUtils::XPConnect()->VariantToJS(aCx, GetWrapper(), data,
+ aRetval);
+}
+
+uint16_t
+nsINode::CompareDocumentPosition(nsINode& aOtherNode) const
+{
+ if (this == &aOtherNode) {
+ return 0;
+ }
+ if (GetPreviousSibling() == &aOtherNode) {
+ MOZ_ASSERT(GetParentNode() == aOtherNode.GetParentNode());
+ return static_cast<uint16_t>(nsIDOMNode::DOCUMENT_POSITION_PRECEDING);
+ }
+ if (GetNextSibling() == &aOtherNode) {
+ MOZ_ASSERT(GetParentNode() == aOtherNode.GetParentNode());
+ return static_cast<uint16_t>(nsIDOMNode::DOCUMENT_POSITION_FOLLOWING);
+ }
+
+ AutoTArray<const nsINode*, 32> parents1, parents2;
+
+ const nsINode *node1 = &aOtherNode, *node2 = this;
+
+ // Check if either node is an attribute
+ const Attr* attr1 = nullptr;
+ if (node1->IsNodeOfType(nsINode::eATTRIBUTE)) {
+ attr1 = static_cast<const Attr*>(node1);
+ 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 (node2->IsNodeOfType(nsINode::eATTRIBUTE)) {
+ const Attr* attr2 = static_cast<const Attr*>(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 nsIDOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
+ nsIDOMNode::DOCUMENT_POSITION_PRECEDING;
+ }
+ if (attrName->Equals(attr2->NodeInfo())) {
+ return nsIDOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
+ nsIDOMNode::DOCUMENT_POSITION_FOLLOWING;
+ }
+ }
+ NS_NOTREACHED("neither attribute in the element");
+ return nsIDOMNode::DOCUMENT_POSITION_DISCONNECTED;
+ }
+
+ if (elem) {
+ node2 = elem;
+ parents2.AppendElement(attr2);
+ }
+ }
+
+ // We now know that both nodes are either nsIContents or nsIDocuments.
+ // 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 ?
+ (nsIDOMNode::DOCUMENT_POSITION_PRECEDING |
+ nsIDOMNode::DOCUMENT_POSITION_DISCONNECTED |
+ nsIDOMNode::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) :
+ (nsIDOMNode::DOCUMENT_POSITION_FOLLOWING |
+ nsIDOMNode::DOCUMENT_POSITION_DISCONNECTED |
+ nsIDOMNode::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
+ // IndexOf will return -1 for the attribute making the attribute be
+ // considered before any child.
+ return parent->IndexOf(child1) < parent->IndexOf(child2) ?
+ static_cast<uint16_t>(nsIDOMNode::DOCUMENT_POSITION_PRECEDING) :
+ static_cast<uint16_t>(nsIDOMNode::DOCUMENT_POSITION_FOLLOWING);
+ }
+ 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 ?
+ (nsIDOMNode::DOCUMENT_POSITION_PRECEDING |
+ nsIDOMNode::DOCUMENT_POSITION_CONTAINS) :
+ (nsIDOMNode::DOCUMENT_POSITION_FOLLOWING |
+ nsIDOMNode::DOCUMENT_POSITION_CONTAINED_BY);
+}
+
+bool
+nsINode::IsSameNode(nsINode *other)
+{
+ return other == this;
+}
+
+bool
+nsINode::IsEqualNode(nsINode* aOther)
+{
+ if (!aOther) {
+ return false;
+ }
+
+ 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 nsIDOMNode::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 nsIDOMNode::TEXT_NODE:
+ case nsIDOMNode::COMMENT_NODE:
+ case nsIDOMNode::CDATA_SECTION_NODE:
+ case nsIDOMNode::PROCESSING_INSTRUCTION_NODE:
+ {
+ string1.Truncate();
+ static_cast<nsIContent*>(node1)->AppendTextTo(string1);
+ string2.Truncate();
+ static_cast<nsIContent*>(node2)->AppendTextTo(string2);
+
+ if (!string1.Equals(string2)) {
+ return false;
+ }
+
+ break;
+ }
+ case nsIDOMNode::DOCUMENT_NODE:
+ case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
+ break;
+ case nsIDOMNode::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 nsIDOMNode::DOCUMENT_TYPE_NODE:
+ {
+ nsCOMPtr<nsIDOMDocumentType> docType1 = do_QueryInterface(node1);
+ nsCOMPtr<nsIDOMDocumentType> docType2 = do_QueryInterface(node2);
+
+ NS_ASSERTION(docType1 && docType2, "Why don't we have a document type node?");
+
+ // 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);
+ }
+}
+
+NS_IMPL_DOMTARGET_DEFAULTS(nsINode)
+
+NS_IMETHODIMP
+nsINode::AddEventListener(const nsAString& aType,
+ nsIDOMEventListener *aListener,
+ bool aUseCapture,
+ bool aWantsUntrusted,
+ uint8_t aOptionalArgc)
+{
+ NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1,
+ "Won't check if this is chrome, you want to set "
+ "aWantsUntrusted to false or make the aWantsUntrusted "
+ "explicit by making aOptionalArgc non-zero.");
+
+ if (!aWantsUntrusted &&
+ (aOptionalArgc < 2 &&
+ !nsContentUtils::IsChromeDoc(OwnerDoc()))) {
+ aWantsUntrusted = true;
+ }
+
+ EventListenerManager* listener_manager = GetOrCreateListenerManager();
+ NS_ENSURE_STATE(listener_manager);
+ listener_manager->AddEventListener(aType, aListener, aUseCapture,
+ aWantsUntrusted);
+ return NS_OK;
+}
+
+void
+nsINode::AddEventListener(const nsAString& aType,
+ EventListener* aListener,
+ const AddEventListenerOptionsOrBoolean& aOptions,
+ const Nullable<bool>& aWantsUntrusted,
+ ErrorResult& aRv)
+{
+ bool wantsUntrusted;
+ if (aWantsUntrusted.IsNull()) {
+ wantsUntrusted = !nsContentUtils::IsChromeDoc(OwnerDoc());
+ } else {
+ wantsUntrusted = aWantsUntrusted.Value();
+ }
+
+ EventListenerManager* listener_manager = GetOrCreateListenerManager();
+ if (!listener_manager) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ listener_manager->AddEventListener(aType, aListener, aOptions,
+ wantsUntrusted);
+}
+
+NS_IMETHODIMP
+nsINode::AddSystemEventListener(const nsAString& aType,
+ nsIDOMEventListener *aListener,
+ bool aUseCapture,
+ bool aWantsUntrusted,
+ uint8_t aOptionalArgc)
+{
+ NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1,
+ "Won't check if this is chrome, you want to set "
+ "aWantsUntrusted to false or make the aWantsUntrusted "
+ "explicit by making aOptionalArgc non-zero.");
+
+ if (!aWantsUntrusted &&
+ (aOptionalArgc < 2 &&
+ !nsContentUtils::IsChromeDoc(OwnerDoc()))) {
+ aWantsUntrusted = true;
+ }
+
+ return NS_AddSystemEventListener(this, aType, aListener, aUseCapture,
+ aWantsUntrusted);
+}
+
+NS_IMETHODIMP
+nsINode::RemoveEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture)
+{
+ EventListenerManager* elm = GetExistingListenerManager();
+ if (elm) {
+ elm->RemoveEventListener(aType, aListener, aUseCapture);
+ }
+ return NS_OK;
+}
+
+NS_IMPL_REMOVE_SYSTEM_EVENT_LISTENER(nsINode)
+
+nsresult
+nsINode::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ // This is only here so that we can use the NS_DECL_NSIDOMTARGET macro
+ NS_ABORT();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+nsINode::GetBoxQuads(const BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad> >& aResult,
+ mozilla::ErrorResult& aRv)
+{
+ mozilla::GetBoxQuads(this, aOptions, aResult, aRv);
+}
+
+already_AddRefed<DOMQuad>
+nsINode::ConvertQuadFromNode(DOMQuad& aQuad,
+ const GeometryNode& aFrom,
+ const ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv)
+{
+ return mozilla::ConvertQuadFromNode(this, aQuad, aFrom, aOptions, aRv);
+}
+
+already_AddRefed<DOMQuad>
+nsINode::ConvertRectFromNode(DOMRectReadOnly& aRect,
+ const GeometryNode& aFrom,
+ const ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv)
+{
+ return mozilla::ConvertRectFromNode(this, aRect, aFrom, aOptions, aRv);
+}
+
+already_AddRefed<DOMPoint>
+nsINode::ConvertPointFromNode(const DOMPointInit& aPoint,
+ const GeometryNode& aFrom,
+ const ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv)
+{
+ return mozilla::ConvertPointFromNode(this, aPoint, aFrom, aOptions, aRv);
+}
+
+nsresult
+nsINode::DispatchEvent(nsIDOMEvent *aEvent, bool* aRetVal)
+{
+ // 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<nsIDocument> document = OwnerDoc();
+
+ // Do nothing if the element does not belong to a document
+ if (!document) {
+ *aRetVal = true;
+ return NS_OK;
+ }
+
+ // Obtain a presentation shell
+ nsIPresShell *shell = document->GetShell();
+ RefPtr<nsPresContext> context;
+ if (shell) {
+ context = shell->GetPresContext();
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv =
+ EventDispatcher::DispatchDOMEvent(this, nullptr, aEvent, context, &status);
+ *aRetVal = (status != nsEventStatus_eConsumeNoDefault);
+ return rv;
+}
+
+nsresult
+nsINode::PostHandleEvent(EventChainPostVisitor& /*aVisitor*/)
+{
+ return NS_OK;
+}
+
+nsresult
+nsINode::DispatchDOMEvent(WidgetEvent* aEvent,
+ nsIDOMEvent* aDOMEvent,
+ nsPresContext* aPresContext,
+ nsEventStatus* aEventStatus)
+{
+ return EventDispatcher::DispatchDOMEvent(this, aEvent, aDOMEvent,
+ aPresContext, aEventStatus);
+}
+
+EventListenerManager*
+nsINode::GetOrCreateListenerManager()
+{
+ return nsContentUtils::GetListenerManagerForNode(this);
+}
+
+EventListenerManager*
+nsINode::GetExistingListenerManager() const
+{
+ return nsContentUtils::GetExistingListenerManagerForNode(this);
+}
+
+nsIScriptContext*
+nsINode::GetContextForEventHandlers(nsresult* aRv)
+{
+ return nsContentUtils::GetContextForEventHandlers(this, aRv);
+}
+
+nsPIDOMWindowOuter*
+nsINode::GetOwnerGlobalForBindings()
+{
+ bool dummy;
+ auto* window = static_cast<nsGlobalWindow*>(OwnerDoc()->GetScriptHandlingObject(dummy));
+ return window ? nsPIDOMWindowOuter::GetFromCurrentInner(window->AsInner()) : nullptr;
+}
+
+nsIGlobalObject*
+nsINode::GetOwnerGlobal() const
+{
+ bool dummy;
+ return OwnerDoc()->GetScriptHandlingObject(dummy);
+}
+
+void
+nsINode::ChangeEditableDescendantCount(int32_t aDelta)
+{
+ if (aDelta == 0) {
+ return;
+ }
+
+ nsSlots* s = Slots();
+ MOZ_ASSERT(aDelta > 0 ||
+ s->mEditableDescendantCount >= (uint32_t) (-1 * aDelta));
+ s->mEditableDescendantCount += aDelta;
+}
+
+void
+nsINode::ResetEditableDescendantCount()
+{
+ nsSlots* s = GetExistingSlots();
+ if (s) {
+ s->mEditableDescendantCount = 0;
+ }
+}
+
+uint32_t
+nsINode::EditableDescendantCount()
+{
+ nsSlots* s = GetExistingSlots();
+ if (s) {
+ return s->mEditableDescendantCount;
+ }
+ return 0;
+}
+
+bool
+nsINode::UnoptimizableCCNode() const
+{
+ const uintptr_t problematicFlags = (NODE_IS_ANONYMOUS_ROOT |
+ NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE |
+ NODE_IS_NATIVE_ANONYMOUS_ROOT |
+ NODE_MAY_BE_IN_BINDING_MNGR |
+ NODE_IS_IN_SHADOW_TREE);
+ return HasFlag(problematicFlags) ||
+ NodeType() == nsIDOMNode::ATTRIBUTE_NODE ||
+ // For strange cases like xbl:content/xbl:children
+ (IsElement() &&
+ AsElement()->IsInNamespace(kNameSpaceID_XBL));
+}
+
+void
+nsINode::ClearServoData() {
+#ifdef MOZ_STYLO
+ Servo_Node_ClearNodeData(this);
+#else
+ MOZ_CRASH("Accessing servo node data in non-stylo build");
+#endif
+}
+
+/* static */
+bool
+nsINode::Traverse(nsINode *tmp, nsCycleCollectionTraversalCallback &cb)
+{
+ if (MOZ_LIKELY(!cb.WantAllTraces())) {
+ nsIDocument *currentDoc = tmp->GetUncomposedDoc();
+ if (currentDoc &&
+ nsCCUncollectableMarker::InGeneration(currentDoc->GetMarkedCCGeneration())) {
+ return false;
+ }
+
+ if (nsCCUncollectableMarker::sGeneration) {
+ // If we're black no need to traverse.
+ if (tmp->IsBlack() || tmp->InCCBlackTree()) {
+ return false;
+ }
+
+ if (!tmp->UnoptimizableCCNode()) {
+ // If we're in a black document, return early.
+ if ((currentDoc && currentDoc->IsBlack())) {
+ 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->IsBlack()) {
+ MOZ_ASSERT(parent->IndexOf(tmp) >= 0, "Parent doesn't own us?");
+ return false;
+ }
+ }
+ }
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfo)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(GetParent())
+
+ nsSlots *slots = tmp->GetExistingSlots();
+ if (slots) {
+ slots->Traverse(cb);
+ }
+
+ if (tmp->HasProperties()) {
+ nsNodeUtils::TraverseUserData(tmp, cb);
+ 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));
+ }
+ }
+ }
+
+ if (tmp->NodeType() != nsIDOMNode::DOCUMENT_NODE &&
+ tmp->HasFlag(NODE_HAS_LISTENERMANAGER)) {
+ nsContentUtils::TraverseListenerManager(tmp, cb);
+ }
+
+ return true;
+}
+
+/* static */
+void
+nsINode::Unlink(nsINode* tmp)
+{
+ tmp->ReleaseWrapper(tmp);
+
+ nsSlots *slots = tmp->GetExistingSlots();
+ if (slots) {
+ slots->Unlink();
+ }
+
+ if (tmp->NodeType() != nsIDOMNode::DOCUMENT_NODE &&
+ tmp->HasFlag(NODE_HAS_LISTENERMANAGER)) {
+ nsContentUtils::RemoveListenerManager(tmp);
+ tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ if (tmp->HasProperties()) {
+ nsNodeUtils::UnlinkUserData(tmp);
+ tmp->DeleteProperty(nsGkAtoms::keepobjectsalive);
+ }
+}
+
+static void
+ReleaseURI(void*, /* aObject*/
+ nsIAtom*, /* aPropertyName */
+ void* aPropertyValue,
+ void* /* aData */)
+{
+ nsIURI* uri = static_cast<nsIURI*>(aPropertyValue);
+ NS_RELEASE(uri);
+}
+
+nsresult
+nsINode::SetExplicitBaseURI(nsIURI* aURI)
+{
+ nsresult rv = SetProperty(nsGkAtoms::baseURIProperty, aURI, ReleaseURI);
+ if (NS_SUCCEEDED(rv)) {
+ SetHasExplicitBaseURI();
+ NS_ADDREF(aURI);
+ }
+ return rv;
+}
+
+static nsresult
+AdoptNodeIntoOwnerDoc(nsINode *aParent, nsINode *aNode)
+{
+ NS_ASSERTION(!aNode->GetParentNode(),
+ "Should have removed from parent already");
+
+ nsIDocument *doc = aParent->OwnerDoc();
+
+ nsresult rv;
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDOMNode> adoptedNode;
+ rv = domDoc->AdoptNode(node, getter_AddRefs(adoptedNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(aParent->OwnerDoc() == doc,
+ "ownerDoc chainged while adopting");
+ NS_ASSERTION(adoptedNode == node, "Uh, adopt node changed nodes?");
+ NS_ASSERTION(aParent->OwnerDoc() == aNode->OwnerDoc(),
+ "ownerDocument changed again after adopting!");
+
+ return NS_OK;
+}
+
+static nsresult
+CheckForOutdatedParent(nsINode* aParent, nsINode* aNode)
+{
+ if (JSObject* existingObjUnrooted = aNode->GetWrapper()) {
+ JS::Rooted<JSObject*> existingObj(RootingCx(), existingObjUnrooted);
+
+ AutoJSContext cx;
+ nsIGlobalObject* global = aParent->OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+
+ if (js::GetGlobalForObjectCrossCompartment(existingObj) !=
+ global->GetGlobalJSObject()) {
+ JSAutoCompartment ac(cx, existingObj);
+ nsresult rv = ReparentWrapper(cx, existingObj);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsINode::doInsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify, nsAttrAndChildArray& aChildArray)
+{
+ NS_PRECONDITION(!aKid->GetParentNode(),
+ "Inserting node that already has parent");
+ nsresult rv;
+
+ // 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
+ nsIDocument* doc = GetUncomposedDoc();
+ mozAutoDocUpdate updateBatch(GetComposedDoc(), UPDATE_CONTENT_MODEL, aNotify);
+
+ if (OwnerDoc() != aKid->OwnerDoc()) {
+ rv = AdoptNodeIntoOwnerDoc(this, aKid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (OwnerDoc()->DidDocumentOpen()) {
+ rv = CheckForOutdatedParent(this, aKid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t childCount = aChildArray.ChildCount();
+ NS_ENSURE_TRUE(aIndex <= childCount, NS_ERROR_ILLEGAL_VALUE);
+ bool isAppend = (aIndex == childCount);
+
+ rv = aChildArray.InsertChildAt(aKid, aIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aIndex == 0) {
+ mFirstChild = aKid;
+ }
+
+ nsIContent* parent =
+ IsNodeOfType(eDOCUMENT) ? nullptr : static_cast<nsIContent*>(this);
+
+ rv = aKid->BindToTree(doc, parent,
+ parent ? parent->GetBindingParent() : nullptr,
+ true);
+ if (NS_FAILED(rv)) {
+ if (GetFirstChild() == aKid) {
+ mFirstChild = aKid->GetNextSibling();
+ }
+ aChildArray.RemoveChildAt(aIndex);
+ aKid->UnbindFromTree();
+ return rv;
+ }
+
+ 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 && isAppend) {
+ nsNodeUtils::ContentAppended(parent, aKid, aIndex);
+ } else {
+ nsNodeUtils::ContentInserted(this, aKid, aIndex);
+ }
+
+ if (nsContentUtils::HasMutationListeners(aKid,
+ NS_EVENT_BITS_MUTATION_NODEINSERTED, this)) {
+ InternalMutationEvent mutation(true, eLegacyNodeInserted);
+ mutation.mRelatedNode = do_QueryInterface(this);
+
+ mozAutoSubtreeModified subtree(OwnerDoc(), this);
+ (new AsyncEventDispatcher(aKid, mutation))->RunDOMEventWhenSafe();
+ }
+ }
+
+ return NS_OK;
+}
+
+Element*
+nsINode::GetPreviousElementSibling() const
+{
+ nsIContent* previousSibling = GetPreviousSibling();
+ while (previousSibling) {
+ if (previousSibling->IsElement()) {
+ return previousSibling->AsElement();
+ }
+ previousSibling = previousSibling->GetPreviousSibling();
+ }
+
+ return nullptr;
+}
+
+Element*
+nsINode::GetNextElementSibling() const
+{
+ nsIContent* nextSibling = GetNextSibling();
+ while (nextSibling) {
+ if (nextSibling->IsElement()) {
+ return nextSibling->AsElement();
+ }
+ nextSibling = nextSibling->GetNextSibling();
+ }
+
+ return nullptr;
+}
+
+static already_AddRefed<nsINode>
+GetNodeFromNodeOrString(const OwningNodeOrString& aNode,
+ nsIDocument* 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.
+ */
+static already_AddRefed<nsINode>
+ConvertNodesOrStringsIntoNode(const Sequence<OwningNodeOrString>& aNodes,
+ nsIDocument* 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,
+ nsTHashtable<nsPtrHashKey<nsINode>>& aHashset)
+{
+ for (const auto& node : aNodes) {
+ if (node.IsNode()) {
+ aHashset.PutEntry(node.GetAsNode());
+ }
+ }
+}
+
+static nsINode*
+FindViablePreviousSibling(const nsINode& aNode,
+ const Sequence<OwningNodeOrString>& aNodes)
+{
+ nsTHashtable<nsPtrHashKey<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)
+{
+ nsTHashtable<nsPtrHashKey<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<nsINode> node =
+ ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), 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<nsINode> node =
+ ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), 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<nsINode> node =
+ ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), 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;
+ }
+ int32_t index = parent->IndexOf(this);
+ if (index < 0) {
+ NS_WARNING("Ignoring call to nsINode::Remove on anonymous child.");
+ return;
+ }
+ parent->RemoveChildAt(uint32_t(index), true);
+}
+
+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;
+}
+
+void
+nsINode::Prepend(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsINode> node =
+ ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ nsCOMPtr<nsINode> refNode = mFirstChild;
+ InsertBefore(*node, refNode, aRv);
+}
+
+void
+nsINode::Append(const Sequence<OwningNodeOrString>& aNodes,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsINode> node =
+ ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ AppendChild(*node, aRv);
+}
+
+void
+nsINode::doRemoveChildAt(uint32_t aIndex, bool aNotify,
+ nsIContent* aKid, nsAttrAndChildArray& aChildArray)
+{
+ NS_PRECONDITION(aKid && aKid->GetParentNode() == this &&
+ aKid == GetChildAt(aIndex) &&
+ IndexOf(aKid) == (int32_t)aIndex, "Bogus aKid");
+
+ nsMutationGuard::DidMutate();
+ mozAutoDocUpdate updateBatch(GetComposedDoc(), UPDATE_CONTENT_MODEL, aNotify);
+
+ nsIContent* previousSibling = aKid->GetPreviousSibling();
+
+ if (GetFirstChild() == aKid) {
+ mFirstChild = aKid->GetNextSibling();
+ }
+
+ aChildArray.RemoveChildAt(aIndex);
+
+ if (aNotify) {
+ nsNodeUtils::ContentRemoved(this, aKid, aIndex, 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.
+static
+bool IsAllowedAsChild(nsIContent* aNewChild, nsINode* aParent,
+ bool aIsReplace, nsINode* aRefChild)
+{
+ MOZ_ASSERT(aNewChild, "Must have new child");
+ MOZ_ASSERT_IF(aIsReplace, aRefChild);
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(aParent->IsNodeOfType(nsINode::eDOCUMENT) ||
+ aParent->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT) ||
+ aParent->IsElement(),
+ "Nodes that are not documents, document fragments or elements "
+ "can't be parents!");
+
+ // 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->GetShadowRoot()) &&
+ nsContentUtils::ContentIsHostIncludingDescendantOf(aParent,
+ aNewChild))) {
+ return false;
+ }
+
+ // The allowed child nodes differ for documents and elements
+ switch (aNewChild->NodeType()) {
+ case nsIDOMNode::COMMENT_NODE :
+ case nsIDOMNode::PROCESSING_INSTRUCTION_NODE :
+ // OK in both cases
+ return true;
+ case nsIDOMNode::TEXT_NODE :
+ case nsIDOMNode::CDATA_SECTION_NODE :
+ case nsIDOMNode::ENTITY_REFERENCE_NODE :
+ // Allowed under Elements and DocumentFragments
+ return aParent->NodeType() != nsIDOMNode::DOCUMENT_NODE;
+ case nsIDOMNode::ELEMENT_NODE :
+ {
+ if (!aParent->IsNodeOfType(nsINode::eDOCUMENT)) {
+ // Always ok to have elements under other elements or document fragments
+ return true;
+ }
+
+ nsIDocument* parentDocument = static_cast<nsIDocument*>(aParent);
+ Element* rootElement = parentDocument->GetRootElement();
+ if (rootElement) {
+ // Already have a documentElement, so this is only OK if we're
+ // replacing it.
+ return aIsReplace && rootElement == aRefChild;
+ }
+
+ // 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 true;
+ }
+
+ nsIContent* docTypeContent = parentDocument->GetDoctype();
+ if (!docTypeContent) {
+ // It's all good.
+ return true;
+ }
+
+ int32_t doctypeIndex = aParent->IndexOf(docTypeContent);
+ int32_t insertIndex = aParent->IndexOf(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
+ return aIsReplace ? (insertIndex >= doctypeIndex) :
+ insertIndex > doctypeIndex;
+ }
+ case nsIDOMNode::DOCUMENT_TYPE_NODE :
+ {
+ if (!aParent->IsNodeOfType(nsINode::eDOCUMENT)) {
+ // doctypes only allowed under documents
+ return false;
+ }
+
+ nsIDocument* parentDocument = static_cast<nsIDocument*>(aParent);
+ nsIContent* docTypeContent = parentDocument->GetDoctype();
+ if (docTypeContent) {
+ // Already have a doctype, so this is only OK if we're replacing it
+ return aIsReplace && docTypeContent == aRefChild;
+ }
+
+ // 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 true;
+ }
+
+ if (!aRefChild) {
+ // Trying to append a doctype, but have a documentElement
+ return false;
+ }
+
+ int32_t rootIndex = aParent->IndexOf(rootElement);
+ int32_t insertIndex = aParent->IndexOf(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.
+ return insertIndex <= rootIndex;
+ }
+ case nsIDOMNode::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->IsNodeOfType(nsINode::eDOCUMENT)) {
+ // All good here
+ return true;
+ }
+
+ 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
+ return false;
+ }
+ sawElement = true;
+ }
+ // If we can put this content at the the right place, we might be ok;
+ // if not, we bail out.
+ if (!IsAllowedAsChild(child, aParent, aIsReplace, aRefChild)) {
+ return false;
+ }
+ }
+
+ // Everything in the fragment checked out ok, so we can stick it in here
+ return true;
+ }
+ default:
+ /*
+ * aNewChild is of invalid type.
+ */
+ break;
+ }
+
+ return false;
+}
+
+void
+nsINode::EnsurePreInsertionValidity(nsINode& aNewChild, nsINode* aRefChild,
+ ErrorResult& aError)
+{
+ EnsurePreInsertionValidity1(aNewChild, aRefChild, aError);
+ if (aError.Failed()) {
+ return;
+ }
+ EnsurePreInsertionValidity2(false, aNewChild, aRefChild, aError);
+}
+
+void
+nsINode::EnsurePreInsertionValidity1(nsINode& aNewChild, nsINode* aRefChild,
+ ErrorResult& aError)
+{
+ if ((!IsNodeOfType(eDOCUMENT) &&
+ !IsNodeOfType(eDOCUMENT_FRAGMENT) &&
+ !IsElement()) ||
+ !aNewChild.IsNodeOfType(eCONTENT)) {
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return;
+ }
+}
+
+void
+nsINode::EnsurePreInsertionValidity2(bool aReplace, nsINode& aNewChild,
+ nsINode* aRefChild, ErrorResult& aError)
+{
+ nsIContent* newContent = aNewChild.AsContent();
+ if (newContent->IsRootOfAnonymousSubtree()) {
+ // This is anonymous content. Don't allow its insertion
+ // anywhere, since it might have UnbindFromTree calls coming
+ // its way.
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ // Make sure that the inserted node is allowed as a child of its new parent.
+ if (!IsAllowedAsChild(newContent, this, aReplace, aRefChild)) {
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return;
+ }
+}
+
+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);
+
+ EnsurePreInsertionValidity1(*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
+ {
+ // This check happens again further down (though then using IndexOf).
+ // We're only checking this here to avoid firing mutation events when
+ // none should be fired.
+ // It's ok that we do the check twice in the case when firing mutation
+ // events as we need to recheck after running script anyway.
+ if (aRefChild && aRefChild->GetParentNode() != this) {
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+
+ // 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, OwnerDoc());
+ }
+
+ // If the new node already has a parent, fire for removing from old
+ // parent
+ nsINode* oldParent = aNewChild->GetParentNode();
+ if (oldParent) {
+ nsContentUtils::MaybeFireNodeRemoved(aNewChild, oldParent,
+ aNewChild->OwnerDoc());
+ }
+
+ // If we're inserting a fragment, fire for all the children of the
+ // fragment
+ if (nodeType == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+ static_cast<FragmentOrElement*>(aNewChild)->FireNodeRemovedForChildren();
+ }
+ // Verify that our aRefChild is still sensible
+ if (aRefChild && aRefChild->GetParentNode() != this) {
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+ }
+
+ EnsurePreInsertionValidity2(aReplace, *aNewChild, aRefChild, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ // Record the node to insert before, if any
+ nsINode* nodeToInsertBefore;
+ if (aReplace) {
+ nodeToInsertBefore = aRefChild->GetNextSibling();
+ } else {
+ nodeToInsertBefore = aRefChild;
+ }
+ 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) {
+ int32_t removeIndex = oldParent->IndexOf(newContent);
+ if (removeIndex < 0) {
+ // newContent is anonymous. We can't deal with this, so just bail
+ NS_ERROR("How come our flags didn't catch this?");
+ aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ // 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(),
+ UPDATE_CONTENT_MODEL, true);
+ nsAutoMutationBatch mb(oldParent, true, true);
+ oldParent->RemoveChildAt(removeIndex, true);
+ if (nsAutoMutationBatch::GetCurrentBatch() == &mb) {
+ mb.RemovalDone();
+ mb.SetPrevSibling(oldParent->GetChildAt(removeIndex - 1));
+ mb.SetNextSibling(oldParent->GetChildAt(removeIndex));
+ }
+ }
+
+ // We expect one mutation (the removal) to have happened.
+ if (guard.Mutated(1)) {
+ // 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.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return nullptr;
+ }
+
+ // Verify that newContent has no parent.
+ if (newContent->GetParentNode()) {
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ 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.
+ if (!IsAllowedAsChild(newContent, this, false, nodeToInsertBefore)) {
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return nullptr;
+ }
+ } else {
+ if ((aRefChild && aRefChild->GetParent() != this) ||
+ !IsAllowedAsChild(newContent, this, aReplace, aRefChild)) {
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return nullptr;
+ }
+ // And recompute nodeToInsertBefore, just in case.
+ if (aReplace) {
+ nodeToInsertBefore = aRefChild->GetNextSibling();
+ } else {
+ nodeToInsertBefore = aRefChild;
+ }
+ }
+ }
+ } else if (nodeType == nsIDOMNode::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->GetComposedDoc() == 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(),
+ UPDATE_CONTENT_MODEL, true);
+ nsAutoMutationBatch mb(newContent, false, true);
+
+ for (uint32_t i = count; i > 0;) {
+ newContent->RemoveChildAt(--i, 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.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ 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.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ 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
+ // IsAllowedAsChild 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.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return nullptr;
+ }
+
+ // Recompute nodeToInsertBefore, just in case.
+ if (aReplace) {
+ nodeToInsertBefore = aRefChild->GetNextSibling();
+ } else {
+ nodeToInsertBefore = aRefChild;
+ }
+
+ // And verify that newContent is still allowed as our child. Sadly, we
+ // need to reimplement the relevant part of IsAllowedAsChild() because
+ // now our nodes are in an array and all. If you change this code,
+ // change the code there.
+ if (IsNodeOfType(nsINode::eDOCUMENT)) {
+ bool sawElement = false;
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIContent* child = fragChildren->ElementAt(i);
+ if (child->IsElement()) {
+ if (sawElement) {
+ // No good
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return nullptr;
+ }
+ sawElement = true;
+ }
+ if (!IsAllowedAsChild(child, this, aReplace, aRefChild)) {
+ aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return nullptr;
+ }
+ }
+ }
+ }
+ }
+
+ mozAutoDocUpdate batch(GetComposedDoc(), UPDATE_CONTENT_MODEL, true);
+ nsAutoMutationBatch mb;
+
+ // Figure out which index we want to insert at. Note that we use
+ // nodeToInsertBefore to determine this, because it's possible that
+ // aRefChild == aNewChild, in which case we just removed it from the
+ // parent list.
+ int32_t insPos;
+ if (nodeToInsertBefore) {
+ insPos = IndexOf(nodeToInsertBefore);
+ if (insPos < 0) {
+ // XXXbz How the heck would _that_ happen, exactly?
+ aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
+ return nullptr;
+ }
+ }
+ else {
+ insPos = GetChildCount();
+ }
+
+ // 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");
+
+ // An since nodeToInsertBefore is at index insPos, we want to remove
+ // at the previous index.
+ NS_ASSERTION(insPos >= 1, "insPos too small");
+ RemoveChildAt(insPos-1, true);
+ --insPos;
+ }
+
+ // 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().
+ nsIDocument* doc = OwnerDoc();
+ if (doc != newContent->OwnerDoc()) {
+ aError = AdoptNodeIntoOwnerDoc(this, aNewChild);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ } else if (doc->DidDocumentOpen()) {
+ aError = CheckForOutdatedParent(this, aNewChild);
+ 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 == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+ if (!aReplace) {
+ mb.Init(this, true, true);
+ }
+ nsAutoMutationBatch* mutationBatch = nsAutoMutationBatch::GetCurrentBatch();
+ if (mutationBatch) {
+ mutationBatch->RemovalDone();
+ mutationBatch->SetPrevSibling(GetChildAt(insPos - 1));
+ mutationBatch->SetNextSibling(GetChildAt(insPos));
+ }
+
+ uint32_t count = fragChildren->Length();
+ if (!count) {
+ return result;
+ }
+
+ bool appending =
+ !IsNodeOfType(eDOCUMENT) && uint32_t(insPos) == GetChildCount();
+ int32_t firstInsPos = insPos;
+ 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, ++insPos) {
+ // XXXbz how come no reparenting here? That seems odd...
+ // Insert the child.
+ aError = InsertChildAt(fragChildren->ElementAt(i), insPos,
+ !appending);
+ if (aError.Failed()) {
+ // Make sure to notify on any children that we did succeed to insert
+ if (appending && i != 0) {
+ nsNodeUtils::ContentAppended(static_cast<nsIContent*>(this),
+ firstInsertedContent,
+ firstInsPos);
+ }
+ return nullptr;
+ }
+ }
+
+ if (mutationBatch && !appending) {
+ mutationBatch->NodesAdded();
+ }
+
+ // Notify and fire mutation events when appending
+ if (appending) {
+ nsNodeUtils::ContentAppended(static_cast<nsIContent*>(this),
+ firstInsertedContent, firstInsPos);
+ 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(GetChildAt(insPos - 1));
+ mb.SetNextSibling(GetChildAt(insPos));
+ }
+ aError = InsertChildAt(newContent, insPos, true);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return result;
+}
+
+nsresult
+nsINode::ReplaceOrInsertBefore(bool aReplace, nsIDOMNode *aNewChild,
+ nsIDOMNode *aRefChild, nsIDOMNode **aReturn)
+{
+ nsCOMPtr<nsINode> newChild = do_QueryInterface(aNewChild);
+ if (!newChild) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (aReplace && !aRefChild) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<nsINode> refChild = do_QueryInterface(aRefChild);
+ if (aRefChild && !refChild) {
+ return NS_NOINTERFACE;
+ }
+
+ ErrorResult rv;
+ nsINode* result = ReplaceOrInsertBefore(aReplace, newChild, refChild, rv);
+ if (result) {
+ NS_ADDREF(*aReturn = result->AsDOMNode());
+ }
+ return rv.StealNSResult();
+}
+
+nsresult
+nsINode::CompareDocumentPosition(nsIDOMNode* aOther, uint16_t* aReturn)
+{
+ nsCOMPtr<nsINode> other = do_QueryInterface(aOther);
+ NS_ENSURE_ARG(other);
+ *aReturn = CompareDocumentPosition(*other);
+ return NS_OK;
+}
+
+nsresult
+nsINode::IsEqualNode(nsIDOMNode* aOther, bool* aReturn)
+{
+ nsCOMPtr<nsINode> other = do_QueryInterface(aOther);
+ *aReturn = IsEqualNode(other);
+ return NS_OK;
+}
+
+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);
+ }
+}
+
+void
+nsINode::GetBoundMutationObservers(nsTArray<RefPtr<nsDOMMutationObserver> >& aResult)
+{
+ nsCOMArray<nsISupports>* objects =
+ static_cast<nsCOMArray<nsISupports>*>(GetProperty(nsGkAtoms::keepobjectsalive));
+ if (objects) {
+ for (int32_t i = 0; i < objects->Count(); ++i) {
+ nsCOMPtr<nsDOMMutationObserver> mo = do_QueryInterface(objects->ObjectAt(i));
+ if (mo) {
+ MOZ_ASSERT(!aResult.Contains(mo));
+ aResult.AppendElement(mo.forget());
+ }
+ }
+ }
+}
+
+already_AddRefed<AccessibleNode>
+nsINode::GetAccessibleNode()
+{
+#ifdef ACCESSIBILITY
+ RefPtr<AccessibleNode> anode = new AccessibleNode(this);
+ return anode.forget();
+#endif
+
+ return nullptr;
+}
+
+size_t
+nsINode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ EventListenerManager* elm = GetExistingListenerManager();
+ if (elm) {
+ n += elm->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // 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, mPreviousSibling, mFirstChild: because they're
+ // non-owning
+ return n;
+}
+
+#define EVENT(name_, id_, type_, struct_) \
+ EventHandlerNonNull* nsINode::GetOn##name_() { \
+ EventListenerManager *elm = GetExistingListenerManager(); \
+ return elm ? elm->GetEventHandler(nsGkAtoms::on##name_, EmptyString()) \
+ : nullptr; \
+ } \
+ void nsINode::SetOn##name_(EventHandlerNonNull* handler) \
+ { \
+ EventListenerManager *elm = GetOrCreateListenerManager(); \
+ if (elm) { \
+ elm->SetEventHandler(nsGkAtoms::on##name_, EmptyString(), handler); \
+ } \
+ }
+#define TOUCH_EVENT EVENT
+#define DOCUMENT_ONLY_EVENT EVENT
+#include "mozilla/EventNameList.h"
+#undef DOCUMENT_ONLY_EVENT
+#undef TOUCH_EVENT
+#undef EVENT
+
+bool
+nsINode::Contains(const nsINode* aOther) const
+{
+ if (aOther == this) {
+ return true;
+ }
+ if (!aOther ||
+ OwnerDoc() != aOther->OwnerDoc() ||
+ IsInUncomposedDoc() != aOther->IsInUncomposedDoc() ||
+ !(aOther->IsElement() ||
+ aOther->IsNodeOfType(nsINode::eCONTENT)) ||
+ !GetFirstChild()) {
+ return false;
+ }
+
+ const nsIContent* other = static_cast<const nsIContent*>(aOther);
+ if (this == OwnerDoc()) {
+ // 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 !other->IsInAnonymousSubtree();
+ }
+
+ if (!IsElement() && !IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT)) {
+ return false;
+ }
+
+ const nsIContent* thisContent = static_cast<const nsIContent*>(this);
+ if (thisContent->GetBindingParent() != other->GetBindingParent()) {
+ return false;
+ }
+
+ return nsContentUtils::ContentIsDescendantOf(other, this);
+}
+
+nsresult
+nsINode::Contains(nsIDOMNode* aOther, bool* aReturn)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aOther);
+ *aReturn = Contains(node);
+ return NS_OK;
+}
+
+uint32_t
+nsINode::Length() const
+{
+ switch (NodeType()) {
+ case nsIDOMNode::DOCUMENT_TYPE_NODE:
+ return 0;
+
+ case nsIDOMNode::TEXT_NODE:
+ case nsIDOMNode::CDATA_SECTION_NODE:
+ case nsIDOMNode::PROCESSING_INSTRUCTION_NODE:
+ case nsIDOMNode::COMMENT_NODE:
+ MOZ_ASSERT(IsNodeOfType(eCONTENT));
+ return static_cast<const nsIContent*>(this)->TextLength();
+
+ default:
+ return GetChildCount();
+ }
+}
+
+nsCSSSelectorList*
+nsINode::ParseSelectorList(const nsAString& aSelectorString,
+ ErrorResult& aRv)
+{
+ nsIDocument* doc = OwnerDoc();
+ nsIDocument::SelectorCache& cache = doc->GetSelectorCache();
+ nsCSSSelectorList* selectorList = nullptr;
+ bool haveCachedList = cache.GetList(aSelectorString, &selectorList);
+ if (haveCachedList) {
+ if (!selectorList) {
+ // Invalid selector.
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
+ NS_LITERAL_CSTRING("'") + NS_ConvertUTF16toUTF8(aSelectorString) +
+ NS_LITERAL_CSTRING("' is not a valid selector")
+ );
+ }
+ return selectorList;
+ }
+
+ nsCSSParser parser(doc->CSSLoader());
+
+ aRv = parser.ParseSelectorString(aSelectorString,
+ doc->GetDocumentURI(),
+ 0, // XXXbz get the line number!
+ &selectorList);
+ if (aRv.Failed()) {
+ // We hit this for syntax errors, which are quite common, so don't
+ // use NS_ENSURE_SUCCESS. (For example, jQuery has an extended set
+ // of selectors, but it sees if we can parse them first.)
+ MOZ_ASSERT(aRv.ErrorCodeIs(NS_ERROR_DOM_SYNTAX_ERR),
+ "Unexpected error, so cached version won't return it");
+
+ // Change the error message to match above.
+ aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
+ NS_LITERAL_CSTRING("'") + NS_ConvertUTF16toUTF8(aSelectorString) +
+ NS_LITERAL_CSTRING("' is not a valid selector")
+ );
+
+ cache.CacheList(aSelectorString, nullptr);
+ return nullptr;
+ }
+
+ // Filter out pseudo-element selectors from selectorList
+ nsCSSSelectorList** slot = &selectorList;
+ do {
+ nsCSSSelectorList* cur = *slot;
+ if (cur->mSelectors->IsPseudoElement()) {
+ *slot = cur->mNext;
+ cur->mNext = nullptr;
+ delete cur;
+ } else {
+ slot = &cur->mNext;
+ }
+ } while (*slot);
+
+ if (selectorList) {
+ NS_ASSERTION(selectorList->mSelectors,
+ "How can we not have any selectors?");
+ cache.CacheList(aSelectorString, selectorList);
+ } else {
+ // This is the "only pseudo-element selectors" case, which is
+ // not common, so just don't worry about caching it. That way a
+ // null cached value can always indicate an invalid selector.
+ }
+
+ return selectorList;
+}
+
+static void
+AddScopeElements(TreeMatchContext& aMatchContext,
+ nsINode* aMatchContextNode)
+{
+ if (aMatchContextNode->IsElement()) {
+ aMatchContext.SetHasSpecifiedScope();
+ aMatchContext.AddScopeElement(aMatchContextNode->AsElement());
+ }
+}
+
+namespace {
+struct SelectorMatchInfo {
+ nsCSSSelectorList* const mSelectorList;
+ TreeMatchContext& mMatchContext;
+};
+} // namespace
+
+// Given an id, find elements with that id under aRoot that match aMatchInfo if
+// any is provided. If no SelectorMatchInfo is provided, just find the ones
+// with the given id. aRoot must be in the document.
+template<bool onlyFirstMatch, class T>
+inline static void
+FindMatchingElementsWithId(const nsAString& aId, nsINode* aRoot,
+ SelectorMatchInfo* aMatchInfo,
+ T& aList)
+{
+ MOZ_ASSERT(aRoot->IsInUncomposedDoc(),
+ "Don't call me if the root is not in the document");
+ MOZ_ASSERT(aRoot->IsElement() || aRoot->IsNodeOfType(nsINode::eDOCUMENT),
+ "The optimization below to check ContentIsDescendantOf only for "
+ "elements depends on aRoot being either an element or a "
+ "document if it's in the document. Note that document fragments "
+ "can't be IsInUncomposedDoc(), so should never show up here.");
+
+ const nsTArray<Element*>* elements = aRoot->OwnerDoc()->GetAllElementsForId(aId);
+ if (!elements) {
+ // Nothing to do; we're done
+ return;
+ }
+
+ // XXXbz: Should we fall back to the tree walk if aRoot is not the
+ // document and |elements| is long, for some value of "long"?
+ for (size_t i = 0; i < elements->Length(); ++i) {
+ Element* element = (*elements)[i];
+ if (!aRoot->IsElement() ||
+ (element != aRoot &&
+ nsContentUtils::ContentIsDescendantOf(element, aRoot))) {
+ // We have an element with the right id and it's a strict descendant
+ // of aRoot. Make sure it really matches the selector.
+ if (!aMatchInfo ||
+ nsCSSRuleProcessor::SelectorListMatches(element,
+ aMatchInfo->mMatchContext,
+ aMatchInfo->mSelectorList)) {
+ aList.AppendElement(element);
+ if (onlyFirstMatch) {
+ return;
+ }
+ }
+ }
+ }
+}
+
+// Actually find elements matching aSelectorList (which must not be
+// null) and which are descendants of aRoot and put them in aList. If
+// onlyFirstMatch, then stop once the first one is found.
+template<bool onlyFirstMatch, class Collector, class T>
+MOZ_ALWAYS_INLINE static void
+FindMatchingElements(nsINode* aRoot, nsCSSSelectorList* aSelectorList, T &aList,
+ ErrorResult& aRv)
+{
+ nsIDocument* doc = aRoot->OwnerDoc();
+
+ TreeMatchContext matchingContext(false, nsRuleWalker::eRelevantLinkUnvisited,
+ doc, TreeMatchContext::eNeverMatchVisited);
+ doc->FlushPendingLinkUpdates();
+ AddScopeElements(matchingContext, aRoot);
+
+ // Fast-path selectors involving IDs. We can only do this if aRoot
+ // is in the document and the document is not in quirks mode, since
+ // ID selectors are case-insensitive in quirks mode. Also, only do
+ // this if aSelectorList only has one selector, because otherwise
+ // ordering the elements correctly is a pain.
+ NS_ASSERTION(aRoot->IsElement() || aRoot->IsNodeOfType(nsINode::eDOCUMENT) ||
+ !aRoot->IsInUncomposedDoc(),
+ "The optimization below to check ContentIsDescendantOf only for "
+ "elements depends on aRoot being either an element or a "
+ "document if it's in the document.");
+ if (aRoot->IsInUncomposedDoc() &&
+ doc->GetCompatibilityMode() != eCompatibility_NavQuirks &&
+ !aSelectorList->mNext &&
+ aSelectorList->mSelectors->mIDList) {
+ nsIAtom* id = aSelectorList->mSelectors->mIDList->mAtom;
+ SelectorMatchInfo info = { aSelectorList, matchingContext };
+ FindMatchingElementsWithId<onlyFirstMatch, T>(nsDependentAtomString(id),
+ aRoot, &info, aList);
+ return;
+ }
+
+ Collector results;
+ for (nsIContent* cur = aRoot->GetFirstChild();
+ cur;
+ cur = cur->GetNextNode(aRoot)) {
+ if (cur->IsElement() &&
+ nsCSSRuleProcessor::SelectorListMatches(cur->AsElement(),
+ matchingContext,
+ aSelectorList)) {
+ if (onlyFirstMatch) {
+ aList.AppendElement(cur->AsElement());
+ return;
+ }
+ results.AppendElement(cur->AsElement());
+ }
+ }
+
+ const uint32_t len = results.Length();
+ if (len) {
+ aList.SetCapacity(len);
+ for (uint32_t i = 0; i < len; ++i) {
+ aList.AppendElement(results.ElementAt(i));
+ }
+ }
+}
+
+struct ElementHolder {
+ ElementHolder() : mElement(nullptr) {}
+ void AppendElement(Element* aElement) {
+ MOZ_ASSERT(!mElement, "Should only get one element");
+ mElement = aElement;
+ }
+ void SetCapacity(uint32_t aCapacity) { MOZ_CRASH("Don't call me!"); }
+ uint32_t Length() { return 0; }
+ Element* ElementAt(uint32_t aIndex) { return nullptr; }
+
+ Element* mElement;
+};
+
+Element*
+nsINode::QuerySelector(const nsAString& aSelector, ErrorResult& aResult)
+{
+ nsCSSSelectorList* selectorList = ParseSelectorList(aSelector, aResult);
+ if (!selectorList) {
+ // Either we failed (and aResult already has the exception), or this
+ // is a pseudo-element-only selector that matches nothing.
+ return nullptr;
+ }
+ ElementHolder holder;
+ FindMatchingElements<true, ElementHolder>(this, selectorList, holder, aResult);
+ return holder.mElement;
+}
+
+already_AddRefed<nsINodeList>
+nsINode::QuerySelectorAll(const nsAString& aSelector, ErrorResult& aResult)
+{
+ RefPtr<nsSimpleContentList> contentList = new nsSimpleContentList(this);
+
+ nsCSSSelectorList* selectorList = ParseSelectorList(aSelector, aResult);
+ if (selectorList) {
+ FindMatchingElements<false, AutoTArray<Element*, 128>>(this,
+ selectorList,
+ *contentList,
+ aResult);
+ } else {
+ // Either we failed (and aResult already has the exception), or this
+ // is a pseudo-element-only selector that matches nothing.
+ }
+
+ return contentList.forget();
+}
+
+nsresult
+nsINode::QuerySelector(const nsAString& aSelector, nsIDOMElement **aReturn)
+{
+ ErrorResult rv;
+ Element* result = nsINode::QuerySelector(aSelector, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ nsCOMPtr<nsIDOMElement> elt = do_QueryInterface(result);
+ elt.forget(aReturn);
+ return NS_OK;
+}
+
+nsresult
+nsINode::QuerySelectorAll(const nsAString& aSelector, nsIDOMNodeList **aReturn)
+{
+ ErrorResult rv;
+ *aReturn = nsINode::QuerySelectorAll(aSelector, rv).take();
+ return rv.StealNSResult();
+}
+
+Element*
+nsINode::GetElementById(const nsAString& aId)
+{
+ MOZ_ASSERT(IsElement() || IsNodeOfType(eDOCUMENT_FRAGMENT),
+ "Bogus this object for GetElementById call");
+ if (IsInUncomposedDoc()) {
+ ElementHolder holder;
+ FindMatchingElementsWithId<true>(aId, this, nullptr, holder);
+ return holder.mElement;
+ }
+
+ for (nsIContent* kid = GetFirstChild(); kid; kid = kid->GetNextNode(this)) {
+ if (!kid->IsElement()) {
+ continue;
+ }
+ nsIAtom* 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::IsCallerChrome()) {
+ Throw(aCx, NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, WrapNode(aCx, aGivenProto));
+ MOZ_ASSERT_IF(obj && ChromeOnlyAccess(),
+ xpc::IsInContentXBLScope(obj) ||
+ !xpc::UseContentXBLScope(js::GetObjectCompartment(obj)));
+ return obj;
+}
+
+already_AddRefed<nsINode>
+nsINode::CloneNode(bool aDeep, ErrorResult& aError)
+{
+ nsCOMPtr<nsINode> result;
+ aError = nsNodeUtils::CloneNodeImpl(this, aDeep, getter_AddRefs(result));
+ return result.forget();
+}
+
+nsDOMAttributeMap*
+nsINode::GetAttributes()
+{
+ if (!IsElement()) {
+ return nullptr;
+ }
+ return AsElement()->Attributes();
+}
+
+Element*
+nsINode::GetParentElementCrossingShadowRoot() const
+{
+ if (!mParent) {
+ return nullptr;
+ }
+
+ if (mParent->IsElement()) {
+ return mParent->AsElement();
+ }
+
+ ShadowRoot* shadowRoot = ShadowRoot::FromNode(mParent);
+ if (shadowRoot) {
+ nsIContent* host = shadowRoot->GetHost();
+ MOZ_ASSERT(host, "ShowRoots should always have a host");
+ MOZ_ASSERT(host->IsElement(), "ShadowRoot hosts should always be Elements");
+ return host->AsElement();
+ }
+
+ return nullptr;
+}
+
+bool
+nsINode::HasBoxQuadsSupport(JSContext* aCx, JSObject* /* unused */)
+{
+ return xpc::AccessCheck::isChrome(js::GetContextCompartment(aCx)) ||
+ Preferences::GetBool("layout.css.getBoxQuads.enabled");
+}
+
+nsINode*
+nsINode::GetScopeChainParent() const
+{
+ return nullptr;
+}
+
+void
+nsINode::AddAnimationObserver(nsIAnimationObserver* aAnimationObserver)
+{
+ AddMutationObserver(aAnimationObserver);
+ OwnerDoc()->SetMayHaveAnimationObservers();
+}
+
+void
+nsINode::AddAnimationObserverUnlessExists(
+ nsIAnimationObserver* aAnimationObserver)
+{
+ AddMutationObserverUnlessExists(aAnimationObserver);
+ OwnerDoc()->SetMayHaveAnimationObservers();
+}
+
+bool
+nsINode::IsApzAware() const
+{
+ return IsNodeApzAware();
+}
+
+bool
+nsINode::IsNodeApzAwareInternal() const
+{
+ return EventTarget::IsApzAware();
+}
+
+#ifdef MOZ_STYLO
+bool
+nsINode::IsStyledByServo() const
+{
+ return OwnerDoc()->IsStyledByServo();
+}
+#endif
diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h
new file mode 100644
index 000000000..d82f5f899
--- /dev/null
+++ b/dom/base/nsINode.h
@@ -0,0 +1,2339 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Likely.h"
+#include "mozilla/ServoTypes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h" // for member, local
+#include "nsGkAtoms.h" // for nsGkAtoms::baseURIProperty
+#include "nsIDOMNode.h"
+#include "mozilla/dom/NodeInfo.h" // member (in nsCOMPtr)
+#include "nsIVariant.h" // for use in GetUserData()
+#include "nsNodeInfoManager.h" // for use in NodePrincipal()
+#include "nsPropertyTable.h" // for typedefs
+#include "nsTObserverArray.h" // for member
+#include "mozilla/ErrorResult.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 <iosfwd>
+
+// Including 'windows.h' will #define GetClassInfo to something else.
+#ifdef XP_WIN
+#ifdef GetClassInfo
+#undef GetClassInfo
+#endif
+#endif
+
+class nsAttrAndChildArray;
+class nsChildContentList;
+struct nsCSSSelectorList;
+class nsDOMAttributeMap;
+class nsIAnimationObserver;
+class nsIContent;
+class nsIDocument;
+class nsIDOMElement;
+class nsIDOMNodeList;
+class nsIEditor;
+class nsIFrame;
+class nsIMutationObserver;
+class nsINode;
+class nsINodeList;
+class nsIPresShell;
+class nsIPrincipal;
+class nsIURI;
+class nsNodeSupportsWeakRefTearoff;
+class nsNodeWeakReference;
+class nsDOMMutationObserver;
+
+namespace mozilla {
+class EventListenerManager;
+namespace dom {
+/**
+ * @return true if aChar is what the DOM spec defines as 'space character'.
+ * http://dom.spec.whatwg.org/#space-character
+ */
+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 AccessibleNode;
+struct BoxQuadOptions;
+struct ConvertCoordinateOptions;
+class DOMPoint;
+class DOMQuad;
+class DOMRectReadOnly;
+class Element;
+class EventHandlerNonNull;
+template<typename T> class Optional;
+class OwningNodeOrString;
+template<typename> class Sequence;
+class Text;
+class TextOrElementOrDocument;
+struct DOMPointInit;
+} // namespace dom
+} // namespace mozilla
+
+#define NODE_FLAG_BIT(n_) \
+ (nsWrapperCache::FlagsType(1U) << (WRAPPER_CACHE_FLAGS_BITS_USED + (n_)))
+
+enum {
+ // 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 this node is the root of an anonymous subtree. Note that this
+ // need not be a native anonymous subtree. Any anonymous subtree, including
+ // XBL-generated ones, will do. 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_ANONYMOUS_ROOT = NODE_FLAG_BIT(2),
+
+ // 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(3),
+
+ // 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(4),
+
+ // Forces the XBL code to treat this node as if it were
+ // in the document and therefore should get bindings attached.
+ NODE_FORCE_XBL_BINDINGS = NODE_FLAG_BIT(5),
+
+ // Whether a binding manager may have a pointer to this
+ NODE_MAY_BE_IN_BINDING_MNGR = NODE_FLAG_BIT(6),
+
+ NODE_IS_EDITABLE = NODE_FLAG_BIT(7),
+
+ // For all Element nodes, NODE_MAY_HAVE_CLASS is guaranteed to be set if the
+ // node in fact has a class, but may be set even if it doesn't.
+ NODE_MAY_HAVE_CLASS = NODE_FLAG_BIT(8),
+
+ // Whether the node participates in a shadow tree.
+ NODE_IS_IN_SHADOW_TREE = NODE_FLAG_BIT(9),
+
+ // Node has an :empty or :-moz-only-whitespace selector
+ NODE_HAS_EMPTY_SELECTOR = NODE_FLAG_BIT(10),
+
+ // A child of the node has a selector such that any insertion,
+ // removal, or appending of children requires restyling the parent.
+ NODE_HAS_SLOW_SELECTOR = NODE_FLAG_BIT(11),
+
+ // 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(12),
+
+ // 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(13),
+
+ NODE_ALL_SELECTOR_FLAGS = NODE_HAS_EMPTY_SELECTOR |
+ NODE_HAS_SLOW_SELECTOR |
+ NODE_HAS_EDGE_CHILD_SELECTOR |
+ NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS,
+
+ // This node needs to go through frame construction to get a frame (or
+ // undisplayed entry).
+ NODE_NEEDS_FRAME = NODE_FLAG_BIT(14),
+
+ // 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(15),
+
+ // Set if the node has the accesskey attribute set.
+ NODE_HAS_ACCESSKEY = NODE_FLAG_BIT(16),
+
+ // Set if the node has right-to-left directionality
+ NODE_HAS_DIRECTION_RTL = NODE_FLAG_BIT(17),
+
+ // Set if the node has left-to-right directionality
+ NODE_HAS_DIRECTION_LTR = NODE_FLAG_BIT(18),
+
+ NODE_ALL_DIRECTION_FLAGS = NODE_HAS_DIRECTION_LTR |
+ NODE_HAS_DIRECTION_RTL,
+
+ NODE_CHROME_ONLY_ACCESS = NODE_FLAG_BIT(19),
+
+ NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS = NODE_FLAG_BIT(20),
+
+ // These two bits are shared by Gecko's and Servo's restyle systems for
+ // different purposes. They should not be accessed directly, and access to
+ // them should be properly guarded by asserts.
+ NODE_SHARED_RESTYLE_BIT_1 = NODE_FLAG_BIT(21),
+ NODE_SHARED_RESTYLE_BIT_2 = NODE_FLAG_BIT(22),
+
+ // Whether this node is dirty for Servo's style system.
+ NODE_IS_DIRTY_FOR_SERVO = NODE_SHARED_RESTYLE_BIT_1,
+
+ // Whether this node has dirty descendants for Servo's style system.
+ NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO = NODE_SHARED_RESTYLE_BIT_2,
+
+ // Remaining bits are node type specific.
+ NODE_TYPE_SPECIFIC_BITS_OFFSET = 23
+};
+
+// 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;
+};
+
+// 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
+// SizeOfExcludingThis from its super-class. SizeOfIncludingThis() need not be
+// defined, it is inherited from nsINode.
+// This macro isn't actually specific to nodes, and bug 956400 will move it into MFBT.
+#define NS_DECL_SIZEOF_EXCLUDING_THIS \
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+// Categories of node properties
+// 0 is global.
+#define DOM_USER_DATA 1
+#define SMIL_MAPPED_ATTR_ANIMVAL 2
+
+// IID for the nsINode interface
+#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 nsIDocument share. An instance of this interface has a list
+ * of nsIContent children and provides access to them.
+ */
+class nsINode : public mozilla::dom::EventTarget
+{
+public:
+ typedef mozilla::dom::BoxQuadOptions BoxQuadOptions;
+ typedef mozilla::dom::ConvertCoordinateOptions ConvertCoordinateOptions;
+ typedef mozilla::dom::DOMPoint DOMPoint;
+ typedef mozilla::dom::DOMPointInit DOMPointInit;
+ typedef mozilla::dom::DOMQuad DOMQuad;
+ typedef mozilla::dom::DOMRectReadOnly DOMRectReadOnly;
+ typedef mozilla::dom::OwningNodeOrString OwningNodeOrString;
+ typedef mozilla::dom::TextOrElementOrDocument TextOrElementOrDocument;
+ typedef mozilla::ErrorResult ErrorResult;
+
+ template<class T>
+ using Sequence = mozilla::dom::Sequence<T>;
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID)
+
+ // 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 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) 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, and so |aMallocSizeOf(this)| is always
+ // safe to call no matter which object it was invoked on.
+ virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ friend class nsNodeUtils;
+ friend class nsNodeWeakReference;
+ friend class nsNodeSupportsWeakRefTearoff;
+ friend class nsAttrAndChildArray;
+
+#ifdef MOZILLA_INTERNAL_API
+ explicit nsINode(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : mNodeInfo(aNodeInfo)
+ , mParent(nullptr)
+ , mBoolFlags(0)
+ , mNextSibling(nullptr)
+ , mPreviousSibling(nullptr)
+ , mFirstChild(nullptr)
+ , mSubtreeRoot(this)
+ , mSlots(nullptr)
+ {
+ }
+#endif
+
+ virtual ~nsINode();
+
+ /**
+ * Bit-flags to pass (or'ed together) to IsNodeOfType()
+ */
+ enum {
+ /** nsIContent nodes */
+ eCONTENT = 1 << 0,
+ /** nsIDocument nodes */
+ eDOCUMENT = 1 << 1,
+ /** nsIAttribute nodes */
+ eATTRIBUTE = 1 << 2,
+ /** text nodes */
+ eTEXT = 1 << 3,
+ /** xml processing instructions */
+ ePROCESSING_INSTRUCTION = 1 << 4,
+ /** comment nodes */
+ eCOMMENT = 1 << 5,
+ /** form control elements */
+ eHTML_FORM_CONTROL = 1 << 6,
+ /** document fragments */
+ eDOCUMENT_FRAGMENT = 1 << 7,
+ /** data nodes (comments, PIs, text). Nodes of this type always
+ returns a non-null value for nsIContent::GetText() */
+ eDATA_NODE = 1 << 8,
+ /** HTMLMediaElement */
+ eMEDIA = 1 << 9,
+ /** animation elements */
+ eANIMATION = 1 << 10,
+ /** filter elements that implement SVGFilterPrimitiveStandardAttributes */
+ eFILTER = 1 << 11
+ };
+
+ /**
+ * API for doing a quick check if a content is of a given
+ * type, such as Text, Document, Comment ... Use this when you can instead of
+ * checking the tag.
+ *
+ * @param aFlags what types you want to test for (see above)
+ * @return whether the content matches ALL flags passed in
+ */
+ virtual bool IsNodeOfType(uint32_t aFlags) const = 0;
+
+ virtual JSObject* WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ /**
+ * 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 nsIDocument.h
+
+ /**
+ * 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;
+
+ /**
+ * Return whether the node is an Element node
+ */
+ bool IsElement() const {
+ return GetBoolFlag(NodeIsElement);
+ }
+
+ /**
+ * Return this node as an Element. Should only be used for nodes
+ * for which IsElement() is true. This is defined inline in Element.h.
+ */
+ mozilla::dom::Element* AsElement();
+ const mozilla::dom::Element* AsElement() const;
+
+ /**
+ * Return this node as nsIContent. Should only be used for nodes for which
+ * IsContent() is true. This is defined inline in nsIContent.h.
+ */
+ nsIContent* AsContent();
+ const nsIContent* AsContent() const
+ {
+ return const_cast<nsINode*>(this)->AsContent();
+ }
+
+ /**
+ * Return this node as Text if it is one, otherwise null. This is defined
+ * inline in Text.h.
+ */
+ mozilla::dom::Text* GetAsText();
+ const mozilla::dom::Text* GetAsText() const;
+
+ virtual nsIDOMNode* AsDOMNode() = 0;
+
+ /**
+ * Return if this node has any children.
+ */
+ bool HasChildren() const { return !!mFirstChild; }
+
+ /**
+ * Get the number of children
+ * @return the number of children
+ */
+ virtual uint32_t GetChildCount() const = 0;
+
+ /**
+ * Get a child by index
+ * @param aIndex the index of the child to get
+ * @return the child, or null if index out of bounds
+ */
+ virtual nsIContent* GetChildAt(uint32_t aIndex) const = 0;
+
+ /**
+ * Get a raw pointer to the child array. This should only be used if you
+ * plan to walk a bunch of the kids, promise to make sure that nothing ever
+ * mutates (no attribute changes, not DOM tree changes, no script execution,
+ * NOTHING), and will never ever peform an out-of-bounds access here. This
+ * method may return null if there are no children, or it may return a
+ * garbage pointer. In all cases the out param will be set to the number of
+ * children.
+ */
+ virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const = 0;
+
+ /**
+ * 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
+ *
+ * If the return value is not -1, then calling GetChildAt() with that value
+ * will return aPossibleChild.
+ */
+ virtual int32_t IndexOf(const nsINode* aPossibleChild) const = 0;
+
+ /**
+ * 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.
+ */
+ nsIDocument *OwnerDoc() const
+ {
+ return mNodeInfo->GetDocument();
+ }
+
+ /**
+ * Return the "owner document" of this node as an nsINode*. Implemented
+ * in nsIDocument.h.
+ */
+ nsINode *OwnerDocAsNode() const;
+
+ /**
+ * 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
+ */
+
+ nsIDocument* GetUncomposedDoc() const
+ {
+ return IsInUncomposedDoc() ? OwnerDoc() : nullptr;
+ }
+
+ /**
+ * This method returns the owner doc if the node is in the
+ * composed document (as defined in the Shadow DOM spec), otherwise
+ * it returns null.
+ */
+ nsIDocument* GetComposedDoc() const
+ {
+ return IsInShadowTree() ?
+ GetComposedDocInternal() : GetUncomposedDoc();
+ }
+
+ /**
+ * Returns true if GetComposedDoc() would return a non-null value.
+ */
+ bool IsInComposedDoc() const
+ {
+ return IsInUncomposedDoc() || (IsInShadowTree() && GetComposedDocInternal());
+ }
+
+ /**
+ * The values returned by this function are the ones defined for
+ * nsIDOMNode.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;
+ }
+
+ inline bool IsInNamespace(int32_t aNamespace) const
+ {
+ return mNodeInfo->NamespaceID() == aNamespace;
+ }
+
+ /**
+ * 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(nsIAtom* 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(nsIAtom* 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...);
+ }
+
+ inline bool IsXULElement() const
+ {
+ return IsElement() && IsInNamespace(kNameSpaceID_XUL);
+ }
+
+ inline bool IsXULElement(nsIAtom* 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(nsIAtom* 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...);
+ }
+
+ /**
+ * Insert a content node at a particular index. This method handles calling
+ * BindToTree on the child appropriately.
+ *
+ * @param aKid the content to insert
+ * @param aIndex the index it is being inserted at (the index it will have
+ * after it is inserted)
+ * @param aNotify whether to notify the document (current document for
+ * nsIContent, and |this| for nsIDocument) that the insert has
+ * occurred
+ *
+ * @throws 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
+ * nsIDocument::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|.
+ *
+ * @throws NS_ERROR_OUT_OF_MEMORY in some cases (from BindToTree).
+ */
+ virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify) = 0;
+
+ /**
+ * 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 nsIDocument) that the append has
+ * occurred
+ *
+ * @throws 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
+ * nsIDocument::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|.
+ *
+ * @throws NS_ERROR_OUT_OF_MEMORY in some cases (from BindToTree).
+ */
+ nsresult AppendChildTo(nsIContent* aKid, bool aNotify)
+ {
+ return InsertChildAt(aKid, GetChildCount(), aNotify);
+ }
+
+ /**
+ * Remove a child from this node. This method handles calling UnbindFromTree
+ * on the child appropriately.
+ *
+ * @param aIndex the index of the child to remove
+ * @param aNotify whether to notify the document (current document for
+ * nsIContent, and |this| for nsIDocument) that the remove has
+ * occurred
+ *
+ * Note: If there is no child at aIndex, this method will simply do nothing.
+ */
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) = 0;
+
+ /**
+ * 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(nsIAtom *aPropertyName,
+ nsresult *aStatus = nullptr) const
+ {
+ return GetProperty(0, aPropertyName, aStatus);
+ }
+
+ /**
+ * Get a property associated with this node.
+ *
+ * @param aCategory category of property to get.
+ * @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(uint16_t aCategory, nsIAtom *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(nsIAtom *aPropertyName, void *aValue,
+ NSPropertyDtorFunc aDtor = nullptr,
+ bool aTransfer = false)
+ {
+ return SetProperty(0, aPropertyName, aValue, aDtor, aTransfer);
+ }
+
+ /**
+ * 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 aCategory category of property to 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.
+ * @param aOldValue [out] previous value of property.
+ *
+ * @return NS_PROPTABLE_PROP_OVERWRITTEN (success value) if the property
+ * was already set
+ * @throws NS_ERROR_OUT_OF_MEMORY if that occurs
+ */
+ nsresult SetProperty(uint16_t aCategory,
+ nsIAtom *aPropertyName, void *aValue,
+ NSPropertyDtorFunc aDtor = nullptr,
+ bool aTransfer = false,
+ void **aOldValue = nullptr);
+
+ /**
+ * A generic destructor for property values allocated with new.
+ */
+ template<class T>
+ static void DeleteProperty(void *, nsIAtom *, void *aPropertyValue, void *)
+ {
+ delete static_cast<T *>(aPropertyValue);
+ }
+
+ /**
+ * Destroys 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 DeleteProperty(nsIAtom *aPropertyName)
+ {
+ DeleteProperty(0, aPropertyName);
+ }
+
+ /**
+ * Destroys a property associated with this node. The value is destroyed
+ * using the destruction function given when that value was set.
+ *
+ * @param aCategory category of property to destroy.
+ * @param aPropertyName name of property to destroy.
+ */
+ void DeleteProperty(uint16_t aCategory, nsIAtom *aPropertyName);
+
+ /**
+ * Unset 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* UnsetProperty(nsIAtom *aPropertyName,
+ nsresult *aStatus = nullptr)
+ {
+ return UnsetProperty(0, aPropertyName, aStatus);
+ }
+
+ /**
+ * Unset 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 aCategory category of property to unset.
+ * @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* UnsetProperty(uint16_t aCategory, nsIAtom *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();
+ }
+
+ /**
+ * 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)) ?
+ reinterpret_cast<nsIContent*>(mParent) : nullptr;
+ }
+
+ /**
+ * Get the parent nsINode for this node. This can be either an nsIContent,
+ * an nsIDocument or an nsIAttribute.
+ * @return the parent node
+ */
+ nsINode* GetParentNode() const
+ {
+ return mParent;
+ }
+
+ /**
+ * 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;
+
+ /**
+ * Get the parent nsINode for this node if it is an Element.
+ * @return the parent node
+ */
+ mozilla::dom::Element* GetParentElement() const
+ {
+ return mParent && mParent->IsElement() ? mParent->AsElement() : nullptr;
+ }
+
+ /**
+ * Get the parent Element of this node, traversing over a ShadowRoot
+ * to its host if necessary.
+ */
+ mozilla::dom::Element* GetParentElementCrossingShadowRoot() 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;
+
+ nsINode* RootNode() const
+ {
+ return SubtreeRoot();
+ }
+
+ /**
+ * See nsIDOMEventTarget
+ */
+ NS_DECL_NSIDOMEVENTTARGET
+
+ virtual mozilla::EventListenerManager*
+ GetExistingListenerManager() const override;
+ virtual mozilla::EventListenerManager*
+ GetOrCreateListenerManager() override;
+
+ using mozilla::dom::EventTarget::RemoveEventListener;
+ using nsIDOMEventTarget::AddEventListener;
+ virtual void AddEventListener(const nsAString& aType,
+ mozilla::dom::EventListener* aListener,
+ const mozilla::dom::AddEventListenerOptionsOrBoolean& aOptions,
+ const mozilla::dom::Nullable<bool>& aWantsUntrusted,
+ mozilla::ErrorResult& aRv) override;
+ using nsIDOMEventTarget::AddSystemEventListener;
+
+ virtual bool IsApzAware() const override;
+
+ virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
+ virtual nsIGlobalObject* GetOwnerGlobal() const override;
+
+ /**
+ * Returns true if this is a node belonging to a document that uses the Servo
+ * style system.
+ */
+#ifdef MOZ_STYLO
+ bool IsStyledByServo() const;
+#else
+ bool IsStyledByServo() const { return false; }
+#endif
+
+ bool IsDirtyForServo() const
+ {
+ MOZ_ASSERT(IsStyledByServo());
+ return HasFlag(NODE_IS_DIRTY_FOR_SERVO);
+ }
+
+ bool HasDirtyDescendantsForServo() const
+ {
+ MOZ_ASSERT(IsStyledByServo());
+ return HasFlag(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ void SetIsDirtyForServo() {
+ MOZ_ASSERT(IsStyledByServo());
+ SetFlags(NODE_IS_DIRTY_FOR_SERVO);
+ }
+
+ void SetHasDirtyDescendantsForServo() {
+ MOZ_ASSERT(IsStyledByServo());
+ SetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ void SetIsDirtyAndHasDirtyDescendantsForServo() {
+ MOZ_ASSERT(IsStyledByServo());
+ SetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO | NODE_IS_DIRTY_FOR_SERVO);
+ }
+
+ void UnsetIsDirtyForServo() {
+ MOZ_ASSERT(IsStyledByServo());
+ UnsetFlags(NODE_IS_DIRTY_FOR_SERVO);
+ }
+
+ void UnsetHasDirtyDescendantsForServo() {
+ MOZ_ASSERT(IsStyledByServo());
+ UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
+ }
+
+ void UnsetIsDirtyAndHasDirtyDescendantsForServo() {
+ MOZ_ASSERT(IsStyledByServo());
+ UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO | NODE_IS_DIRTY_FOR_SERVO);
+ }
+
+ inline void UnsetRestyleFlagsIfGecko();
+
+ /**
+ * 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();
+ NS_ASSERTION(s->mMutationObservers.IndexOf(aMutationObserver) ==
+ nsTArray<int>::NoIndex,
+ "Observer already in the list");
+ s->mMutationObservers.AppendElement(aMutationObserver);
+ }
+
+ /**
+ * 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();
+ s->mMutationObservers.AppendElementUnlessExists(aMutationObserver);
+ }
+
+ /**
+ * 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.RemoveElement(aMutationObserver);
+ }
+ }
+
+ /**
+ * 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 *aNodeInfo, 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();
+
+ void Traverse(nsCycleCollectionTraversalCallback &cb);
+ void Unlink();
+
+ /**
+ * A list of mutation observers
+ */
+ nsTObserverArray<nsIMutationObserver*> mMutationObservers;
+
+ /**
+ * An object implementing nsIDOMNodeList for this content (childNodes)
+ * @see nsIDOMNodeList
+ * @see nsGenericHTMLElement::GetChildNodes
+ */
+ RefPtr<nsChildContentList> mChildNodes;
+
+ /**
+ * Weak reference to this node. This is cleared by the destructor of
+ * nsNodeWeakReference.
+ */
+ nsNodeWeakReference* MOZ_NON_OWNING_REF mWeakReference;
+
+ /**
+ * Number of descendant nodes in the uncomposed document that have been
+ * explicitly set as editable.
+ */
+ uint32_t mEditableDescendantCount;
+ };
+
+ /**
+ * Functions for managing flags and slots
+ */
+#ifdef DEBUG
+ nsSlots* DebugGetSlots()
+ {
+ return Slots();
+ }
+#endif
+
+ void SetFlags(FlagsType aFlagsToSet)
+ {
+ NS_ASSERTION(!(aFlagsToSet & (NODE_IS_ANONYMOUS_ROOT |
+ NODE_IS_NATIVE_ANONYMOUS_ROOT |
+ NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE |
+ NODE_DESCENDANTS_NEED_FRAMES |
+ NODE_NEEDS_FRAME |
+ NODE_CHROME_ONLY_ACCESS)) ||
+ IsNodeOfType(eCONTENT),
+ "Flag only permitted on nsIContent nodes");
+ nsWrapperCache::SetFlags(aFlagsToSet);
+ }
+
+ void UnsetFlags(FlagsType aFlagsToUnset)
+ {
+ NS_ASSERTION(!(aFlagsToUnset &
+ (NODE_IS_ANONYMOUS_ROOT |
+ NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE |
+ NODE_IS_NATIVE_ANONYMOUS_ROOT)),
+ "Trying to unset write-only flags");
+ nsWrapperCache::UnsetFlags(aFlagsToUnset);
+ }
+
+ void ChangeEditableDescendantCount(int32_t aDelta);
+
+ /**
+ * Returns the count of descendant nodes in the uncomposed
+ * document that are explicitly set as editable.
+ */
+ uint32_t EditableDescendantCount();
+
+ /**
+ * Sets the editable descendant count to 0. The editable
+ * descendant count only counts explicitly editable nodes
+ * that are in the uncomposed document so this method
+ * should be called when nodes are are removed from it.
+ */
+ void ResetEditableDescendantCount();
+
+ void SetEditableFlag(bool aEditable)
+ {
+ if (aEditable) {
+ SetFlags(NODE_IS_EDITABLE);
+ }
+ else {
+ UnsetFlags(NODE_IS_EDITABLE);
+ }
+ }
+
+ bool IsEditable() const
+ {
+#ifdef MOZILLA_INTERNAL_API
+ return IsEditableInternal();
+#else
+ return IsEditableExternal();
+#endif
+ }
+
+ /**
+ * Returns true if |this| or any of its ancestors is native anonymous.
+ */
+ bool IsInNativeAnonymousSubtree() const
+ {
+#ifdef DEBUG
+ if (HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE)) {
+ return true;
+ }
+ CheckNotNativeAnonymous();
+ return false;
+#else
+ return HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+#endif
+ }
+
+ bool IsInAnonymousSubtree() const;
+
+ // Note: This asserts |IsInAnonymousSubtree()|.
+ bool IsAnonymousContentInSVGUseSubtree() const;
+
+ // True for native anonymous content and for XBL content if the binding
+ // has chromeOnlyContent="true".
+ bool ChromeOnlyAccess() const
+ {
+ return HasFlag(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE | NODE_CHROME_ONLY_ACCESS);
+ }
+
+ bool IsInShadowTree() const
+ {
+ return HasFlag(NODE_IS_IN_SHADOW_TREE);
+ }
+
+ /**
+ * Returns true if |this| node is the common 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 IsSelectionDescendant() const
+ {
+ return IsDescendantOfCommonAncestorForRangeInSelection() ||
+ IsCommonAncestorForRangeInSelection();
+ }
+
+ /**
+ * Get the root content of an editor. So, this node must be a descendant of
+ * an editor. Note that this should be only used for getting input or textarea
+ * editor's root content. This method doesn't support HTML editors.
+ */
+ nsIContent* GetTextEditorRootContent(nsIEditor** aEditor = 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.
+ */
+ nsIContent* GetSelectionRootContent(nsIPresShell* aPresShell);
+
+ virtual nsINodeList* ChildNodes();
+ nsIContent* GetFirstChild() const { return mFirstChild; }
+ nsIContent* GetLastChild() const
+ {
+ uint32_t count;
+ nsIContent* const* children = GetChildArray(&count);
+
+ return count > 0 ? children[count - 1] : nullptr;
+ }
+
+ /**
+ * Implementation is in nsIDocument.h, because it needs to cast from
+ * nsIDocument* to nsINode*.
+ */
+ nsIDocument* GetOwnerDocument() const;
+
+ 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, and XML
+ * supports setting a per-node base URI.
+ *
+ * @return the base URI
+ */
+ virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const = 0;
+ already_AddRefed<nsIURI> GetBaseURIObject() const;
+
+ /**
+ * Facility for explicitly setting a base URI on a node.
+ */
+ nsresult SetExplicitBaseURI(nsIURI* aURI);
+ /**
+ * The explicit base URI, if set, otherwise null
+ */
+
+ /**
+ * 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;
+
+ // HTML elements named <shadow> may or may not be HTMLShadowElement. This is
+ // a way to ask an element whether it's an HTMLShadowElement.
+ virtual bool IsHTMLShadowElement() const { return false; }
+
+ // Elements named <content> may or may not be HTMLContentElement. This is a
+ // way to ask an element whether it's an HTMLContentElement.
+ virtual bool IsHTMLContentElement() const { return false; }
+
+protected:
+ nsIURI* GetExplicitBaseURI() const {
+ if (HasExplicitBaseURI()) {
+ return static_cast<nsIURI*>(GetProperty(nsGkAtoms::baseURIProperty));
+ }
+ return nullptr;
+ }
+
+public:
+ void GetTextContent(nsAString& aTextContent,
+ mozilla::ErrorResult& aError)
+ {
+ GetTextContentInternal(aTextContent, aError);
+ }
+ void SetTextContent(const nsAString& aTextContent,
+ mozilla::ErrorResult& aError)
+ {
+ SetTextContentInternal(aTextContent, aError);
+ }
+
+ mozilla::dom::Element* QuerySelector(const nsAString& aSelector,
+ mozilla::ErrorResult& aResult);
+ already_AddRefed<nsINodeList> QuerySelectorAll(const nsAString& aSelector,
+ mozilla::ErrorResult& aResult);
+
+ nsresult QuerySelector(const nsAString& aSelector, nsIDOMElement **aReturn);
+ nsresult QuerySelectorAll(const nsAString& aSelector, nsIDOMNodeList **aReturn);
+
+protected:
+ // nsIDocument overrides this with its own (faster) version. This
+ // should really only be called for elements and document fragments.
+ mozilla::dom::Element* GetElementById(const nsAString& aId);
+
+public:
+ /**
+ * Associate an object aData to aKey on this node. If aData is null any
+ * previously registered object associated to aKey on this node will
+ * be removed.
+ * Should only be used to implement the DOM Level 3 UserData API.
+ *
+ * @param aKey the key to associate the object to
+ * @param aData the object to associate to aKey on this node (may be null)
+ * @param aResult [out] the previously registered object for aKey on this
+ * node, if any
+ * @return whether adding the object succeeded
+ */
+ nsresult SetUserData(const nsAString& aKey, nsIVariant* aData,
+ nsIVariant** aResult);
+
+ /**
+ * Get the UserData object registered for a Key on this node, if any.
+ * Should only be used to implement the DOM Level 3 UserData API.
+ *
+ * @param aKey the key to get UserData for
+ * @return aResult the previously registered object for aKey on this node, if
+ * any
+ */
+ nsIVariant* GetUserData(const nsAString& aKey);
+
+ nsresult GetUserData(const nsAString& aKey, nsIVariant** aResult)
+ {
+ NS_IF_ADDREF(*aResult = GetUserData(aKey));
+
+ return NS_OK;
+ }
+
+ void LookupPrefix(const nsAString& aNamespace, nsAString& aResult);
+ bool IsDefaultNamespace(const nsAString& aNamespaceURI)
+ {
+ nsAutoString defaultNamespace;
+ LookupNamespaceURI(EmptyString(), defaultNamespace);
+ return aNamespaceURI.Equals(defaultNamespace);
+ }
+ void LookupNamespaceURI(const nsAString& aNamespacePrefix,
+ nsAString& aNamespaceURI);
+
+ nsresult IsEqualNode(nsIDOMNode* aOther, bool* aReturn);
+
+ nsIContent* GetNextSibling() const { return mNextSibling; }
+ nsIContent* GetPreviousSibling() const { return mPreviousSibling; }
+
+ /**
+ * 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;
+ nsresult Contains(nsIDOMNode* aOther, bool* aReturn);
+
+ bool UnoptimizableCCNode() const;
+
+private:
+
+ nsIDocument* GetComposedDocInternal() const;
+
+ nsIContent* GetNextNodeImpl(const nsINode* aRoot,
+ const bool aSkipChildren) const
+ {
+ // Can't use nsContentUtils::ContentIsDescendantOf here, since we
+ // can't include it here.
+#ifdef DEBUG
+ if (aRoot) {
+ 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;
+ }
+ NS_NOTREACHED("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
+ {
+ // Can't use nsContentUtils::ContentIsDescendantOf here, since we
+ // can't include it here.
+#ifdef DEBUG
+ if (aRoot) {
+ 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 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 inline style.
+ ElementMayHaveStyle,
+ // Set if the element has a name attribute set.
+ ElementHasName,
+ // Set if the element might have a contenteditable attribute set.
+ ElementMayHaveContentEditableAttr,
+ // Set if the node is the common ancestor of the start/end nodes of a Range
+ // that is in a Selection.
+ NodeIsCommonAncestorForRangeInSelection,
+ // Set if the node is a descendant of a node with the above bit set.
+ NodeIsDescendantOfCommonAncestorForRangeInSelection,
+ // 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 node has an explicit base URI stored
+ NodeHasExplicitBaseURI,
+ // 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)
+ NodeHasValidDirAttribute,
+ // Set if node has a dir attribute with a fixed value (ltr or rtl, NOT auto)
+ NodeHasFixedDir,
+ // 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 the node has dir=auto.
+ NodeHasDirAuto,
+ // Set if a node in the node's parent chain has dir=auto.
+ NodeAncestorHasDirAuto,
+ // Set if the element is in the scope of a scoped style sheet; this flag is
+ // only accurate for elements bound to a document
+ ElementIsInStyleScope,
+ // Set if the element is a scoped style sheet root
+ ElementIsScopedStyleRoot,
+ // Set if the node is handling a click.
+ NodeHandlingClick,
+ // Set if the node has had :hover selectors matched against it
+ NodeHasRelevantHoverRules,
+ // 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,
+ // 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 MayHaveStyle() const { return GetBoolFlag(ElementMayHaveStyle); }
+ bool HasName() const { return GetBoolFlag(ElementHasName); }
+ bool MayHaveContentEditableAttr() const
+ { return GetBoolFlag(ElementMayHaveContentEditableAttr); }
+ bool IsCommonAncestorForRangeInSelection() const
+ { return GetBoolFlag(NodeIsCommonAncestorForRangeInSelection); }
+ void SetCommonAncestorForRangeInSelection()
+ { SetBoolFlag(NodeIsCommonAncestorForRangeInSelection); }
+ void ClearCommonAncestorForRangeInSelection()
+ { ClearBoolFlag(NodeIsCommonAncestorForRangeInSelection); }
+ bool IsDescendantOfCommonAncestorForRangeInSelection() const
+ { return GetBoolFlag(NodeIsDescendantOfCommonAncestorForRangeInSelection); }
+ void SetDescendantOfCommonAncestorForRangeInSelection()
+ { SetBoolFlag(NodeIsDescendantOfCommonAncestorForRangeInSelection); }
+ void ClearDescendantOfCommonAncestorForRangeInSelection()
+ { ClearBoolFlag(NodeIsDescendantOfCommonAncestorForRangeInSelection); }
+
+ 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() { return GetBoolFlag(ElementHasAnimations); }
+ void SetMayHaveAnimations() { SetBoolFlag(ElementHasAnimations); }
+ void SetHasValidDir() { SetBoolFlag(NodeHasValidDirAttribute); }
+ void ClearHasValidDir() { ClearBoolFlag(NodeHasValidDirAttribute); }
+ bool HasValidDir() const { return GetBoolFlag(NodeHasValidDirAttribute); }
+ void SetHasFixedDir() {
+ MOZ_ASSERT(NodeType() != nsIDOMNode::TEXT_NODE,
+ "SetHasFixedDir on text node");
+ SetBoolFlag(NodeHasFixedDir);
+ }
+ void ClearHasFixedDir() {
+ MOZ_ASSERT(NodeType() != nsIDOMNode::TEXT_NODE,
+ "ClearHasFixedDir on text node");
+ ClearBoolFlag(NodeHasFixedDir);
+ }
+ bool HasFixedDir() const { return GetBoolFlag(NodeHasFixedDir); }
+ void SetHasDirAutoSet() {
+ MOZ_ASSERT(NodeType() != nsIDOMNode::TEXT_NODE,
+ "SetHasDirAutoSet on text node");
+ SetBoolFlag(NodeHasDirAutoSet);
+ }
+ void ClearHasDirAutoSet() {
+ MOZ_ASSERT(NodeType() != nsIDOMNode::TEXT_NODE,
+ "ClearHasDirAutoSet on text node");
+ ClearBoolFlag(NodeHasDirAutoSet);
+ }
+ bool HasDirAutoSet() const
+ { return GetBoolFlag(NodeHasDirAutoSet); }
+ void SetHasTextNodeDirectionalityMap() {
+ MOZ_ASSERT(NodeType() == nsIDOMNode::TEXT_NODE,
+ "SetHasTextNodeDirectionalityMap on non-text node");
+ SetBoolFlag(NodeHasTextNodeDirectionalityMap);
+ }
+ void ClearHasTextNodeDirectionalityMap() {
+ MOZ_ASSERT(NodeType() == nsIDOMNode::TEXT_NODE,
+ "ClearHasTextNodeDirectionalityMap on non-text node");
+ ClearBoolFlag(NodeHasTextNodeDirectionalityMap);
+ }
+ bool HasTextNodeDirectionalityMap() const {
+ MOZ_ASSERT(NodeType() == nsIDOMNode::TEXT_NODE,
+ "HasTextNodeDirectionalityMap on non-text node");
+ return GetBoolFlag(NodeHasTextNodeDirectionalityMap);
+ }
+
+ void SetHasDirAuto() { SetBoolFlag(NodeHasDirAuto); }
+ void ClearHasDirAuto() { ClearBoolFlag(NodeHasDirAuto); }
+ bool HasDirAuto() const { return GetBoolFlag(NodeHasDirAuto); }
+
+ void SetAncestorHasDirAuto() { SetBoolFlag(NodeAncestorHasDirAuto); }
+ void ClearAncestorHasDirAuto() { ClearBoolFlag(NodeAncestorHasDirAuto); }
+ bool AncestorHasDirAuto() const { return GetBoolFlag(NodeAncestorHasDirAuto); }
+
+ bool NodeOrAncestorHasDirAuto() const
+ { return HasDirAuto() || AncestorHasDirAuto(); }
+
+ void SetIsElementInStyleScope(bool aValue) {
+ MOZ_ASSERT(IsElement(), "SetIsInStyleScope on a non-Element node");
+ SetBoolFlag(ElementIsInStyleScope, aValue);
+ }
+ void SetIsElementInStyleScope() {
+ MOZ_ASSERT(IsElement(), "SetIsInStyleScope on a non-Element node");
+ SetBoolFlag(ElementIsInStyleScope);
+ }
+ void ClearIsElementInStyleScope() {
+ MOZ_ASSERT(IsElement(), "ClearIsInStyleScope on a non-Element node");
+ ClearBoolFlag(ElementIsInStyleScope);
+ }
+ bool IsElementInStyleScope() const { return GetBoolFlag(ElementIsInStyleScope); }
+
+ void SetIsScopedStyleRoot() { SetBoolFlag(ElementIsScopedStyleRoot); }
+ void ClearIsScopedStyleRoot() { ClearBoolFlag(ElementIsScopedStyleRoot); }
+ bool IsScopedStyleRoot() { return GetBoolFlag(ElementIsScopedStyleRoot); }
+ bool HasRelevantHoverRules() const { return GetBoolFlag(NodeHasRelevantHoverRules); }
+ void SetHasRelevantHoverRules() { SetBoolFlag(NodeHasRelevantHoverRules); }
+ void SetParserHasNotified() { SetBoolFlag(ParserHasNotified); };
+ bool HasParserNotified() { return GetBoolFlag(ParserHasNotified); }
+
+ void SetMayBeApzAware() { SetBoolFlag(MayBeApzAware); }
+ bool NodeMayBeApzAware() const
+ {
+ return GetBoolFlag(MayBeApzAware);
+ }
+protected:
+ void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
+ void SetIsInDocument() { SetBoolFlag(IsInDocument); }
+ void SetNodeIsContent() { SetBoolFlag(NodeIsContent); }
+ void ClearInDocument() { ClearBoolFlag(IsInDocument); }
+ 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 SetMayHaveContentEditableAttr()
+ { SetBoolFlag(ElementMayHaveContentEditableAttr); }
+ bool HasExplicitBaseURI() const { return GetBoolFlag(NodeHasExplicitBaseURI); }
+ void SetHasExplicitBaseURI() { SetBoolFlag(NodeHasExplicitBaseURI); }
+ 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(!(IsNodeOfType(eCONTENT) && 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 GetBoundMutationObservers(nsTArray<RefPtr<nsDOMMutationObserver> >& 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.SetStringBuffer(nsStringBuffer::FromString(nodeName),
+ nodeName.Length());
+ }
+ MOZ_MUST_USE 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, mozilla::ErrorResult& aRv) const;
+ bool HasChildNodes() const
+ {
+ return HasChildren();
+ }
+ uint16_t CompareDocumentPosition(nsINode& aOther) 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);
+ }
+ 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);
+ }
+ 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();
+ if (localName.IsVoid()) {
+ aLocalName.SetNull();
+ } else {
+ aLocalName.SetStringBuffer(nsStringBuffer::FromString(localName),
+ localName.Length());
+ }
+ }
+
+ nsDOMAttributeMap* GetAttributes();
+ void SetUserData(JSContext* aCx, const nsAString& aKey,
+ JS::Handle<JS::Value> aData,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aError);
+ void GetUserData(JSContext* aCx, const nsAString& aKey,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aError);
+
+ // 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
+ mozilla::dom::Element* GetPreviousElementSibling() const;
+ mozilla::dom::Element* GetNextElementSibling() const;
+
+ void Before(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
+ void After(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
+ 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;
+
+ void Prepend(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
+ void Append(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
+
+ void GetBoxQuads(const BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad> >& aResult,
+ mozilla::ErrorResult& aRv);
+
+ already_AddRefed<DOMQuad> ConvertQuadFromNode(DOMQuad& aQuad,
+ const TextOrElementOrDocument& aFrom,
+ const ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv);
+ already_AddRefed<DOMQuad> ConvertRectFromNode(DOMRectReadOnly& aRect,
+ const TextOrElementOrDocument& aFrom,
+ const ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv);
+ already_AddRefed<DOMPoint> ConvertPointFromNode(const DOMPointInit& aPoint,
+ const TextOrElementOrDocument& aFrom,
+ const ConvertCoordinateOptions& aOptions,
+ ErrorResult& aRv);
+
+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();
+ }
+
+ nsTObserverArray<nsIMutationObserver*> *GetMutationObservers()
+ {
+ return HasSlots() ? &GetExistingSlots()->mMutationObservers : nullptr;
+ }
+
+ bool IsEditableInternal() const;
+ virtual bool IsEditableExternal() const
+ {
+ return IsEditableInternal();
+ }
+
+ virtual void GetTextContentInternal(nsAString& aTextContent,
+ mozilla::ErrorResult& aError);
+ virtual void SetTextContentInternal(const nsAString& aTextContent,
+ mozilla::ErrorResult& aError)
+ {
+ }
+
+#ifdef DEBUG
+ // Note: virtual so that IsInNativeAnonymousSubtree can be called accross
+ // module boundaries.
+ virtual void CheckNotNativeAnonymous() const;
+#endif
+
+ // These are just used to implement nsIDOMNode using
+ // NS_FORWARD_NSIDOMNODE_TO_NSINODE_HELPER and for quickstubs.
+ nsresult GetParentNode(nsIDOMNode** aParentNode);
+ nsresult GetParentElement(nsIDOMElement** aParentElement);
+ nsresult GetChildNodes(nsIDOMNodeList** aChildNodes);
+ nsresult GetFirstChild(nsIDOMNode** aFirstChild);
+ nsresult GetLastChild(nsIDOMNode** aLastChild);
+ nsresult GetPreviousSibling(nsIDOMNode** aPrevSibling);
+ nsresult GetNextSibling(nsIDOMNode** aNextSibling);
+ nsresult GetOwnerDocument(nsIDOMDocument** aOwnerDocument);
+ nsresult CompareDocumentPosition(nsIDOMNode* aOther,
+ uint16_t* aReturn);
+
+ void EnsurePreInsertionValidity1(nsINode& aNewChild, nsINode* aRefChild,
+ mozilla::ErrorResult& aError);
+ void EnsurePreInsertionValidity2(bool aReplace, nsINode& aNewChild,
+ nsINode* aRefChild,
+ mozilla::ErrorResult& aError);
+ nsresult ReplaceOrInsertBefore(bool aReplace, nsIDOMNode *aNewChild,
+ nsIDOMNode *aRefChild, nsIDOMNode **aReturn);
+ nsINode* ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild,
+ nsINode* aRefChild,
+ mozilla::ErrorResult& aError);
+ nsresult RemoveChild(nsIDOMNode* aOldChild, nsIDOMNode** aReturn);
+
+ /**
+ * 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;
+
+ /**
+ * Most of the implementation of the nsINode RemoveChildAt method.
+ * Should only be called on document, element, and document fragment
+ * nodes. The aChildArray passed in should be the one for |this|.
+ *
+ * @param aIndex The index to remove at.
+ * @param aNotify Whether to notify.
+ * @param aKid The kid at aIndex. Must not be null.
+ * @param aChildArray The child array to work with.
+ * @param aMutationEvent whether to fire a mutation event for this removal.
+ */
+ void doRemoveChildAt(uint32_t aIndex, bool aNotify, nsIContent* aKid,
+ nsAttrAndChildArray& aChildArray);
+
+ /**
+ * Most of the implementation of the nsINode InsertChildAt method.
+ * Should only be called on document, element, and document fragment
+ * nodes. The aChildArray passed in should be the one for |this|.
+ *
+ * @param aKid The child to insert.
+ * @param aIndex The index to insert at.
+ * @param aNotify Whether to notify.
+ * @param aChildArray The child array to work with
+ */
+ nsresult doInsertChildAt(nsIContent* aKid, uint32_t aIndex,
+ bool aNotify, nsAttrAndChildArray& aChildArray);
+
+ /**
+ * Parse the given selector string into an nsCSSSelectorList.
+ *
+ * A null return value with a non-failing aRv means the string only
+ * contained pseudo-element selectors.
+ *
+ * A failing aRv means the string was not a valid selector.
+ */
+ nsCSSSelectorList* ParseSelectorList(const nsAString& aSelectorString,
+ mozilla::ErrorResult& aRv);
+
+public:
+ /* Event stuff that documents and elements share. This needs to be
+ NS_IMETHOD because some subclasses implement DOM methods with
+ this exact name and signature and then the calling convention
+ needs to match.
+
+ 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_(); \
+ void SetOn##name_(mozilla::dom::EventHandlerNonNull* listener);
+#define TOUCH_EVENT EVENT
+#define DOCUMENT_ONLY_EVENT EVENT
+#include "mozilla/EventNameList.h"
+#undef DOCUMENT_ONLY_EVENT
+#undef TOUCH_EVENT
+#undef EVENT
+
+ bool HasServoData() {
+#ifdef MOZ_STYLO
+ return !!mServoData.Get();
+#else
+ MOZ_CRASH("Accessing servo node data in non-stylo build");
+#endif
+ }
+
+ void ClearServoData();
+
+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:
+ // Boolean flags.
+ uint32_t mBoolFlags;
+
+protected:
+ // These references are non-owning and safe, as they are managed by
+ // nsAttrAndChildArray.
+ nsIContent* MOZ_NON_OWNING_REF mNextSibling;
+ nsIContent* MOZ_NON_OWNING_REF mPreviousSibling;
+ // This reference is non-owning and safe, since in the case of documents,
+ // it is set to null when the document gets destroyed, and in the case of
+ // other nodes, the children keep the parents alive.
+ nsIContent* MOZ_NON_OWNING_REF mFirstChild;
+
+ 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;
+
+#ifdef MOZ_STYLO
+ // Per-node data managed by Servo.
+ mozilla::ServoCell<ServoNodeData*> mServoData;
+#endif
+};
+
+inline nsIDOMNode* GetAsDOMNode(nsINode* aNode)
+{
+ return aNode ? aNode->AsDOMNode() : nullptr;
+}
+
+// Useful inline function for getting a node given an nsIContent and an
+// nsIDocument. 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 nsIDocument* 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;
+}
+
+inline nsISupports*
+ToCanonicalSupports(nsINode* aPointer)
+{
+ return aPointer;
+}
+
+#define NS_FORWARD_NSIDOMNODE_TO_NSINODE_HELPER(...) \
+ NS_IMETHOD GetNodeName(nsAString& aNodeName) __VA_ARGS__ override \
+ { \
+ aNodeName = nsINode::NodeName(); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD GetNodeValue(nsAString& aNodeValue) __VA_ARGS__ override \
+ { \
+ nsINode::GetNodeValue(aNodeValue); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD SetNodeValue(const nsAString& aNodeValue) __VA_ARGS__ override \
+ { \
+ mozilla::ErrorResult rv; \
+ nsINode::SetNodeValue(aNodeValue, rv); \
+ return rv.StealNSResult(); \
+ } \
+ NS_IMETHOD GetNodeType(uint16_t* aNodeType) __VA_ARGS__ override \
+ { \
+ *aNodeType = nsINode::NodeType(); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD GetParentNode(nsIDOMNode** aParentNode) __VA_ARGS__ override \
+ { \
+ return nsINode::GetParentNode(aParentNode); \
+ } \
+ NS_IMETHOD GetParentElement(nsIDOMElement** aParentElement) __VA_ARGS__ override \
+ { \
+ return nsINode::GetParentElement(aParentElement); \
+ } \
+ NS_IMETHOD GetChildNodes(nsIDOMNodeList** aChildNodes) __VA_ARGS__ override \
+ { \
+ return nsINode::GetChildNodes(aChildNodes); \
+ } \
+ NS_IMETHOD GetFirstChild(nsIDOMNode** aFirstChild) __VA_ARGS__ override \
+ { \
+ return nsINode::GetFirstChild(aFirstChild); \
+ } \
+ NS_IMETHOD GetLastChild(nsIDOMNode** aLastChild) __VA_ARGS__ override \
+ { \
+ return nsINode::GetLastChild(aLastChild); \
+ } \
+ NS_IMETHOD GetPreviousSibling(nsIDOMNode** aPreviousSibling) __VA_ARGS__ override \
+ { \
+ return nsINode::GetPreviousSibling(aPreviousSibling); \
+ } \
+ NS_IMETHOD GetNextSibling(nsIDOMNode** aNextSibling) __VA_ARGS__ override \
+ { \
+ return nsINode::GetNextSibling(aNextSibling); \
+ } \
+ NS_IMETHOD GetOwnerDocument(nsIDOMDocument** aOwnerDocument) __VA_ARGS__ override \
+ { \
+ return nsINode::GetOwnerDocument(aOwnerDocument); \
+ } \
+ NS_IMETHOD InsertBefore(nsIDOMNode* aNewChild, nsIDOMNode* aRefChild, nsIDOMNode** aResult) __VA_ARGS__ override \
+ { \
+ return ReplaceOrInsertBefore(false, aNewChild, aRefChild, aResult); \
+ } \
+ NS_IMETHOD ReplaceChild(nsIDOMNode* aNewChild, nsIDOMNode* aOldChild, nsIDOMNode** aResult) __VA_ARGS__ override \
+ { \
+ return ReplaceOrInsertBefore(true, aNewChild, aOldChild, aResult); \
+ } \
+ NS_IMETHOD RemoveChild(nsIDOMNode* aOldChild, nsIDOMNode** aResult) __VA_ARGS__ override \
+ { \
+ return nsINode::RemoveChild(aOldChild, aResult); \
+ } \
+ NS_IMETHOD AppendChild(nsIDOMNode* aNewChild, nsIDOMNode** aResult) __VA_ARGS__ override \
+ { \
+ return InsertBefore(aNewChild, nullptr, aResult); \
+ } \
+ NS_IMETHOD HasChildNodes(bool* aResult) __VA_ARGS__ override \
+ { \
+ *aResult = nsINode::HasChildNodes(); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD CloneNode(bool aDeep, uint8_t aArgc, nsIDOMNode** aResult) __VA_ARGS__ override \
+ { \
+ if (aArgc == 0) { \
+ aDeep = true; \
+ } \
+ mozilla::ErrorResult rv; \
+ nsCOMPtr<nsINode> clone = nsINode::CloneNode(aDeep, rv); \
+ if (rv.Failed()) { \
+ return rv.StealNSResult(); \
+ } \
+ *aResult = clone.forget().take()->AsDOMNode(); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD Normalize() __VA_ARGS__ override \
+ { \
+ nsINode::Normalize(); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD GetNamespaceURI(nsAString& aNamespaceURI) __VA_ARGS__ override \
+ { \
+ nsINode::GetNamespaceURI(aNamespaceURI); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD GetPrefix(nsAString& aPrefix) __VA_ARGS__ override \
+ { \
+ nsINode::GetPrefix(aPrefix); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD GetLocalName(nsAString& aLocalName) __VA_ARGS__ override \
+ { \
+ aLocalName = nsINode::LocalName(); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD UnusedPlaceholder(bool* aResult) __VA_ARGS__ override \
+ { \
+ *aResult = false; \
+ return NS_OK; \
+ } \
+ NS_IMETHOD GetDOMBaseURI(nsAString& aBaseURI) __VA_ARGS__ override \
+ { \
+ return nsINode::GetBaseURI(aBaseURI); \
+ } \
+ NS_IMETHOD CompareDocumentPosition(nsIDOMNode* aOther, uint16_t* aResult) __VA_ARGS__ override \
+ { \
+ return nsINode::CompareDocumentPosition(aOther, aResult); \
+ } \
+ NS_IMETHOD GetTextContent(nsAString& aTextContent) __VA_ARGS__ override \
+ { \
+ mozilla::ErrorResult rv; \
+ nsINode::GetTextContent(aTextContent, rv); \
+ return rv.StealNSResult(); \
+ } \
+ NS_IMETHOD SetTextContent(const nsAString& aTextContent) __VA_ARGS__ override \
+ { \
+ mozilla::ErrorResult rv; \
+ nsINode::SetTextContent(aTextContent, rv); \
+ return rv.StealNSResult(); \
+ } \
+ NS_IMETHOD LookupPrefix(const nsAString& aNamespaceURI, nsAString& aResult) __VA_ARGS__ override \
+ { \
+ nsINode::LookupPrefix(aNamespaceURI, aResult); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD IsDefaultNamespace(const nsAString& aNamespaceURI, bool* aResult) __VA_ARGS__ override \
+ { \
+ *aResult = nsINode::IsDefaultNamespace(aNamespaceURI); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD LookupNamespaceURI(const nsAString& aPrefix, nsAString& aResult) __VA_ARGS__ override \
+ { \
+ nsINode::LookupNamespaceURI(aPrefix, aResult); \
+ return NS_OK; \
+ } \
+ NS_IMETHOD IsEqualNode(nsIDOMNode* aArg, bool* aResult) __VA_ARGS__ override \
+ { \
+ return nsINode::IsEqualNode(aArg, aResult); \
+ } \
+ NS_IMETHOD SetUserData(const nsAString& aKey, nsIVariant* aData, nsIVariant** aResult) __VA_ARGS__ override \
+ { \
+ return nsINode::SetUserData(aKey, aData, aResult); \
+ } \
+ NS_IMETHOD GetUserData(const nsAString& aKey, nsIVariant** aResult) __VA_ARGS__ override \
+ { \
+ return nsINode::GetUserData(aKey, aResult); \
+ } \
+ NS_IMETHOD Contains(nsIDOMNode* aOther, bool* aResult) __VA_ARGS__ override \
+ { \
+ return nsINode::Contains(aOther, aResult); \
+ }
+
+#define NS_FORWARD_NSIDOMNODE_TO_NSINODE \
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE_HELPER(final)
+
+#define NS_FORWARD_NSIDOMNODE_TO_NSINODE_OVERRIDABLE \
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE_HELPER()
+
+#endif /* nsINode_h___ */
diff --git a/dom/base/nsINodeList.h b/dom/base/nsINodeList.h
new file mode 100644
index 000000000..15ca3a39a
--- /dev/null
+++ b/dom/base/nsINodeList.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMNodeList.h"
+#include "nsWrapperCache.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 nsIDOMNodeList,
+ 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;
+
+ using nsIDOMNodeList::Item;
+
+ uint32_t Length()
+ {
+ uint32_t length;
+ GetLength(&length);
+ return length;
+ }
+ virtual nsIContent* Item(uint32_t aIndex) = 0;
+ nsIContent* IndexedGetter(uint32_t aIndex, bool& aFound)
+ {
+ nsIContent* item = Item(aIndex);
+ aFound = !!item;
+ return item;
+ }
+};
+
+#define NS_NODELIST_OFFSET_AND_INTERFACE_TABLE_BEGIN(_class) \
+ NS_OFFSET_AND_INTERFACE_TABLE_BEGIN_AMBIGUOUS(_class, nsINodeList)
+
+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 000000000..953a25911
--- /dev/null
+++ b/dom/base/nsIObjectLoadingContent.idl
@@ -0,0 +1,191 @@
+/* -*- 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 nsIRequest;
+interface nsIFrame;
+interface nsIObjectFrame;
+interface nsIPluginTag;
+interface nsIDOMElement;
+interface nsIDOMClientRect;
+interface nsIURI;
+
+%{C++
+class nsNPAPIPluginInstance;
+%}
+[ptr] native nsNPAPIPluginInstancePtr(nsNPAPIPluginInstance);
+
+/**
+ * This interface represents a content node that loads objects.
+ *
+ * Please make sure to update the MozObjectLoadingContent WebIDL
+ * interface to mirror this interface when changing it.
+ */
+
+[scriptable, 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_PLUGIN = 2;
+ const unsigned long TYPE_DOCUMENT = 3;
+ const unsigned long TYPE_NULL = 4;
+
+ const unsigned long PLUGIN_ACTIVE = 0xFF;
+
+ // The content type is not supported (e.g. plugin not installed)
+ const unsigned long PLUGIN_UNSUPPORTED = 0;
+ // Showing alternate content
+ const unsigned long PLUGIN_ALTERNATE = 1;
+ // The plugin exists, but is disabled
+ const unsigned long PLUGIN_DISABLED = 2;
+ // The plugin is blocklisted and disabled
+ const unsigned long PLUGIN_BLOCKLISTED = 3;
+ // The plugin is considered outdated, but not disabled
+ const unsigned long PLUGIN_OUTDATED = 4;
+ // The plugin has crashed
+ const unsigned long PLUGIN_CRASHED = 5;
+ // Suppressed by security policy
+ const unsigned long PLUGIN_SUPPRESSED = 6;
+ // Blocked by content policy
+ const unsigned long PLUGIN_USER_DISABLED = 7;
+ /// ** All values >= PLUGIN_CLICK_TO_PLAY are plugin placeholder types that
+ /// would be replaced by a real plugin if activated (playPlugin())
+ /// ** Furthermore, values >= PLUGIN_CLICK_TO_PLAY and
+ /// <= PLUGIN_VULNERABLE_NO_UPDATE are click-to-play types.
+ // The plugin is disabled until the user clicks on it
+ const unsigned long PLUGIN_CLICK_TO_PLAY = 8;
+ // The plugin is vulnerable (update available)
+ const unsigned long PLUGIN_VULNERABLE_UPDATABLE = 9;
+ // The plugin is vulnerable (no update available)
+ const unsigned long PLUGIN_VULNERABLE_NO_UPDATE = 10;
+
+ /**
+ * 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_NULL will be returned.
+ */
+ unsigned long getContentTypeForMIMEType(in AUTF8String aMimeType);
+
+ /**
+ * Returns the base URI of the object as seen by plugins. This differs from
+ * the normal codebase in that it takes <param> tags and plugin-specific
+ * quirks into account.
+ */
+ [noscript] readonly attribute nsIURI baseURI;
+
+ /**
+ * Returns the plugin instance if it has already been instantiated. This
+ * will never instantiate the plugin and so is safe to call even when
+ * content script must not execute.
+ */
+ [noscript] readonly attribute nsNPAPIPluginInstancePtr pluginInstance;
+
+ /**
+ * Tells the content about an associated object frame.
+ * This can be called multiple times for different frames.
+ *
+ * This is noscript because this is an internal method that will go away, and
+ * because nsIObjectFrame is unscriptable.
+ */
+ [noscript] void hasNewFrame(in nsIObjectFrame aFrame);
+
+ /**
+ * If this object is in going to be printed, this method
+ * returns the nsIObjectFrame object which should be used when
+ * printing the plugin. The returned nsIFrame is in the original document,
+ * not in the static clone.
+ */
+ [noscript] nsIFrame getPrintFrame();
+
+ /*
+ * Notifications from pluginhost that our instance crashed or was destroyed.
+ */
+ [noscript] void pluginDestroyed();
+ [noscript] void pluginCrashed(in nsIPluginTag pluginTag,
+ in AString pluginDumpID,
+ in AString browserDumpID,
+ in boolean submittedCrashReport);
+
+ /**
+ * This method will play a plugin that has been stopped by click-to-play.
+ */
+ void playPlugin();
+
+ /**
+ * 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);
+
+ /**
+ * This attribute will return true if the current content type has been
+ * activated, either explicitly or by passing checks that would have it be
+ * click-to-play.
+ */
+ readonly attribute boolean activated;
+
+ [noscript] void stopPluginInstance();
+
+ [noscript] void syncStartPluginInstance();
+ [noscript] void asyncStartPluginInstance();
+
+ /**
+ * 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;
+
+ /**
+ * The plugin's current state of fallback content. This property
+ * only makes sense if the plugin is not activated.
+ */
+ readonly attribute unsigned long pluginFallbackType;
+
+ /**
+ * If this object currently owns a running plugin, regardless of whether or
+ * not one is pending spawn/despawn.
+ */
+ readonly attribute bool hasRunningPlugin;
+
+ /**
+ * If this plugin runs out-of-process, it has a runID to differentiate
+ * between different times the plugin process has been instantiated.
+ *
+ * This throws NS_ERROR_NOT_IMPLEMENTED for in-process plugins.
+ */
+ readonly attribute unsigned long runID;
+};
diff --git a/dom/base/nsIRemoteWindowContext.idl b/dom/base/nsIRemoteWindowContext.idl
new file mode 100644
index 000000000..d85e166b0
--- /dev/null
+++ b/dom/base/nsIRemoteWindowContext.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 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 "nsISupports.idl"
+
+interface nsIURI;
+
+[scriptable, builtinclass, uuid(94f4a92b-752e-4fd9-8345-11b069ca19f3)]
+interface nsIRemoteWindowContext : nsISupports
+{
+ void openURI(in nsIURI aURI);
+};
diff --git a/dom/base/nsIScriptChannel.idl b/dom/base/nsIScriptChannel.idl
new file mode 100644
index 000000000..0e57313f7
--- /dev/null
+++ b/dom/base/nsIScriptChannel.idl
@@ -0,0 +1,68 @@
+/* -*- 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.
+ */
+[scriptable, 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;
+};
diff --git a/dom/base/nsIScriptContext.h b/dom/base/nsIScriptContext.h
new file mode 100644
index 000000000..8274e22f9
--- /dev/null
+++ b/dom/base/nsIScriptContext.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 nsIScriptContext_h__
+#define nsIScriptContext_h__
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "jspubtd.h"
+#include "js/GCAPI.h"
+
+class nsIScriptGlobalObject;
+
+#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;
+
+ /**
+ * Initialize the context generally. Does not create a global object.
+ **/
+ virtual nsresult InitContext() = 0;
+
+ /**
+ * Check to see if context is as yet intialized. Used to prevent
+ * reentrancy issues during the initialization process.
+ *
+ * @return true if initialized, false if not
+ *
+ */
+ virtual bool IsContextInitialized() = 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, always call
+ * WillInitializeContext() before calling InitContext(), and always
+ * call DidInitializeContext() when a context is fully
+ * (successfully) initialized.
+ */
+ virtual nsresult InitClasses(JS::Handle<JSObject*> aGlobalObj) = 0;
+
+ /**
+ * Tell the context we're about to be reinitialize it.
+ */
+ virtual void WillInitializeContext() = 0;
+
+ /**
+ * Tell the context we're done reinitializing it.
+ */
+ virtual void DidInitializeContext() = 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 CompileScript 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(JSScript* aScript, nsresult aStatus) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIOffThreadScriptReceiver, NS_IOFFTHREADSCRIPTRECEIVER_IID)
+
+#endif // nsIScriptContext_h__
diff --git a/dom/base/nsIScriptElement.h b/dom/base/nsIScriptElement.h
new file mode 100644
index 000000000..470d51c94
--- /dev/null
+++ b/dom/base/nsIScriptElement.h
@@ -0,0 +1,329 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIScriptElement_h___
+#define nsIScriptElement_h___
+
+#include "nsISupports.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsIScriptLoaderObserver.h"
+#include "nsWeakPtr.h"
+#include "nsIParser.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIDOMHTMLScriptElement.h"
+#include "mozilla/CORSMode.h"
+
+#define NS_ISCRIPTELEMENT_IID \
+{ 0xe60fca9b, 0x1b96, 0x4e4e, \
+ { 0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c } }
+
+/**
+ * Internal interface implemented by script elements
+ */
+class nsIScriptElement : public nsIScriptLoaderObserver {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTELEMENT_IID)
+
+ explicit nsIScriptElement(mozilla::dom::FromParser aFromParser)
+ : mLineNumber(1),
+ mAlreadyStarted(false),
+ mMalformed(false),
+ mDoneAddingChildren(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
+ aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
+ mForceAsync(aFromParser == mozilla::dom::NOT_FROM_PARSER ||
+ aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT),
+ mFrozen(false),
+ mDefer(false),
+ mAsync(false),
+ mExternal(false),
+ mParserCreated(aFromParser == mozilla::dom::FROM_PARSER_FRAGMENT ?
+ mozilla::dom::NOT_FROM_PARSER : aFromParser),
+ // Fragment parser-created scripts (if executable)
+ // behave like script-created scripts.
+ mCreatorParser(nullptr)
+ {
+ }
+
+ /**
+ * Content type identifying the scripting language. Can be empty, in
+ * which case javascript will be assumed.
+ * Return false if type attribute is not found.
+ */
+ virtual bool GetScriptType(nsAString& type) = 0;
+
+ /**
+ * Location of script source text. Can return null, in which case
+ * this is assumed to be an inline script element.
+ */
+ nsIURI* GetScriptURI()
+ {
+ NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
+ return mUri;
+ }
+
+ /**
+ * Script source text for inline script elements.
+ */
+ virtual void GetScriptText(nsAString& text) = 0;
+
+ virtual void GetScriptCharset(nsAString& charset) = 0;
+
+ /**
+ * Freezes the return values of GetScriptDeferred(), GetScriptAsync() and
+ * GetScriptURI() so that subsequent modifications to the attributes don't
+ * change execution behavior.
+ */
+ virtual void FreezeUriAsyncDefer() = 0;
+
+ /**
+ * Is the script deferred. Currently only supported by HTML scripts.
+ */
+ bool GetScriptDeferred()
+ {
+ NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
+ return mDefer;
+ }
+
+ /**
+ * Is the script async. Currently only supported by HTML scripts.
+ */
+ bool GetScriptAsync()
+ {
+ NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
+ return mAsync;
+ }
+
+ /**
+ * Is the script an external script?
+ */
+ bool GetScriptExternal()
+ {
+ NS_PRECONDITION(mFrozen, "Not ready for this call yet!");
+ return mExternal;
+ }
+
+ /**
+ * Returns how the element was created.
+ */
+ mozilla::dom::FromParser GetParserCreated()
+ {
+ return mParserCreated;
+ }
+
+ void SetScriptLineNumber(uint32_t aLineNumber)
+ {
+ mLineNumber = aLineNumber;
+ }
+ uint32_t GetScriptLineNumber()
+ {
+ return mLineNumber;
+ }
+
+ void SetIsMalformed()
+ {
+ mMalformed = true;
+ }
+ bool IsMalformed()
+ {
+ return mMalformed;
+ }
+
+ void PreventExecution()
+ {
+ mAlreadyStarted = true;
+ }
+
+ void LoseParserInsertedness()
+ {
+ mFrozen = false;
+ mUri = nullptr;
+ mCreatorParser = nullptr;
+ mParserCreated = mozilla::dom::NOT_FROM_PARSER;
+ bool async = false;
+ nsCOMPtr<nsIDOMHTMLScriptElement> htmlScript = do_QueryInterface(this);
+ if (htmlScript) {
+ htmlScript->GetAsync(&async);
+ }
+ mForceAsync = !async;
+ }
+
+ void SetCreatorParser(nsIParser* aParser)
+ {
+ mCreatorParser = do_GetWeakReference(aParser);
+ }
+
+ /**
+ * Unblocks the creator parser
+ */
+ void UnblockParser()
+ {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->UnblockParser();
+ }
+ }
+
+ /**
+ * Attempts to resume parsing asynchronously
+ */
+ void ContinueParserAsync()
+ {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->ContinueInterruptedParsingAsync();
+ }
+ }
+
+ /**
+ * Informs the creator parser that the evaluation of this script is starting
+ */
+ void BeginEvaluating()
+ {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->PushDefinedInsertionPoint();
+ }
+ }
+
+ /**
+ * Informs the creator parser that the evaluation of this script is ending
+ */
+ void EndEvaluating()
+ {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->PopDefinedInsertionPoint();
+ }
+ }
+
+ /**
+ * Retrieves a pointer to the creator parser if this has one or null if not
+ */
+ already_AddRefed<nsIParser> GetCreatorParser()
+ {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ return parser.forget();
+ }
+
+ /**
+ * This method is called when the parser finishes creating the script
+ * element's children, if any are present.
+ *
+ * @return whether the parser will be blocked while this script is being
+ * loaded
+ */
+ bool AttemptToExecute()
+ {
+ mDoneAddingChildren = true;
+ bool block = MaybeProcessScript();
+ if (!mAlreadyStarted) {
+ // Need to lose parser-insertedness here to allow another script to cause
+ // execution later.
+ LoseParserInsertedness();
+ }
+ return block;
+ }
+
+ /**
+ * Get the CORS mode of the script element
+ */
+ virtual mozilla::CORSMode GetCORSMode() const
+ {
+ /* Default to no CORS */
+ return mozilla::CORS_NONE;
+ }
+
+ /**
+ * Fire an error event
+ */
+ virtual nsresult FireErrorEvent() = 0;
+
+protected:
+ /**
+ * Processes the script if it's in the document-tree and links to or
+ * contains a script. Once it has been evaluated there is no way to make it
+ * reevaluate the script, you'll have to create a new element. This also means
+ * that when adding a src attribute to an element that already contains an
+ * inline script, the script referenced by the src attribute will not be
+ * loaded.
+ *
+ * In order to be able to use multiple childNodes, or to use the
+ * fallback mechanism of using both inline script and linked script you have
+ * to add all attributes and childNodes before adding the element to the
+ * document-tree.
+ *
+ * @return whether the parser will be blocked while this script is being
+ * loaded
+ */
+ virtual bool MaybeProcessScript() = 0;
+
+ /**
+ * The start line number of the script.
+ */
+ uint32_t mLineNumber;
+
+ /**
+ * The "already started" flag per HTML5.
+ */
+ bool mAlreadyStarted;
+
+ /**
+ * The script didn't have an end tag.
+ */
+ bool mMalformed;
+
+ /**
+ * False if parser-inserted but the parser hasn't triggered running yet.
+ */
+ bool mDoneAddingChildren;
+
+ /**
+ * If true, the .async property returns true instead of reflecting the
+ * content attribute.
+ */
+ bool mForceAsync;
+
+ /**
+ * Whether src, defer and async are frozen.
+ */
+ bool mFrozen;
+
+ /**
+ * The effective deferredness.
+ */
+ bool mDefer;
+
+ /**
+ * The effective asyncness.
+ */
+ bool mAsync;
+
+ /**
+ * The effective externalness. A script can be external with mUri being null
+ * if the src attribute contained an invalid URL string.
+ */
+ bool mExternal;
+
+ /**
+ * Whether this element was parser-created.
+ */
+ mozilla::dom::FromParser mParserCreated;
+
+ /**
+ * The effective src (or null if no src).
+ */
+ nsCOMPtr<nsIURI> mUri;
+
+ /**
+ * The creator parser of a non-defer, non-async parser-inserted script.
+ */
+ nsWeakPtr mCreatorParser;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptElement, NS_ISCRIPTELEMENT_IID)
+
+#endif // nsIScriptElement_h___
diff --git a/dom/base/nsIScriptGlobalObject.h b/dom/base/nsIScriptGlobalObject.h
new file mode 100644
index 000000000..771912561
--- /dev/null
+++ b/dom/base/nsIScriptGlobalObject.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 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 {
+namespace dom {
+struct ErrorEventInit;
+} // namespace dom
+} // namespace mozilla
+
+// 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.
+bool
+NS_HandleScriptError(nsIScriptGlobalObject *aScriptGlobal,
+ const mozilla::dom::ErrorEventInit &aErrorEvent,
+ nsEventStatus *aStatus);
+
+
+#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.
+ */
+ virtual nsresult HandleScriptError(
+ const mozilla::dom::ErrorEventInit &aErrorEventInit,
+ nsEventStatus *aEventStatus) {
+ NS_ENSURE_STATE(NS_HandleScriptError(this, aErrorEventInit, aEventStatus));
+ return NS_OK;
+ }
+
+ virtual bool IsBlackForCC(bool aTracingNeeded = true) { return false; }
+
+protected:
+ virtual ~nsIScriptGlobalObject() {}
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptGlobalObject,
+ NS_ISCRIPTGLOBALOBJECT_IID)
+
+#endif
diff --git a/dom/base/nsIScriptLoaderObserver.idl b/dom/base/nsIScriptLoaderObserver.idl
new file mode 100644
index 000000000..ed7196525
--- /dev/null
+++ b/dom/base/nsIScriptLoaderObserver.idl
@@ -0,0 +1,47 @@
+/* -*- 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 nsIScriptElement;
+interface nsIURI;
+
+[scriptable, uuid(7b787204-76fb-4764-96f1-fb7a666db4f4)]
+interface nsIScriptLoaderObserver : nsISupports {
+
+ /**
+ * The script is available for evaluation. For inline scripts, this
+ * method will be called synchronously. For externally loaded scripts,
+ * this method will be called when the load completes.
+ *
+ * @param aResult A result code representing the result of loading
+ * a script. If this is a failure code, script evaluation
+ * will not occur.
+ * @param aElement The element being processed.
+ * @param aIsInline Is this an inline script or externally loaded?
+ * @param aURI What is the URI of the script (the document URI if
+ * it is inline).
+ * @param aLineNo At what line does the script appear (generally 1
+ * if it is a loaded script).
+ */
+ void scriptAvailable(in nsresult aResult,
+ in nsIScriptElement aElement,
+ in boolean aIsInline,
+ in nsIURI aURI,
+ in int32_t aLineNo);
+
+ /**
+ * The script has been evaluated.
+ *
+ * @param aResult A result code representing the success or failure of
+ * the script evaluation.
+ * @param aElement The element being processed.
+ * @param aIsInline Is this an inline script or externally loaded?
+ */
+ void scriptEvaluated(in nsresult aResult,
+ in nsIScriptElement aElement,
+ in boolean aIsInline);
+
+};
diff --git a/dom/base/nsIScriptNameSpaceManager.h b/dom/base/nsIScriptNameSpaceManager.h
new file mode 100644
index 000000000..219816187
--- /dev/null
+++ b/dom/base/nsIScriptNameSpaceManager.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 nsIScriptNameSpaceManager_h__
+#define nsIScriptNameSpaceManager_h__
+
+#define JAVASCRIPT_GLOBAL_CONSTRUCTOR_CATEGORY \
+ "JavaScript-global-constructor"
+
+#define JAVASCRIPT_GLOBAL_PROPERTY_CATEGORY \
+ "JavaScript-global-property"
+
+// a global property that is only accessible to privileged script
+#define JAVASCRIPT_GLOBAL_PRIVILEGED_PROPERTY_CATEGORY \
+ "JavaScript-global-privileged-property"
+
+#endif /* nsIScriptNameSpaceManager_h__ */
diff --git a/dom/base/nsIScriptObjectPrincipal.h b/dom/base/nsIScriptObjectPrincipal.h
new file mode 100644
index 000000000..c75ba7928
--- /dev/null
+++ b/dom/base/nsIScriptObjectPrincipal.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptObjectPrincipal,
+ NS_ISCRIPTOBJECTPRINCIPAL_IID)
+
+#endif // nsIScriptObjectPrincipal_h__
diff --git a/dom/base/nsIScriptTimeoutHandler.h b/dom/base/nsIScriptTimeoutHandler.h
new file mode 100644
index 000000000..722f68e78
--- /dev/null
+++ b/dom/base/nsIScriptTimeoutHandler.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 nsIScriptTimeoutHandler_h___
+#define nsIScriptTimeoutHandler_h___
+
+#include "nsITimeoutHandler.h"
+#include "nsTArray.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Function.h"
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+namespace dom {
+class Function;
+} // namespace dom
+} // namespace mozilla
+
+#define NS_ISCRIPTTIMEOUTHANDLER_IID \
+{ 0x53c8e80e, 0xcc78, 0x48bc, \
+ { 0xba, 0x63, 0x0c, 0xb9, 0xdb, 0xf7, 0x06, 0x34 } }
+
+/**
+ * Abstraction of the script objects etc required to do timeouts in a
+ * language agnostic way.
+ */
+
+class nsIScriptTimeoutHandler : public nsITimeoutHandler
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTTIMEOUTHANDLER_IID)
+
+ // Get the Function to call. If this returns nullptr, GetHandlerText() will
+ // be called to get the string.
+ virtual mozilla::dom::Function* GetCallback() = 0;
+
+ // Get the handler text of not a compiled object.
+ virtual const nsAString& GetHandlerText() = 0;
+
+ // Get the location of the script.
+ // Note: The memory pointed to by aFileName is owned by the
+ // nsIScriptTimeoutHandler and should not be freed by the caller.
+
+ // If we have a Function, get the arguments for passing to it.
+ virtual const nsTArray<JS::Value>& GetArgs() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptTimeoutHandler,
+ NS_ISCRIPTTIMEOUTHANDLER_IID)
+
+#endif // nsIScriptTimeoutHandler_h___
diff --git a/dom/base/nsISelection.idl b/dom/base/nsISelection.idl
new file mode 100644
index 000000000..de43521e1
--- /dev/null
+++ b/dom/base/nsISelection.idl
@@ -0,0 +1,170 @@
+/* -*- 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"
+
+/* THIS IS A PUBLIC INTERFACE */
+
+interface nsIDOMNode;
+interface nsIDOMRange;
+interface nsINode;
+
+%{C++
+namespace mozilla {
+namespace dom {
+class Selection;
+} // namespace dom
+} // namespace mozilla
+%}
+
+/**
+ * Interface for manipulating and querying the current selected range
+ * of nodes within the document.
+ *
+ * @version 1.0
+ */
+
+[builtinclass, uuid(e0a4d4b3-f34e-44bd-b1f2-4e3bde9b6915)]
+interface nsISelection : nsISupports
+{
+ /**
+ * Returns the node in which the selection begins.
+ */
+ readonly attribute nsIDOMNode anchorNode;
+
+ /**
+ * The offset within the (text) node where the selection begins.
+ */
+ readonly attribute long anchorOffset;
+
+ /**
+ * Returns the node in which the selection ends.
+ */
+ readonly attribute nsIDOMNode focusNode;
+
+ /**
+ * The offset within the (text) node where the selection ends.
+ */
+ readonly attribute long focusOffset;
+
+ /**
+ * Indicates if the selection is collapsed or not.
+ */
+ readonly attribute boolean isCollapsed;
+ [noscript,notxpcom,nostdcall] boolean collapsed();
+
+ /**
+ * Returns the number of ranges in the selection.
+ */
+ readonly attribute long rangeCount;
+
+ /**
+ * Returns the range at the specified index.
+ */
+ nsIDOMRange getRangeAt(in long index);
+
+ /**
+ * Collapses the selection to a single point, at the specified offset
+ * in the given DOM node. When the selection is collapsed, and the content
+ * is focused and editable, the caret will blink there.
+ * @param parentNode The given dom node where the selection will be set
+ * @param offset Where in given dom node to place the selection (the offset into the given node)
+ */
+ void collapse(in nsIDOMNode parentNode, in long offset);
+ [noscript] void collapseNative(in nsINode parentNode, in long offset);
+
+ /**
+ * 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 parentNode The node where the selection will be extended to
+ * @param offset Where in node to place the offset in the new selection end
+ */
+ void extend(in nsIDOMNode parentNode, in long offset);
+ [noscript] void extendNative(in nsINode parentNode, in long offset);
+
+ /**
+ * 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.
+ */
+ void collapseToStart();
+
+ /**
+ * 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.
+ */
+ void collapseToEnd();
+
+ /**
+ * Indicates whether the node is part of the selection. If partlyContained
+ * is set to PR_TRUE, the function returns true when some part of the node
+ * is part of the selection. If partlyContained is set to PR_FALSE, the
+ * function only returns true when the entire node is part of the selection.
+ */
+ boolean containsNode(in nsIDOMNode node, in boolean partlyContained);
+
+ /**
+ * Adds all children of the specified node to the selection.
+ * @param parentNode the parent of the children to be added to the selection.
+ */
+ void selectAllChildren(in nsIDOMNode parentNode);
+
+ /**
+ * Adds a range to the current selection.
+ */
+ void addRange(in nsIDOMRange range);
+
+ /**
+ * Removes a range from the current selection.
+ */
+ void removeRange(in nsIDOMRange range);
+
+ /**
+ * Removes all ranges from the current selection.
+ */
+ void removeAllRanges();
+
+ /**
+ * Deletes this selection from document the nodes belong to.
+ */
+ void deleteFromDocument();
+
+ /**
+ * Returns the whole selection into a plain text string.
+ */
+ DOMString toString();
+
+ /**
+ * 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" }
+ *
+ * @returns NS_ERROR_NOT_IMPLEMENTED if the granularity is "sentence",
+ * "sentenceboundary", "paragraph", "paragraphboundary", or
+ * "documentboundary". Returns NS_ERROR_INVALID_ARG if alter, direction,
+ * or granularity has an unrecognized value.
+ */
+ void modify(in DOMString alter, in DOMString direction,
+ in DOMString granularity);
+
+%{C++
+ /**
+ * AsSelection() returns a pointer to Selection class if the instance is
+ * derived from it. Otherwise, nullptr but it should never happen
+ * since Selection is the only class implementing nsISelection.
+ */
+ virtual mozilla::dom::Selection* AsSelection() = 0;
+%}
+};
diff --git a/dom/base/nsISelectionController.idl b/dom/base/nsISelectionController.idl
new file mode 100644
index 000000000..6840fbb0a
--- /dev/null
+++ b/dom/base/nsISelectionController.idl
@@ -0,0 +1,322 @@
+/* -*- 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;
+%}
+
+interface nsIContent;
+interface nsIDOMNode;
+interface nsISelection;
+interface nsISelectionDisplay;
+
+[scriptable, uuid(3801c9d4-8e69-4bfc-9edb-b58278621f8f)]
+interface nsISelectionController : nsISelectionDisplay
+{
+ // RawSelectionType values:
+ const short SELECTION_NONE=0;
+ const short SELECTION_NORMAL=1;
+ const short SELECTION_SPELLCHECK=2;
+ const short SELECTION_IME_RAWINPUT=4;
+ const short SELECTION_IME_SELECTEDRAWTEXT=8;
+ const short SELECTION_IME_CONVERTEDTEXT=16;
+ const short SELECTION_IME_SELECTEDCONVERTEDTEXT=32;
+ const short SELECTION_ACCESSIBILITY=64; // For accessibility API usage
+ const short SELECTION_FIND=128;
+ const short SELECTION_URLSECONDARY=256;
+ const short SELECTION_URLSTRIKEOUT=512;
+ const short NUM_SELECTIONTYPES=11;
+
+ // 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
+ */
+ nsISelection getSelection(in short type);
+
+ 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;
+
+ /**
+ * CharacterExtendForDelete will extend the selection one character cell
+ * forward in the document.
+ * this method is used internally for handling del key.
+ */
+ [noscript] void characterExtendForDelete();
+
+ /**
+ * CharacterExtendForBackspace will extend the selection one character cell
+ * backward in the document.
+ * this method is used internally for handling backspace key only when we're
+ * after UTF-16 surrogates.
+ */
+ [noscript] void characterExtendForBackspace();
+
+ /** 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);
+
+ /** wordExtendForDelete will extend the selection one word forward/backward in the document.
+ * this method is used internally for handling ctrl[option]-backspace and ctrl[option]-del.
+ * @param aForward forward or backward if PR_FALSE
+ */
+ [noscript] void wordExtendForDelete(in boolean forward);
+
+ /** 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?
+ */
+ 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?
+ */
+ 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);
+
+ /** SelectAll will select the whole page
+ */
+ void selectAll();
+
+ /** CheckVisibility will return true if textnode and offsets are actually rendered
+ * in the current precontext.
+ * @param aNode textNode to test
+ * @param aStartOffset offset in dom to first char of textnode to test
+ * @param aEndOffset offset in dom to last char of textnode to test
+ * @param aReturnBool boolean returned TRUE if visible FALSE if not
+ */
+ boolean checkVisibility(in nsIDOMNode node, in short startOffset, in short endOffset);
+ [noscript,nostdcall] boolean checkVisibilityContent(in nsIContent node, in short startOffset, in short endOffset);
+};
+%{ C++
+ #define NS_ISELECTIONCONTROLLER_CID \
+ { 0x513b9460, 0xd56a, 0x4c4e, \
+ { 0xb6, 0xf9, 0x0b, 0x8a, 0xe4, 0x37, 0x2a, 0x3b }}
+
+namespace mozilla {
+
+typedef short RawSelectionType;
+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,
+};
+
+// 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,
+ // kPresentSelectionTypeCount is number of SelectionType except "none".
+ kPresentSelectionTypeCount = kSelectionTypeCount - 1
+};
+
+const char* ToChar(SelectionType aSelectionType);
+SelectionType ToSelectionType(RawSelectionType aRawSelectionType);
+RawSelectionType ToRawSelectionType(SelectionType aSelectionType);
+bool operator &(SelectionType aSelectionType,
+ RawSelectionType aRawSelectionTypes);
+
+} // namespace mozilla
+%}
diff --git a/dom/base/nsISelectionDisplay.idl b/dom/base/nsISelectionDisplay.idl
new file mode 100644
index 000000000..87c9ba427
--- /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 000000000..2e7b3dd08
--- /dev/null
+++ b/dom/base/nsISelectionListener.idl
@@ -0,0 +1,27 @@
+/* -*- 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"
+
+interface nsIDOMDocument;
+interface nsISelection;
+
+[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;
+
+ void notifySelectionChanged(in nsIDOMDocument doc, in nsISelection sel, in short reason);
+};
+
+
diff --git a/dom/base/nsISelectionPrivate.idl b/dom/base/nsISelectionPrivate.idl
new file mode 100644
index 000000000..68412885e
--- /dev/null
+++ b/dom/base/nsISelectionPrivate.idl
@@ -0,0 +1,165 @@
+/* -*- 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 "nsISelection.idl"
+
+interface nsRange;
+interface nsIDOMNode;
+interface nsISelectionListener;
+interface nsIContent;
+interface nsINode;
+
+%{C++
+class nsIFrame;
+struct nsPoint;
+struct ScrollAxis;
+#include "nsDirection.h"
+#include "nsIPresShell.h" // TODO: Remove this include
+#include "nsTArrayForwardDeclare.h"
+#include "mozilla/EventForwards.h"
+%}
+
+[ptr] native nsIFrame(nsIFrame);
+[ptr] native RangeArray(nsTArray<nsRange*>);
+[ref] native constTextRangeStyleRef(const mozilla::TextRangeStyle);
+[ref] native nsPointRef(nsPoint);
+native nsDirection(nsDirection);
+native ScrollAxis(nsIPresShell::ScrollAxis);
+
+[scriptable, builtinclass, uuid(0c9f4f74-ee7e-4fe9-be6b-0ba856368178)]
+interface nsISelectionPrivate : nsISelection
+ {
+ const short ENDOFPRECEDINGLINE=0;
+ const short STARTOFNEXTLINE=1;
+
+ attribute boolean interlinePosition;
+ [noscript] attribute nsIContent ancestorLimiter;
+
+ /* startBatchChanges
+ match this up with endbatchChanges. will stop ui updates while multiple selection methods are called
+ */
+ [noscript] void startBatchChanges();
+
+ /* endBatchChanges
+ match this up with startBatchChanges
+ */
+ [noscript] void endBatchChanges();
+
+ DOMString toStringWithFormat(in string formatType, in unsigned long flags, in int32_t wrapColumn);
+ void addSelectionListener(in nsISelectionListener newListener);
+ void removeSelectionListener(in nsISelectionListener listenerToRemove);
+
+ /* Table selection stuff
+ We should probably move this and table-related
+ items in nsFrameSelection to a
+ new nsITableSelection interface
+ */
+ const long TABLESELECTION_NONE = 0;
+ const long TABLESELECTION_CELL = 1;
+ const long TABLESELECTION_ROW = 2;
+ const long TABLESELECTION_COLUMN = 3;
+ const long TABLESELECTION_TABLE = 4;
+ const long TABLESELECTION_ALLCELLS = 5;
+
+ /** Test if supplied range points to a single table element:
+ * Result is one of above constants. "None" means
+ * a table element isn't selected.
+ */
+ [noscript] long getTableSelectionType(in nsIDOMRange range);
+
+ /* canCacheFrameOffset
+ * 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
+ */
+ [noscript] attribute boolean canCacheFrameOffset;
+
+ /* GetCachedOffsetForFrame
+ * Returns cached value for nsTextFrame::GetPointFromOffset.
+ */
+ [noscript] void getCachedFrameOffset(in nsIFrame aFrame, in int32_t inOffset, in nsPointRef aPoint);
+
+ /**
+ * 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.
+ */
+ [noscript] void setTextRangeStyle(in nsIDOMRange range,
+ in constTextRangeStyleRef textRangeStyle);
+
+ /**
+ * Get the direction of the selection.
+ */
+ [noscript, notxpcom] nsDirection getSelectionDirection();
+ [noscript, notxpcom] void setSelectionDirection(in nsDirection aDirection);
+
+ /**
+ * Returns the type of the selection (see nsISelectionController for
+ * available constants).
+ */
+ readonly attribute short type;
+
+ /**
+ * Return array of ranges intersecting with the given DOM interval.
+ */
+ void GetRangesForInterval(
+ in nsIDOMNode beginNode, in int32_t beginOffset,
+ in nsIDOMNode endNode, in int32_t endOffset,
+ in boolean allowAdjacent,
+ out uint32_t resultCount,
+ [retval, array, size_is(resultCount)] out nsIDOMRange results);
+
+ [noscript] void GetRangesForIntervalArray(
+ in nsINode beginNode, in int32_t beginOffset,
+ in nsINode endNode, in int32_t endOffset,
+ in boolean allowAdjacent,
+ in RangeArray results);
+
+ /**
+ * Scrolls a region of the selection, so that it is visible in
+ * the scrolled view.
+ *
+ * @param aRegion - the region inside the selection to scroll into view
+ * (see selection region constants defined in
+ * nsISelectionController).
+ * @param aIsSynchronous - when true, scrolls the selection into view
+ * before returning. If false, posts a request which
+ * is processed at some point after the method returns.
+ * @param aVPercent - how to align the frame vertically.
+ * @param aHPercent - how to align the frame horizontally.
+ */
+ void scrollIntoView(in short aRegion, in boolean aIsSynchronous,
+ in int16_t aVPercent,
+ in int16_t aHPercent);
+
+ /**
+ * Scrolls a region of the selection, so that it is visible in
+ * the scrolled view.
+ *
+ * @param aRegion - the region inside the selection to scroll into view
+ * (see selection region constants defined in
+ * nsISelectionController).
+ * @param aIsSynchronous - when true, scrolls the selection into view
+ * before returning. If false, posts a request which
+ * is processed at some point after the method returns.
+ * @param aVertical - how to align the frame vertically and when.
+ * See nsIPresShell.h:ScrollAxis for details.
+ * @param aHorizontal - how to align the frame horizontally and when.
+ * See nsIPresShell.h:ScrollAxis for details.
+ */
+ [noscript] void scrollIntoViewInternal(in short aRegion,
+ in boolean aIsSynchronous,
+ in ScrollAxis aVertical,
+ in ScrollAxis aHorizontal);
+
+ /**
+ * Modifies the cursor Bidi level after a change in keyboard direction
+ * @param langRTL is PR_TRUE if the new language is right-to-left or
+ * PR_FALSE if the new language is left-to-right.
+ */
+ [noscript] void selectionLanguageChange(in boolean langRTL);
+};
+
diff --git a/dom/base/nsISimpleContentPolicy.idl b/dom/base/nsISimpleContentPolicy.idl
new file mode 100644
index 000000000..493aee1a5
--- /dev/null
+++ b/dom/base/nsISimpleContentPolicy.idl
@@ -0,0 +1,147 @@
+/* -*- 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"
+#include "nsIContentPolicyBase.idl"
+
+interface nsIURI;
+interface nsIDOMNode;
+interface nsIPrincipal;
+interface nsIDOMElement;
+
+/**
+ * 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.
+ *
+ * This interface differs from nsIContentPolicy in that it offers less control
+ * (the DOM node doing the load is not provided) but more flexibility for
+ * Gecko. In particular, this interface allows an add-on in the chrome process
+ * to block loads without using cross-process wrappers (CPOWs). Add-ons should
+ * prefer this interface to nsIContentPolicy because it should be faster in
+ * e10s. In the future, it may also be run asynchronously.
+ *
+ * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
+ * by launching a dialog to prompt the user for something).
+ */
+
+[scriptable,uuid(b9df71e3-a9b3-4706-b2d5-e6c0d3d68ec7)]
+interface nsISimpleContentPolicy : nsIContentPolicyBase
+{
+ /**
+ * 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 aContentType the type of content being tested. This will be one
+ * one of the TYPE_* constants.
+ *
+ * @param aContentLocation the location of the content being checked; must
+ * not be null
+ *
+ * @param aRequestOrigin OPTIONAL. the location of the resource that
+ * initiated this load request; can be null if
+ * inapplicable
+ *
+ * @param aTopFrameElement OPTIONAL. The top frame element (typically a
+ * <xul:browser> element) that initiated the
+ * request. In a content process, this argument
+ * will be null.
+ *
+ * @param aIsTopLevel OPTIONAL. True iff the request was initiated
+ * from a frame where |window.top === window|.
+ *
+ * @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
+ *
+ * @param aExtra an OPTIONAL argument, pass-through for non-Gecko
+ * callers to pass extra data to callees.
+ *
+ * @param aRequestPrincipal an OPTIONAL argument, defines the principal that
+ * caused the load. This is optional only for
+ * non-gecko code: all gecko code should set this
+ * argument. For navigation events, this is
+ * the principal of the page that caused this load.
+ *
+ * @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 nsContentPolicyType aContentType,
+ in nsIURI aContentLocation,
+ in nsIURI aRequestOrigin,
+ in nsIDOMElement aTopFrameElement,
+ in boolean aIsTopLevel,
+ in ACString aMimeTypeGuess,
+ in nsISupports aExtra,
+ in nsIPrincipal aRequestPrincipal);
+
+ /**
+ * 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 aContentType the type of content being tested. This will be one
+ * one of the TYPE_* constants.
+ *
+ * @param aContentLocation OPTIONAL; the location of the resource being
+ * requested: MAY be, e.g., a post-redirection URI
+ * for the resource.
+ *
+ * @param aRequestOrigin OPTIONAL. the location of the resource that
+ * initiated this load request; can be null if
+ * inapplicable
+ *
+ * @param aTopFrameElement OPTIONAL. The top frame element (typically a
+ * <xul:browser> element) that initiated the
+ * request. In a content process, this argument
+ * will be null.
+ *
+ * @param aIsTopLevel OPTIONAL. True iff the request was initiated
+ * from a frame where |window.top === window|.
+ *
+ * @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, e.g., TYPE_REFRESH).
+ *
+ * @param aExtra an OPTIONAL argument, pass-through for non-Gecko
+ * callers to pass extra data to callees.
+ *
+ * @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 nsContentPolicyType aContentType,
+ in nsIURI aContentLocation,
+ in nsIURI aRequestOrigin,
+ in nsIDOMElement aTopFrameElement,
+ in boolean aIsTopLevel,
+ in ACString aMimeType,
+ in nsISupports aExtra,
+ in nsIPrincipal aRequestPrincipal);
+};
diff --git a/dom/base/nsISiteSpecificUserAgent.idl b/dom/base/nsISiteSpecificUserAgent.idl
new file mode 100644
index 000000000..c8ad1b4f9
--- /dev/null
+++ b/dom/base/nsISiteSpecificUserAgent.idl
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozIDOMWindow;
+
+/**
+ * nsISiteSpecificUserAgent provides you with site/window-specific User Agent strings.
+ */
+
+[scriptable, uuid(0f0ace30-9ab1-4175-9d60-fd26c0324adc)]
+interface nsISiteSpecificUserAgent : nsISupports
+{
+ /**
+ * Get the User Agent string for a given URI.
+ *
+ * @param aURI is the URI of the page the UA string is used for.
+ *
+ * @param aWindow is the window this UA is being requested for
+ *
+ * @returns the User Agent string for the given URI. If no override applies,
+ * the default User Agent string is used.
+ */
+ AString getUserAgentForURIAndWindow(in nsIURI aURI, in mozIDOMWindow aWindow);
+};
diff --git a/dom/base/nsISizeOfEventTarget.h b/dom/base/nsISizeOfEventTarget.h
new file mode 100644
index 000000000..718b5cac8
--- /dev/null
+++ b/dom/base/nsISizeOfEventTarget.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 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 000000000..ddb53684f
--- /dev/null
+++ b/dom/base/nsISlowScriptDebug.idl
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+interface nsIDOMEventTarget;
+
+[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 nsIDOMEventTarget 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/nsIStyleSheetLinkingElement.h b/dom/base/nsIStyleSheetLinkingElement.h
new file mode 100644
index 000000000..332683816
--- /dev/null
+++ b/dom/base/nsIStyleSheetLinkingElement.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 nsIStyleSheetLinkingElement_h__
+#define nsIStyleSheetLinkingElement_h__
+
+
+#include "nsISupports.h"
+#include "mozilla/StyleSheet.h"
+
+class nsICSSLoaderObserver;
+class nsIURI;
+
+#define NS_ISTYLESHEETLINKINGELEMENT_IID \
+{ 0xa8b79f3b, 0x9d18, 0x4f9c, \
+ { 0xb1, 0xaa, 0x8c, 0x9b, 0x1b, 0xaa, 0xac, 0xad } }
+
+class nsIStyleSheetLinkingElement : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISTYLESHEETLINKINGELEMENT_IID)
+
+ /**
+ * 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.
+ */
+ NS_IMETHOD SetStyleSheet(mozilla::StyleSheet* aStyleSheet) = 0;
+
+ /**
+ * Used to obtain the style sheet linked in by this element.
+ *
+ * @return the style sheet associated with this element.
+ */
+ NS_IMETHOD_(mozilla::StyleSheet*) GetStyleSheet() = 0;
+
+ /**
+ * Initialize the stylesheet linking element. If aDontLoadStyle is
+ * true the element will ignore the first modification to the
+ * element that would cause a stylesheet to be loaded. Subsequent
+ * modifications to the element will not be ignored.
+ */
+ NS_IMETHOD InitStyleLinkElement(bool aDontLoadStyle) = 0;
+
+ /**
+ * Tells this element to update the stylesheet.
+ *
+ * @param aObserver observer to notify once the stylesheet is loaded.
+ * This will be passed to the CSSLoader
+ * @param [out] aWillNotify whether aObserver will be notified when the sheet
+ * loads. If this is false, then either we didn't
+ * start the sheet load at all, the load failed, or
+ * this was an inline sheet that completely finished
+ * loading. In the case when the load failed the
+ * failure code will be returned.
+ * @param [out] whether the sheet is an alternate sheet. This value is only
+ * meaningful if aWillNotify is true.
+ * @param aForceUpdate whether we wand to force the update, flushing the
+ * cached version if any.
+ */
+ NS_IMETHOD UpdateStyleSheet(nsICSSLoaderObserver* aObserver,
+ bool *aWillNotify,
+ bool *aIsAlternate,
+ bool aForceUpdate = false) = 0;
+
+ /**
+ * Tells this element whether to update the stylesheet when the
+ * element's properties change.
+ *
+ * @param aEnableUpdates update on changes or not.
+ */
+ NS_IMETHOD SetEnableUpdates(bool aEnableUpdates) = 0;
+
+ /**
+ * Gets the charset that the element claims the style sheet is in
+ *
+ * @param aCharset the charset
+ */
+ NS_IMETHOD GetCharset(nsAString& aCharset) = 0;
+
+ /**
+ * Tells this element to use a different base URI. This is used for
+ * proper loading of xml-stylesheet processing instructions in XUL overlays
+ * and is only currently used by nsXMLStylesheetPI.
+ *
+ * @param aNewBaseURI the new base URI, nullptr to use the default base URI.
+ */
+ virtual void OverrideBaseURI(nsIURI* aNewBaseURI) = 0;
+
+ // 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.
+ virtual void SetLineNumber(uint32_t aLineNumber) = 0;
+
+ /**
+ * Get the line number, as previously set by SetLineNumber.
+ *
+ * @return the line number of this element; or 1 if no line number
+ * was set
+ */
+ virtual uint32_t GetLineNumber() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIStyleSheetLinkingElement,
+ NS_ISTYLESHEETLINKINGELEMENT_IID)
+
+#endif // nsILinkingElement_h__
diff --git a/dom/base/nsITimeoutHandler.h b/dom/base/nsITimeoutHandler.h
new file mode 100644
index 000000000..9f921dfb2
--- /dev/null
+++ b/dom/base/nsITimeoutHandler.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 nsITimeoutHandler_h___
+#define nsITimeoutHandler_h___
+
+#include "nsISupports.h"
+
+#define NS_ITIMEOUTHANDLER_IID \
+{ 0xb071a1d3, 0xfd54, 0x40a8, \
+ { 0x91, 0x9f, 0xc8, 0xf3, 0x3e, 0xb8, 0x3c, 0xfe } }
+
+class nsITimeoutHandler : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITIMEOUTHANDLER_IID)
+
+ virtual nsresult Call() = 0;
+
+ virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
+ uint32_t* aColumn) = 0;
+
+ virtual void MarkForCC() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITimeoutHandler,
+ NS_ITIMEOUTHANDLER_IID)
+
+#endif // nsITimeoutHandler_h___
diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp
new file mode 100644
index 000000000..d25dd6319
--- /dev/null
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -0,0 +1,1608 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDocument.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIURI.h"
+#include "nsILoadGroup.h"
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsImageFrame.h"
+
+#include "nsIPresShell.h"
+
+#include "nsIChannel.h"
+#include "nsIStreamListener.h"
+
+#include "nsIFrame.h"
+#include "nsIDOMNode.h"
+
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIContentPolicy.h"
+#include "nsSVGEffects.h"
+
+#include "gfxPrefs.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Preferences.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 */
+
+
+nsImageLoadingContent::nsImageLoadingContent()
+ : mCurrentRequestFlags(0),
+ mPendingRequestFlags(0),
+ mObserverList(nullptr),
+ mImageBlockingStatus(nsIContentPolicy::ACCEPT),
+ mLoadingEnabled(true),
+ mIsImageStateForced(false),
+ mLoading(false),
+ // mBroken starts out true, since an image without a URI is broken....
+ mBroken(true),
+ mUserDisabled(false),
+ mSuppressed(false),
+ mNewRequestsWillNeedAnimationReset(false),
+ mStateChangerDepth(0),
+ mCurrentRequestRegistered(false),
+ mPendingRequestRegistered(false),
+ mFrameCreateCalled(false)
+{
+ if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
+ mLoadingEnabled = false;
+ }
+
+ bool isInconsistent;
+ mMostRecentRequestChange = TimeStamp::ProcessCreation(isInconsistent);
+}
+
+void
+nsImageLoadingContent::DestroyImageLoadingContent()
+{
+ // Cancel our requests so they won't hold stale refs to us
+ // NB: Don't ask to discard the images here.
+ ClearCurrentRequest(NS_BINDING_ABORTED);
+ ClearPendingRequest(NS_BINDING_ABORTED);
+}
+
+nsImageLoadingContent::~nsImageLoadingContent()
+{
+ NS_ASSERTION(!mCurrentRequest && !mPendingRequest,
+ "DestroyImageLoadingContent not called");
+ NS_ASSERTION(!mObserverList.mObserver && !mObserverList.mNext,
+ "Observers still registered?");
+}
+
+/*
+ * imgINotificationObserver impl
+ */
+NS_IMETHODIMP
+nsImageLoadingContent::Notify(imgIRequest* aRequest,
+ int32_t aType,
+ const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::IS_ANIMATED) {
+ return OnImageIsAnimated(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
+ OnUnlockedDraw();
+ return NS_OK;
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ // We should definitely have a request here
+ MOZ_ASSERT(aRequest, "no request?");
+
+ NS_PRECONDITION(aRequest == mCurrentRequest || aRequest == mPendingRequest,
+ "Unknown request");
+ }
+
+ {
+ // 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.
+ * We make a note of this image node by including it in a dedicated
+ * array of blocked tracking nodes under its parent document.
+ */
+ if (errorCode == NS_ERROR_TRACKING_URI) {
+ nsCOMPtr<nsIContent> thisNode
+ = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ nsIDocument *doc = GetOurOwnerDoc();
+ doc->AddBlockedTrackingNode(thisNode);
+ }
+ }
+ nsresult status =
+ reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+ return OnLoadComplete(aRequest, status);
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ nsCOMPtr<imgIContainer> container;
+ aRequest->GetImage(getter_AddRefs(container));
+ if (container) {
+ container->PropagateUseCounters(GetOurOwnerDoc());
+ }
+
+ UpdateImageState(true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+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 NS_OK;
+
+ // 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(NS_LITERAL_STRING("load"));
+
+ // Do not fire loadend event for multipart/x-mixed-replace image streams.
+ bool isMultipart;
+ if (NS_FAILED(aRequest->GetMultipart(&isMultipart)) || !isMultipart) {
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ }
+ } else {
+ FireEvent(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ }
+
+ nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsSVGEffects::InvalidateDirectRenderingObservers(thisNode->AsElement());
+
+ return NS_OK;
+}
+
+static bool
+ImageIsAnimated(imgIRequest* aRequest)
+{
+ if (!aRequest) {
+ return false;
+ }
+
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
+ bool isAnimated = false;
+ nsresult rv = image->GetAnimated(&isAnimated);
+ if (NS_SUCCEEDED(rv) && isAnimated) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsImageLoadingContent::OnUnlockedDraw()
+{
+ // 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.
+ if (!ImageIsAnimated(mCurrentRequest) && !ImageIsAnimated(mPendingRequest)) {
+ return;
+ }
+
+ nsIFrame* frame = GetOurPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+
+ if (frame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE) {
+ // This frame is already marked visible; there's nothing to do.
+ return;
+ }
+
+ nsPresContext* presContext = frame->PresContext();
+ if (!presContext) {
+ return;
+ }
+
+ nsIPresShell* presShell = presContext->PresShell();
+ if (!presShell) {
+ return;
+ }
+
+ presShell->EnsureFrameInApproximatelyVisibleList(frame);
+}
+
+nsresult
+nsImageLoadingContent::OnImageIsAnimated(imgIRequest *aRequest)
+{
+ bool* requestFlag = GetRegisteredFlagForRequest(aRequest);
+ if (requestFlag) {
+ nsLayoutUtils::RegisterImageRequest(GetFramePresContext(),
+ aRequest, requestFlag);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * nsIImageLoadingContent impl
+ */
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetLoadingEnabled(bool *aLoadingEnabled)
+{
+ *aLoadingEnabled = mLoadingEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled)
+{
+ if (nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
+ mLoadingEnabled = aLoadingEnabled;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetImageBlockingStatus(int16_t* aStatus)
+{
+ NS_PRECONDITION(aStatus, "Null out param");
+ *aStatus = ImageBlockingStatus();
+ return NS_OK;
+}
+
+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);
+ }
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+
+ if (!mObserverList.mObserver) {
+ // Don't touch the linking of the list!
+ mObserverList.mObserver = aObserver;
+
+ ReplayImageStatus(mCurrentRequest, aObserver);
+ ReplayImageStatus(mPendingRequest, aObserver);
+
+ return NS_OK;
+ }
+
+ // 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);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::RemoveObserver(imgINotificationObserver* aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+
+ if (mObserverList.mObserver == aObserver) {
+ mObserverList.mObserver = nullptr;
+ // Don't touch the linking of the list!
+ return NS_OK;
+ }
+
+ // 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
+ return NS_OK;
+}
+
+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_(bool)
+nsImageLoadingContent::CurrentRequestHasSize()
+{
+ return HaveSize(mCurrentRequest);
+}
+
+NS_IMETHODIMP_(void)
+nsImageLoadingContent::FrameCreated(nsIFrame* aFrame)
+{
+ NS_ASSERTION(aFrame, "aFrame is null");
+
+ mFrameCreateCalled = true;
+
+ TrackImage(mCurrentRequest);
+ TrackImage(mPendingRequest);
+
+ // 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");
+
+ mFrameCreateCalled = false;
+
+ // 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);
+
+ nsIPresShell* 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)
+{
+ NS_PRECONDITION(aRequestType, "Null out param");
+
+ ErrorResult result;
+ *aRequestType = GetRequestType(aRequest, result);
+ return result.StealNSResult();
+}
+
+already_AddRefed<nsIURI>
+nsImageLoadingContent::GetCurrentURI(ErrorResult& aError)
+{
+ nsCOMPtr<nsIURI> uri;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetURI(getter_AddRefs(uri));
+ } else if (mCurrentURI) {
+ nsresult rv = NS_EnsureSafeToReturn(mCurrentURI, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+ }
+
+ return uri.forget();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetCurrentURI(nsIURI** aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ ErrorResult result;
+ *aURI = GetCurrentURI(result).take();
+ return result.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel,
+ nsIStreamListener** aListener)
+{
+ imgLoader* loader =
+ nsContentUtils::GetImgLoaderForChannel(aChannel, GetOurOwnerDoc());
+ if (!loader) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<nsIDocument> 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?
+
+ // 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)) {
+ 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(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ return rv;
+}
+
+void
+nsImageLoadingContent::ForceReload(const mozilla::dom::Optional<bool>& aNotify,
+ mozilla::ErrorResult& aError)
+{
+ nsCOMPtr<nsIURI> currentURI;
+ GetCurrentURI(getter_AddRefs(currentURI));
+ if (!currentURI) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ // defaults to true
+ bool notify = !aNotify.WasPassed() || aNotify.Value();
+
+ // 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, notify, loadType, true, nullptr,
+ nsIRequest::VALIDATE_ALWAYS);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::ForceReload(bool aNotify /* = true */,
+ uint8_t aArgc)
+{
+ mozilla::dom::Optional<bool> notify;
+ if (aArgc >= 1) {
+ notify.Construct() = aNotify;
+ }
+
+ ErrorResult result;
+ ForceReload(notify, result);
+ return result.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::BlockOnload(imgIRequest* aRequest)
+{
+ if (aRequest == mCurrentRequest) {
+ NS_ASSERTION(!(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD),
+ "Double BlockOnload!?");
+ mCurrentRequestFlags |= REQUEST_BLOCKS_ONLOAD;
+ } else if (aRequest == mPendingRequest) {
+ NS_ASSERTION(!(mPendingRequestFlags & REQUEST_BLOCKS_ONLOAD),
+ "Double BlockOnload!?");
+ mPendingRequestFlags |= REQUEST_BLOCKS_ONLOAD;
+ } else {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = GetOurCurrentDoc();
+ if (doc) {
+ doc->BlockOnload();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::UnblockOnload(imgIRequest* aRequest)
+{
+ if (aRequest == mCurrentRequest) {
+ NS_ASSERTION(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD,
+ "Double UnblockOnload!?");
+ mCurrentRequestFlags &= ~REQUEST_BLOCKS_ONLOAD;
+ } else if (aRequest == mPendingRequest) {
+ NS_ASSERTION(mPendingRequestFlags & REQUEST_BLOCKS_ONLOAD,
+ "Double UnblockOnload!?");
+ mPendingRequestFlags &= ~REQUEST_BLOCKS_ONLOAD;
+ } else {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = GetOurCurrentDoc();
+ if (doc) {
+ doc->UnblockOnload(false);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Non-interface methods
+ */
+
+nsresult
+nsImageLoadingContent::LoadImage(const nsAString& aNewURI,
+ bool aForce,
+ bool aNotify,
+ ImageLoadType aImageLoadType)
+{
+ // First, get a document (needed for security checks and the like)
+ nsIDocument* doc = GetOurOwnerDoc();
+ if (!doc) {
+ // No reason to bother, I think...
+ 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.IsEmpty()) {
+ // Cancel image requests and then fire only error event per spec.
+ CancelImageRequests(aNotify);
+ // Mark error event as cancelable only for src="" case, since only this
+ // error causes site compat problem (bug 1308069) for now.
+ FireEvent(NS_LITERAL_STRING("error"), true);
+ return NS_OK;
+ }
+
+ // Fire loadstart event
+ FireEvent(NS_LITERAL_STRING("loadstart"));
+
+ // Parse the URI string to get image URI
+ nsCOMPtr<nsIURI> imageURI;
+ nsresult rv = StringToURI(aNewURI, doc, getter_AddRefs(imageURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // XXXbiesi fire onerror if that failed?
+
+ NS_TryToSetImmutable(imageURI);
+
+ return LoadImage(imageURI, aForce, aNotify, aImageLoadType, false, doc);
+}
+
+nsresult
+nsImageLoadingContent::LoadImage(nsIURI* aNewURI,
+ bool aForce,
+ bool aNotify,
+ ImageLoadType aImageLoadType,
+ bool aLoadStart,
+ nsIDocument* aDocument,
+ nsLoadFlags aLoadFlags)
+{
+ // 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();
+
+ // Fire loadstart event if required
+ if (aLoadStart) {
+ FireEvent(NS_LITERAL_STRING("loadstart"));
+ }
+
+ if (!mLoadingEnabled) {
+ // XXX Why fire an error here? seems like the callers to SetLoadingEnabled
+ // don't want/need it.
+ FireEvent(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ 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;
+ }
+ }
+
+ // URI equality check.
+ //
+ // We skip the equality check if our current image was blocked, since in that
+ // case we really do want to try loading again.
+ if (!aForce && NS_CP_ACCEPTED(mImageBlockingStatus)) {
+ 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;
+ }
+ }
+
+ // 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.
+#ifdef DEBUG
+ nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ MOZ_ASSERT(thisContent &&
+ thisContent->NodePrincipal() == aDocument->NodePrincipal(),
+ "Principal mismatch?");
+#endif
+
+ // Are we blocked?
+ int16_t cpDecision = nsIContentPolicy::REJECT_REQUEST;
+ nsContentPolicyType policyType = PolicyTypeForLoad(aImageLoadType);
+
+ nsContentUtils::CanLoadImage(aNewURI,
+ static_cast<nsIImageLoadingContent*>(this),
+ aDocument,
+ aDocument->NodePrincipal(),
+ &cpDecision,
+ policyType);
+ if (!NS_CP_ACCEPTED(cpDecision)) {
+ FireEvent(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ SetBlockedRequest(aNewURI, cpDecision);
+ return NS_OK;
+ }
+
+ nsLoadFlags loadFlags = aLoadFlags;
+ int32_t corsmode = GetCORSMode();
+ if (corsmode == CORS_ANONYMOUS) {
+ loadFlags |= imgILoader::LOAD_CORS_ANONYMOUS;
+ } else if (corsmode == CORS_USE_CREDENTIALS) {
+ loadFlags |= imgILoader::LOAD_CORS_USE_CREDENTIALS;
+ }
+
+ // get document wide referrer policy
+ // if referrer attributes are enabled in preferences, load img referrer attribute
+ // if the image does not provide a referrer attribute, ignore this
+ net::ReferrerPolicy referrerPolicy = aDocument->GetReferrerPolicy();
+ net::ReferrerPolicy imgReferrerPolicy = GetImageReferrerPolicy();
+ if (imgReferrerPolicy != net::RP_Unset) {
+ referrerPolicy = imgReferrerPolicy;
+ }
+
+ // Not blocked. Do the load.
+ RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsCOMPtr<nsINode> thisNode =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsresult rv = nsContentUtils::LoadImage(aNewURI,
+ thisNode,
+ aDocument,
+ aDocument->NodePrincipal(),
+ aDocument->GetDocumentURI(),
+ referrerPolicy,
+ this, loadFlags,
+ content->LocalName(),
+ getter_AddRefs(req),
+ policyType);
+
+ // 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)) {
+ TrackImage(req);
+ ResetAnimationIfNeeded();
+
+ // Handle cases when we just ended up with a pending 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.
+ if (req == mPendingRequest) {
+ uint32_t pendingLoadStatus;
+ rv = req->GetImageStatus(&pendingLoadStatus);
+ if (NS_SUCCEEDED(rv) &&
+ (pendingLoadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ MakePendingRequestCurrent();
+ MOZ_ASSERT(mCurrentRequest,
+ "How could we not have a current request here?");
+
+ nsImageFrame *f = do_QueryFrame(GetOurPrimaryFrame());
+ if (f) {
+ 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(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsImageLoadingContent::ForceImageState(bool aForce,
+ EventStates::InternalType aState)
+{
+ mIsImageStateForced = aForce;
+ mForcedImageState = EventStates(aState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetNaturalWidth(uint32_t* aNaturalWidth)
+{
+ NS_ENSURE_ARG_POINTER(aNaturalWidth);
+
+ nsCOMPtr<imgIContainer> image;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetImage(getter_AddRefs(image));
+ }
+
+ int32_t width;
+ if (image && NS_SUCCEEDED(image->GetWidth(&width))) {
+ *aNaturalWidth = width;
+ } else {
+ *aNaturalWidth = 0;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetNaturalHeight(uint32_t* aNaturalHeight)
+{
+ NS_ENSURE_ARG_POINTER(aNaturalHeight);
+
+ nsCOMPtr<imgIContainer> image;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetImage(getter_AddRefs(image));
+ }
+
+ int32_t height;
+ if (image && NS_SUCCEEDED(image->GetHeight(&height))) {
+ *aNaturalHeight = height;
+ } else {
+ *aNaturalHeight = 0;
+ }
+
+ return NS_OK;
+}
+
+EventStates
+nsImageLoadingContent::ImageState() const
+{
+ if (mIsImageStateForced) {
+ return mForcedImageState;
+ }
+
+ EventStates states;
+
+ if (mBroken) {
+ states |= NS_EVENT_STATE_BROKEN;
+ }
+ if (mUserDisabled) {
+ states |= NS_EVENT_STATE_USERDISABLED;
+ }
+ if (mSuppressed) {
+ states |= NS_EVENT_STATE_SUPPRESSED;
+ }
+ if (mLoading) {
+ states |= NS_EVENT_STATE_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;
+ }
+
+ nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ if (!thisContent) {
+ return;
+ }
+
+ mLoading = mBroken = mUserDisabled = mSuppressed = false;
+
+ // If we were blocked by server-based content policy, we claim to be
+ // suppressed. If we were blocked by type-based content policy, we claim to
+ // be user-disabled. Otherwise, claim to be broken.
+ if (mImageBlockingStatus == nsIContentPolicy::REJECT_SERVER) {
+ mSuppressed = true;
+ } else if (mImageBlockingStatus == nsIContentPolicy::REJECT_TYPE) {
+ mUserDisabled = true;
+ } else if (!mCurrentRequest) {
+ // No current request means error, since we weren't disabled or suppressed
+ mBroken = true;
+ } else {
+ uint32_t currentLoadStatus;
+ nsresult rv = mCurrentRequest->GetImageStatus(&currentLoadStatus);
+ if (NS_FAILED(rv) || (currentLoadStatus & imgIRequest::STATUS_ERROR)) {
+ mBroken = true;
+ } else if (!(currentLoadStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) {
+ mLoading = true;
+ }
+ }
+
+ NS_ASSERTION(thisContent->IsElement(), "Not an element?");
+ thisContent->AsElement()->UpdateState(aNotify);
+}
+
+void
+nsImageLoadingContent::CancelImageRequests(bool aNotify)
+{
+ AutoStateChanger changer(this, aNotify);
+ ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
+ ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
+}
+
+nsresult
+nsImageLoadingContent::UseAsPrimaryRequest(imgRequestProxy* aRequest,
+ bool aNotify,
+ ImageLoadType aImageLoadType)
+{
+ // Our state will change. Watch it.
+ AutoStateChanger changer(this, aNotify);
+
+ // Get rid if our existing images
+ ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
+ ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
+
+ // Clone the request we were given.
+ RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
+ nsresult rv = aRequest->Clone(this, getter_AddRefs(req));
+ if (NS_SUCCEEDED(rv)) {
+ TrackImage(req);
+ } else {
+ MOZ_ASSERT(!req, "Shouldn't have non-null request here");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsIDocument*
+nsImageLoadingContent::GetOurOwnerDoc()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ENSURE_TRUE(thisContent, nullptr);
+
+ return thisContent->OwnerDoc();
+}
+
+nsIDocument*
+nsImageLoadingContent::GetOurCurrentDoc()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ENSURE_TRUE(thisContent, nullptr);
+
+ return thisContent->GetComposedDoc();
+}
+
+nsIFrame*
+nsImageLoadingContent::GetOurPrimaryFrame()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ return thisContent->GetPrimaryFrame();
+}
+
+nsPresContext* nsImageLoadingContent::GetFramePresContext()
+{
+ nsIFrame* frame = GetOurPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ return frame->PresContext();
+}
+
+nsresult
+nsImageLoadingContent::StringToURI(const nsAString& aSpec,
+ nsIDocument* aDocument,
+ nsIURI** aURI)
+{
+ NS_PRECONDITION(aDocument, "Must have a document");
+ NS_PRECONDITION(aURI, "Null out param");
+
+ // (1) Get the base URI
+ nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "An image loading content must be an nsIContent");
+ nsCOMPtr<nsIURI> baseURL = thisContent->GetBaseURI();
+
+ // (2) Get the charset
+ const nsAFlatCString &charset = aDocument->GetDocumentCharacterSet();
+
+ // (3) Construct the silly thing
+ return NS_NewURI(aURI,
+ aSpec,
+ charset.IsEmpty() ? nullptr : charset.get(),
+ baseURL,
+ nsContentUtils::GetIOService());
+}
+
+nsresult
+nsImageLoadingContent::FireEvent(const nsAString& aEventType, bool aIsCancelable)
+{
+ if (nsContentUtils::DocumentInactiveForImageLoads(GetOurOwnerDoc())) {
+ // Don't bother to fire any events, especially error events.
+ 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 = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, false, false);
+ 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)
+{
+ nsImageFrame* frame = do_QueryFrame(GetOurPrimaryFrame());
+ if (frame) {
+ // Detect JavaScript-based animations created by changing the |src|
+ // attribute on a timer.
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration threshold =
+ TimeDuration::FromMilliseconds(
+ gfxPrefs::ImageInferSrcAnimationThresholdMS());
+
+ // If the length of time between request changes is less than the threshold,
+ // then force sync decoding to eliminate flicker from the animation.
+ frame->SetForceSyncDecoding(now - mMostRecentRequestChange < threshold);
+
+ mMostRecentRequestChange = now;
+ }
+
+ // If we don't have a usable current request, get rid of any half-baked
+ // request that might be sitting there and make this one current.
+ if (!HaveSize(mCurrentRequest))
+ return PrepareCurrentRequest(aImageLoadType);
+
+ // Otherwise, make it pending.
+ return PreparePendingRequest(aImageLoadType);
+}
+
+void
+nsImageLoadingContent::SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision)
+{
+ // Sanity
+ MOZ_ASSERT(!NS_CP_ACCEPTED(aContentDecision), "Blocked but not?");
+
+ // We do some slightly illogical stuff here to maintain consistency with
+ // old behavior that people probably depend on. Even in the case where the
+ // new image is blocked, the old one should really be canceled with the
+ // reason "image source changed". However, apparently there's some abuse
+ // over in nsImageFrame where the displaying of the "broken" icon for the
+ // next image depends on the cancel reason of the previous image. ugh.
+ // XXX(seth): So shouldn't we fix nsImageFrame?!
+ ClearPendingRequest(NS_ERROR_IMAGE_BLOCKED,
+ Some(OnNonvisible::DISCARD_IMAGES));
+
+ // For the blocked case, we only want to cancel the existing current request
+ // if size is not available. bz says the web depends on this behavior.
+ if (!HaveSize(mCurrentRequest)) {
+
+ mImageBlockingStatus = aContentDecision;
+ uint32_t keepFlags = mCurrentRequestFlags & REQUEST_IS_IMAGESET;
+ ClearCurrentRequest(NS_ERROR_IMAGE_BLOCKED,
+ Some(OnNonvisible::DISCARD_IMAGES));
+
+ // We still want to remember what URI we were and if it was an imageset,
+ // despite not having an actual request. These are both cleared as part of
+ // ClearCurrentRequest() before a new request is started.
+ mCurrentURI = aURI;
+ mCurrentRequestFlags = keepFlags;
+ }
+}
+
+RefPtr<imgRequestProxy>&
+nsImageLoadingContent::PrepareCurrentRequest(ImageLoadType aImageLoadType)
+{
+ // Blocked images go through SetBlockedRequest, which is a separate path. For
+ // everything else, we're unblocked.
+ mImageBlockingStatus = nsIContentPolicy::ACCEPT;
+
+ // Get rid of anything that was there previously.
+ ClearCurrentRequest(NS_ERROR_IMAGE_SRC_CHANGED,
+ Some(OnNonvisible::DISCARD_IMAGES));
+
+ 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_ERROR_IMAGE_SRC_CHANGED,
+ Some(OnNonvisible::DISCARD_IMAGES));
+
+ 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);
+
+ // 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;
+ mPendingRequest = nullptr;
+ mCurrentRequestFlags = mPendingRequestFlags;
+ mPendingRequestFlags = 0;
+ 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);
+ 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);
+ mPendingRequest->CancelAndForgetObserver(aReason);
+ mPendingRequest = nullptr;
+ mPendingRequestFlags = 0;
+}
+
+bool*
+nsImageLoadingContent::GetRegisteredFlagForRequest(imgIRequest* aRequest)
+{
+ if (aRequest == mCurrentRequest) {
+ return &mCurrentRequestRegistered;
+ } else if (aRequest == mPendingRequest) {
+ return &mPendingRequestRegistered;
+ } else {
+ return nullptr;
+ }
+}
+
+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::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ // We may be entering the document, so if our image should be tracked,
+ // track it.
+ if (!aDocument)
+ return;
+
+ TrackImage(mCurrentRequest);
+ TrackImage(mPendingRequest);
+
+ if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
+ aDocument->BlockOnload();
+}
+
+void
+nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // We may be leaving the document, so if our image is tracked, untrack it.
+ nsCOMPtr<nsIDocument> doc = GetOurCurrentDoc();
+ if (!doc)
+ return;
+
+ UntrackImage(mCurrentRequest);
+ UntrackImage(mPendingRequest);
+
+ if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
+ doc->UnblockOnload(false);
+}
+
+void
+nsImageLoadingContent::OnVisibilityChange(Visibility aNewVisibility,
+ const Maybe<OnNonvisible>& aNonvisibleAction)
+{
+ switch (aNewVisibility) {
+ case Visibility::APPROXIMATELY_VISIBLE:
+ TrackImage(mCurrentRequest);
+ TrackImage(mPendingRequest);
+ break;
+
+ case Visibility::APPROXIMATELY_NONVISIBLE:
+ 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)
+{
+ if (!aImage)
+ return;
+
+ MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
+ "Why haven't we heard of this request?");
+
+ nsIDocument* doc = GetOurCurrentDoc();
+ if (!doc) {
+ return;
+ }
+
+ // We only want to track this request if we're visible. Ordinarily we check
+ // the visible count, but that requires a frame; in cases where
+ // GetOurPrimaryFrame() cannot obtain a frame (e.g. <feImage>), we assume
+ // we're visible if FrameCreated() was called.
+ nsIFrame* frame = GetOurPrimaryFrame();
+ if ((frame && frame->GetVisibility() == Visibility::APPROXIMATELY_NONVISIBLE) ||
+ (!frame && !mFrameCreateCalled)) {
+ 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.
+ nsIDocument* doc = GetOurCurrentDoc();
+ if (aImage == mCurrentRequest) {
+ if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
+ mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Remove(
+ mCurrentRequest,
+ aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
+ ? ImageTracker::REQUEST_DISCARD
+ : 0);
+ } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
+ // 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::DISCARD_IMAGES)
+ ? ImageTracker::REQUEST_DISCARD
+ : 0);
+ } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
+ // If we're not in the document we may still need to be discarded.
+ aImage->RequestDiscard();
+ }
+ }
+}
+
+
+void
+nsImageLoadingContent::CreateStaticImageClone(nsImageLoadingContent* aDest) const
+{
+ aDest->mCurrentRequest = nsContentUtils::GetStaticRequest(mCurrentRequest);
+ aDest->TrackImage(aDest->mCurrentRequest);
+ aDest->mForcedImageState = mForcedImageState;
+ aDest->mImageBlockingStatus = mImageBlockingStatus;
+ aDest->mLoadingEnabled = mLoadingEnabled;
+ aDest->mStateChangerDepth = mStateChangerDepth;
+ aDest->mIsImageStateForced = mIsImageStateForced;
+ aDest->mLoading = mLoading;
+ aDest->mBroken = mBroken;
+ aDest->mUserDisabled = mUserDisabled;
+ aDest->mSuppressed = mSuppressed;
+}
+
+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);
+}
+
+// Only HTMLInputElement.h overrides this for <img> tags
+// all other subclasses use this one, i.e. ignore referrer attributes
+mozilla::net::ReferrerPolicy
+nsImageLoadingContent::GetImageReferrerPolicy()
+{
+ return mozilla::net::RP_Unset;
+};
diff --git a/dom/base/nsImageLoadingContent.h b/dom/base/nsImageLoadingContent.h
new file mode 100644
index 000000000..85db2bd2c
--- /dev/null
+++ b/dom/base/nsImageLoadingContent.h
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "imgIOnloadBlocker.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/TimeStamp.h"
+#include "nsCOMPtr.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIRequest.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/net/ReferrerPolicy.h"
+
+class nsIURI;
+class nsIDocument;
+class nsPresContext;
+class nsIContent;
+class imgRequestProxy;
+
+namespace mozilla {
+class AsyncEventDispatcher;
+} // namespace mozilla
+
+#ifdef LoadImage
+// Undefine LoadImage to prevent naming conflict with Windows.
+#undef LoadImage
+#endif
+
+class nsImageLoadingContent : public nsIImageLoadingContent,
+ public imgIOnloadBlocker
+{
+ 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
+ NS_DECL_IMGIONLOADBLOCKER
+
+ // Web IDL binding methods.
+ // Note that the XPCOM SetLoadingEnabled, AddObserver, RemoveObserver,
+ // ForceImageState methods are OK for Web IDL bindings to use as well,
+ // since none of them throw when called via the Web IDL bindings.
+
+ bool LoadingEnabled() const { return mLoadingEnabled; }
+ int16_t ImageBlockingStatus() const
+ {
+ return mImageBlockingStatus;
+ }
+ already_AddRefed<imgIRequest>
+ GetRequest(int32_t aRequestType, mozilla::ErrorResult& aError);
+ int32_t
+ GetRequestType(imgIRequest* aRequest, mozilla::ErrorResult& aError);
+ already_AddRefed<nsIURI> GetCurrentURI(mozilla::ErrorResult& aError);
+ void ForceReload(const mozilla::dom::Optional<bool>& aNotify,
+ mozilla::ErrorResult& aError);
+
+ // XPCOM [optional] syntax helper
+ nsresult ForceReload(bool aNotify = true) {
+ return ForceReload(aNotify, 1);
+ }
+
+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
+ */
+ nsresult LoadImage(const nsAString& aNewURI, bool aForce,
+ bool aNotify, ImageLoadType aImageLoadType);
+
+ /**
+ * ImageState is called by subclasses that are computing their content state.
+ * The return value will have the NS_EVENT_STATE_BROKEN,
+ * NS_EVENT_STATE_USERDISABLED, and NS_EVENT_STATE_SUPPRESSED bits 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 NS_EVENT_STATE_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::EventStates 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 aLoadStart If true, dispatch "loadstart" event.
+ * @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
+ */
+ nsresult LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify,
+ ImageLoadType aImageLoadType, bool aLoadStart = true,
+ nsIDocument* aDocument = nullptr,
+ nsLoadFlags aLoadFlags = nsIRequest::LOAD_NORMAL);
+
+ /**
+ * 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
+ */
+ nsIDocument* GetOurOwnerDoc();
+ nsIDocument* GetOurCurrentDoc();
+
+ /**
+ * Helper function to get the frame associated with this content. Not named
+ * GetPrimaryFrame to prevent ambiguous method names in subclasses.
+ *
+ * @return The frame which we belong to, or nullptr if it doesn't exist.
+ */
+ nsIFrame* GetOurPrimaryFrame();
+
+ /**
+ * 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);
+
+ /**
+ * UseAsPrimaryRequest is called by subclasses when they have an existing
+ * imgRequestProxy that they want this nsImageLoadingContent to use. This may
+ * effectively be called instead of LoadImage or LoadImageWithChannel.
+ * If aNotify is true, this method will notify on state changes.
+ */
+ nsresult UseAsPrimaryRequest(imgRequestProxy* aRequest, bool aNotify,
+ ImageLoadType aImageLoadType);
+
+ /**
+ * Derived classes of nsImageLoadingContent MUST call
+ * DestroyImageLoadingContent 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 DestroyImageLoadingContent();
+
+ void ClearBrokenState() { mBroken = false; }
+
+ /**
+ * Returns the CORS mode that will be used for all future image loads. The
+ * default implementation returns CORS_NONE unconditionally.
+ */
+ virtual mozilla::CORSMode GetCORSMode();
+
+ virtual mozilla::net::ReferrerPolicy GetImageReferrerPolicy();
+
+ // Subclasses are *required* to call BindToTree/UnbindFromTree.
+ void BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent, bool aCompileEventHandlers);
+ void UnbindFromTree(bool aDeep, bool aNullParent);
+
+ nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
+ void OnUnlockedDraw();
+ nsresult OnImageIsAnimated(imgIRequest *aRequest);
+
+ // The nsContentPolicyType we would use for this ImageLoadType
+ static nsContentPolicyType PolicyTypeForLoad(ImageLoadType aImageLoadType);
+
+ void AsyncEventRunning(mozilla::AsyncEventDispatcher* aEvent);
+
+private:
+ /**
+ * Struct used to manage the image observers.
+ */
+ struct ImageObserver {
+ explicit ImageObserver(imgINotificationObserver* aObserver);
+ ~ImageObserver();
+
+ nsCOMPtr<imgINotificationObserver> mObserver;
+ ImageObserver* mNext;
+ };
+
+ /**
+ * 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;
+
+ /**
+ * 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 fire an event once we know what's going on with the image load.
+ *
+ * @param aEventType "loadstart", "loadend", "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:
+ /**
+ * 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, nsIDocument* aDocument,
+ nsIURI** aURI);
+
+ void CreateStaticImageClone(nsImageLoadingContent* aDest) const;
+
+ /**
+ * 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);
+
+ /**
+ * Called when we would normally call PrepareNextRequest(), but the request was
+ * blocked.
+ */
+ void SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision);
+
+ /**
+ * 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, since it passes a more appropriate
+ * aReason than Prepare*Request() does (NS_ERROR_IMAGE_SRC_CHANGED).
+ *
+ * @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());
+
+ /**
+ * Retrieve a pointer to the 'registered with the refresh driver' flag for
+ * which a particular image request corresponds.
+ *
+ * @returns A pointer to the boolean flag for a given image request, or
+ * |nullptr| if the request is not either |mPendingRequest| or
+ * |mCurrentRequest|.
+ */
+ bool* GetRegisteredFlagForRequest(imgIRequest* aRequest);
+
+ /**
+ * 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 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);
+ void UntrackImage(imgIRequest* aImage,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
+
+ /* MEMBERS */
+ RefPtr<imgRequestProxy> mCurrentRequest;
+ RefPtr<imgRequestProxy> mPendingRequest;
+ uint32_t mCurrentRequestFlags;
+ uint32_t mPendingRequestFlags;
+
+ enum {
+ // Set if the request needs ResetAnimation called on it.
+ REQUEST_NEEDS_ANIMATION_RESET = 0x00000001U,
+ // Set if the request is blocking onload.
+ REQUEST_BLOCKS_ONLOAD = 0x00000002U,
+ // Set if the request is currently tracked with the document.
+ REQUEST_IS_TRACKED = 0x00000004U,
+ // Set if this is an imageset request, such as from <img srcset> or
+ // <picture>
+ REQUEST_IS_IMAGESET = 0x00000008U
+ };
+
+ // 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:
+ /**
+ * 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;
+
+ /**
+ * When mIsImageStateForced is true, this holds the ImageState that we'll
+ * return in ImageState().
+ */
+ mozilla::EventStates mForcedImageState;
+
+ mozilla::TimeStamp mMostRecentRequestChange;
+
+ int16_t mImageBlockingStatus;
+ bool mLoadingEnabled : 1;
+
+ /**
+ * When true, we return mForcedImageState from ImageState().
+ */
+ bool mIsImageStateForced : 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;
+ bool mUserDisabled : 1;
+ bool mSuppressed : 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;
+
+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;
+
+ // True when FrameCreate has been called but FrameDestroy has not.
+ bool mFrameCreateCalled;
+};
+
+#endif // nsImageLoadingContent_h__
diff --git a/dom/base/nsInProcessTabChildGlobal.cpp b/dom/base/nsInProcessTabChildGlobal.cpp
new file mode 100644
index 000000000..10ccf4aec
--- /dev/null
+++ b/dom/base/nsInProcessTabChildGlobal.cpp
@@ -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/. */
+
+#include "nsInProcessTabChildGlobal.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsComponentManagerUtils.h"
+#include "nsScriptLoader.h"
+#include "nsFrameLoader.h"
+#include "xpcpublic.h"
+#include "nsIMozBrowserFrame.h"
+#include "nsDOMClassInfoID.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/SameProcessMessageQueue.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+
+bool
+nsInProcessTabChildGlobal::DoSendBlockingMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal,
+ bool aIsSync)
+{
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ queue->Flush();
+
+ if (mChromeMessageManager) {
+ SameProcessCpowHolder cpows(JS::RootingContext::get(aCx), aCpows);
+ RefPtr<nsFrameMessageManager> mm = mChromeMessageManager;
+ nsCOMPtr<nsIFrameLoader> fl = GetFrameLoader();
+ mm->ReceiveMessage(mOwner, fl, aMessage, true, &aData, &cpows, aPrincipal,
+ aRetVal);
+ }
+ return true;
+}
+
+class nsAsyncMessageToParent : public nsSameProcessAsyncMessageBase,
+ public SameProcessMessageQueue::Runnable
+{
+public:
+ nsAsyncMessageToParent(JS::RootingContext* aRootingCx,
+ JS::Handle<JSObject*> aCpows,
+ nsInProcessTabChildGlobal* aTabChild)
+ : nsSameProcessAsyncMessageBase(aRootingCx, aCpows)
+ , mTabChild(aTabChild)
+ { }
+
+ virtual nsresult HandleMessage() override
+ {
+ nsCOMPtr<nsIFrameLoader> fl = mTabChild->GetFrameLoader();
+ ReceiveMessage(mTabChild->mOwner, fl, mTabChild->mChromeMessageManager);
+ return NS_OK;
+ }
+ RefPtr<nsInProcessTabChildGlobal> mTabChild;
+};
+
+nsresult
+nsInProcessTabChildGlobal::DoSendAsyncMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal)
+{
+ SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
+ JS::RootingContext* rcx = JS::RootingContext::get(aCx);
+ RefPtr<nsAsyncMessageToParent> ev =
+ new nsAsyncMessageToParent(rcx, aCpows, this);
+
+ nsresult rv = ev->Init(aMessage, aData, aPrincipal);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ queue->Push(ev);
+ return NS_OK;
+}
+
+nsInProcessTabChildGlobal::nsInProcessTabChildGlobal(nsIDocShell* aShell,
+ nsIContent* aOwner,
+ nsFrameMessageManager* aChrome)
+: mDocShell(aShell), mInitialized(false), mLoadingScript(false),
+ mPreventEventsEscaping(false),
+ mOwner(aOwner), mChromeMessageManager(aChrome)
+{
+ SetIsNotDOMBinding();
+ mozilla::HoldJSObjects(this);
+
+ // If owner corresponds to an <iframe mozbrowser> or <iframe mozapp>, we'll
+ // have to tweak our PreHandleEvent implementation.
+ nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner);
+ if (browserFrame) {
+ mIsBrowserOrAppFrame = browserFrame->GetReallyIsBrowserOrApp();
+ }
+ else {
+ mIsBrowserOrAppFrame = false;
+ }
+}
+
+nsInProcessTabChildGlobal::~nsInProcessTabChildGlobal()
+{
+ mAnonymousGlobalScopes.Clear();
+ 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.
+NS_IMETHODIMP_(bool)
+nsInProcessTabChildGlobal::MarkForCC()
+{
+ MarkScopesForCC();
+ return mMessageManager ? mMessageManager->MarkForCC() : false;
+}
+
+nsresult
+nsInProcessTabChildGlobal::Init()
+{
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ InitTabChildGlobal();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Couldn't initialize nsInProcessTabChildGlobal");
+ mMessageManager = new nsFrameMessageManager(this,
+ nullptr,
+ dom::ipc::MM_CHILD);
+ return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsInProcessTabChildGlobal)
+
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsInProcessTabChildGlobal,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+ tmp->TraverseHostObjectURIs(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsInProcessTabChildGlobal,
+ DOMEventTargetHelper)
+ tmp->nsMessageManagerScriptExecutor::Trace(aCallbacks, aClosure);
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsInProcessTabChildGlobal,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousGlobalScopes)
+ tmp->UnlinkHostObjectURIs();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsInProcessTabChildGlobal)
+ NS_INTERFACE_MAP_ENTRY(nsIMessageListenerManager)
+ NS_INTERFACE_MAP_ENTRY(nsIMessageSender)
+ NS_INTERFACE_MAP_ENTRY(nsISyncMessageSender)
+ NS_INTERFACE_MAP_ENTRY(nsIContentFrameMessageManager)
+ NS_INTERFACE_MAP_ENTRY(nsIInProcessContentFrameMessageManager)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(ContentFrameMessageManager)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(nsInProcessTabChildGlobal, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(nsInProcessTabChildGlobal, DOMEventTargetHelper)
+
+void
+nsInProcessTabChildGlobal::CacheFrameLoader(nsIFrameLoader* aFrameLoader)
+{
+ mFrameLoader = aFrameLoader;
+}
+
+NS_IMETHODIMP
+nsInProcessTabChildGlobal::GetContent(mozIDOMWindowProxy** aContent)
+{
+ *aContent = nullptr;
+ if (!mDocShell) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow();
+ window.forget(aContent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInProcessTabChildGlobal::GetDocShell(nsIDocShell** aDocShell)
+{
+ NS_IF_ADDREF(*aDocShell = mDocShell);
+ return NS_OK;
+}
+
+void
+nsInProcessTabChildGlobal::FireUnloadEvent()
+{
+ // We're called from nsDocument::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(NS_LITERAL_STRING("unload"));
+
+ // Allow events fired during docshell destruction (pagehide, unload) to
+ // propagate to the <browser> element since chrome code depends on this.
+ mPreventEventsEscaping = false;
+}
+
+void
+nsInProcessTabChildGlobal::DisconnectEventListeners()
+{
+ if (mDocShell) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = mDocShell->GetWindow()) {
+ MOZ_ASSERT(win->IsOuterWindow());
+ win->SetChromeEventHandler(win->GetChromeEventHandler());
+ }
+ }
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ }
+
+ mDocShell = nullptr;
+}
+
+void
+nsInProcessTabChildGlobal::Disconnect()
+{
+ mChromeMessageManager = nullptr;
+ mOwner = nullptr;
+ if (mMessageManager) {
+ static_cast<nsFrameMessageManager*>(mMessageManager.get())->Disconnect();
+ mMessageManager = nullptr;
+ }
+}
+
+NS_IMETHODIMP_(nsIContent *)
+nsInProcessTabChildGlobal::GetOwnerContent()
+{
+ return mOwner;
+}
+
+nsresult
+nsInProcessTabChildGlobal::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mForceContentDispatch = true;
+ aVisitor.mCanHandle = true;
+
+#ifdef DEBUG
+ if (mOwner) {
+ nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(mOwner);
+ RefPtr<nsFrameLoader> fl = owner->GetFrameLoader();
+ if (fl) {
+ NS_ASSERTION(this == fl->GetTabChildGlobalAsEventTarget(),
+ "Wrong event target!");
+ NS_ASSERTION(fl->mMessageManager == mChromeMessageManager,
+ "Wrong message manager!");
+ }
+ }
+#endif
+
+ if (mPreventEventsEscaping) {
+ aVisitor.mParentTarget = nullptr;
+ return NS_OK;
+ }
+
+ if (mIsBrowserOrAppFrame &&
+ (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
+ if (mOwner) {
+ if (nsPIDOMWindowInner* innerWindow = mOwner->OwnerDoc()->GetInnerWindow()) {
+ aVisitor.mParentTarget = innerWindow->GetParentTarget();
+ }
+ }
+ } else {
+ aVisitor.mParentTarget = mOwner;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsInProcessTabChildGlobal::InitTabChildGlobal()
+{
+ // If you change this, please change GetCompartmentName() in XPCJSContext.cpp
+ // accordingly.
+ nsAutoCString id;
+ id.AssignLiteral("inProcessTabChildGlobal");
+ nsIURI* uri = mOwner->OwnerDoc()->GetDocumentURI();
+ if (uri) {
+ nsAutoCString u;
+ nsresult rv = uri->GetSpec(u);
+ NS_ENSURE_SUCCESS(rv, rv);
+ id.AppendLiteral("?ownedBy=");
+ id.Append(u);
+ }
+ nsISupports* scopeSupports = NS_ISUPPORTS_CAST(EventTarget*, this);
+ NS_ENSURE_STATE(InitChildGlobalInternal(scopeSupports, id));
+ return NS_OK;
+}
+
+class nsAsyncScriptLoad : public Runnable
+{
+public:
+ nsAsyncScriptLoad(nsInProcessTabChildGlobal* aTabChild, const nsAString& aURL,
+ bool aRunInGlobalScope)
+ : mTabChild(aTabChild), mURL(aURL), mRunInGlobalScope(aRunInGlobalScope) {}
+
+ NS_IMETHOD Run() override
+ {
+ mTabChild->LoadFrameScript(mURL, mRunInGlobalScope);
+ return NS_OK;
+ }
+ RefPtr<nsInProcessTabChildGlobal> mTabChild;
+ nsString mURL;
+ bool mRunInGlobalScope;
+};
+
+void
+nsInProcessTabChildGlobal::LoadFrameScript(const nsAString& aURL, bool aRunInGlobalScope)
+{
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::AddScriptRunner(new nsAsyncScriptLoad(this, aURL, aRunInGlobalScope));
+ return;
+ }
+ if (!mInitialized) {
+ mInitialized = true;
+ Init();
+ }
+ bool tmp = mLoadingScript;
+ mLoadingScript = true;
+ LoadScriptInternal(aURL, aRunInGlobalScope);
+ mLoadingScript = tmp;
+}
+
+already_AddRefed<nsIFrameLoader>
+nsInProcessTabChildGlobal::GetFrameLoader()
+{
+ nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(mOwner);
+ nsCOMPtr<nsIFrameLoader> fl = owner ? owner->GetFrameLoader() : nullptr;
+ if (!fl) {
+ fl = mFrameLoader;
+ }
+ return fl.forget();
+}
diff --git a/dom/base/nsInProcessTabChildGlobal.h b/dom/base/nsInProcessTabChildGlobal.h
new file mode 100644
index 000000000..e7dd9cb5a
--- /dev/null
+++ b/dom/base/nsInProcessTabChildGlobal.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsInProcessTabChildGlobal_h
+#define nsInProcessTabChildGlobal_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsCOMPtr.h"
+#include "nsFrameMessageManager.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptContext.h"
+#include "nsIClassInfo.h"
+#include "nsIDocShell.h"
+#include "nsIDOMElement.h"
+#include "nsCOMArray.h"
+#include "nsIRunnable.h"
+#include "nsIGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+class EventChainPreVisitor;
+} // namespace mozilla
+
+class nsInProcessTabChildGlobal : public mozilla::DOMEventTargetHelper,
+ public nsMessageManagerScriptExecutor,
+ public nsIInProcessContentFrameMessageManager,
+ public nsIGlobalObject,
+ public nsIScriptObjectPrincipal,
+ public nsSupportsWeakReference,
+ public mozilla::dom::ipc::MessageManagerCallback
+{
+ typedef mozilla::dom::ipc::StructuredCloneData StructuredCloneData;
+
+ using mozilla::dom::ipc::MessageManagerCallback::GetProcessMessageManager;
+
+public:
+ nsInProcessTabChildGlobal(nsIDocShell* aShell, nsIContent* aOwner,
+ nsFrameMessageManager* aChrome);
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsInProcessTabChildGlobal,
+ mozilla::DOMEventTargetHelper)
+
+ NS_FORWARD_SAFE_NSIMESSAGELISTENERMANAGER(mMessageManager)
+ NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
+ NS_FORWARD_SAFE_NSIMESSAGEMANAGERGLOBAL(mMessageManager)
+ NS_IMETHOD SendSyncMessage(const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObject,
+ JS::Handle<JS::Value> aRemote,
+ nsIPrincipal* aPrincipal,
+ JSContext* aCx,
+ uint8_t aArgc,
+ JS::MutableHandle<JS::Value> aRetval) override
+ {
+ return mMessageManager
+ ? mMessageManager->SendSyncMessage(aMessageName, aObject, aRemote,
+ aPrincipal, aCx, aArgc, aRetval)
+ : NS_ERROR_NULL_POINTER;
+ }
+ NS_IMETHOD SendRpcMessage(const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObject,
+ JS::Handle<JS::Value> aRemote,
+ nsIPrincipal* aPrincipal,
+ JSContext* aCx,
+ uint8_t aArgc,
+ JS::MutableHandle<JS::Value> aRetval) override
+ {
+ return mMessageManager
+ ? mMessageManager->SendRpcMessage(aMessageName, aObject, aRemote,
+ aPrincipal, aCx, aArgc, aRetval)
+ : NS_ERROR_NULL_POINTER;
+ }
+ NS_IMETHOD GetContent(mozIDOMWindowProxy** aContent) override;
+ NS_IMETHOD GetDocShell(nsIDocShell** aDocShell) override;
+
+ NS_DECL_NSIINPROCESSCONTENTFRAMEMESSAGEMANAGER
+
+ /**
+ * MessageManagerCallback methods that we override.
+ */
+ virtual bool DoSendBlockingMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal,
+ nsTArray<StructuredCloneData>* aRetVal,
+ bool aIsSync) override;
+ virtual nsresult DoSendAsyncMessage(JSContext* aCx,
+ const nsAString& aMessage,
+ StructuredCloneData& aData,
+ JS::Handle<JSObject *> aCpows,
+ nsIPrincipal* aPrincipal) override;
+
+ virtual nsresult PreHandleEvent(
+ mozilla::EventChainPreVisitor& aVisitor) override;
+ NS_IMETHOD AddEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture)
+ {
+ // By default add listeners only for trusted events!
+ return mozilla::DOMEventTargetHelper::AddEventListener(aType, aListener,
+ aUseCapture, false,
+ 2);
+ }
+ NS_IMETHOD AddEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture, bool aWantsUntrusted,
+ uint8_t optional_argc) override
+ {
+ return mozilla::DOMEventTargetHelper::AddEventListener(aType, aListener,
+ aUseCapture,
+ aWantsUntrusted,
+ optional_argc);
+ }
+ using mozilla::DOMEventTargetHelper::AddEventListener;
+
+ virtual nsIPrincipal* GetPrincipal() override { return mPrincipal; }
+ 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;
+ }
+
+ virtual JSObject* GetGlobalJSObject() override {
+ if (!mGlobal) {
+ return nullptr;
+ }
+
+ return mGlobal->GetJSObject();
+ }
+ virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ MOZ_CRASH("nsInProcessTabChildGlobal doesn't use DOM bindings!");
+ }
+
+ already_AddRefed<nsIFrameLoader> GetFrameLoader();
+
+protected:
+ virtual ~nsInProcessTabChildGlobal();
+
+ nsresult Init();
+ nsresult InitTabChildGlobal();
+ nsCOMPtr<nsIContentFrameMessageManager> mMessageManager;
+ nsCOMPtr<nsIDocShell> mDocShell;
+ bool mInitialized;
+ bool mLoadingScript;
+
+ // Is this the message manager for an in-process <iframe mozbrowser> or
+ // <iframe mozapp>? This affects where events get sent, so it affects
+ // PreHandleEvent.
+ bool mIsBrowserOrAppFrame;
+ 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.
+ nsCOMPtr<nsIFrameLoader> mFrameLoader;
+public:
+ nsIContent* mOwner;
+ nsFrameMessageManager* mChromeMessageManager;
+};
+
+#endif
diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp
new file mode 100644
index 000000000..b273d00c9
--- /dev/null
+++ b/dom/base/nsJSEnvironment.cpp
@@ -0,0 +1,2812 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIDOMChromeWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsDOMCID.h"
+#include "nsIServiceManager.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 "nsIScriptError.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPrompt.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsIAtom.h"
+#include "nsContentUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsIContent.h"
+#include "nsCycleCollector.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsIXULRuntime.h"
+#include "nsTextFormatter.h"
+#include "ScriptSettings.h"
+
+#include "xpcpublic.h"
+
+#include "jsapi.h"
+#include "jswrapper.h"
+#include "js/SliceBudget.h"
+#include "nsIArray.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "prmem.h"
+#include "WrapperFactory.h"
+#include "nsGlobalWindow.h"
+#include "nsScriptNameSpaceManager.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "nsAXPCNativeCallContext.h"
+#include "mozilla/CycleCollectedJSContext.h"
+
+#include "nsJSPrincipals.h"
+
+#ifdef XP_MACOSX
+// AssertMacros.h defines 'check' and conflicts with AccessCheck.h
+#undef check
+#endif
+#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/asmjscache/AsmJSCache.h"
+#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/ContentEvents.h"
+
+#include "nsCycleCollectionNoteRootCallback.h"
+#include "GeckoProfiler.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+const size_t gStackSize = 8192;
+
+// Thank you Microsoft!
+#ifdef CompareString
+#undef CompareString
+#endif
+
+#define NS_SHRINK_GC_BUFFERS_DELAY 4000 // ms
+
+// The amount of time we wait from the first request to GC to actually
+// doing the first GC.
+#define NS_FIRST_GC_DELAY 10000 // ms
+
+#define NS_FULL_GC_DELAY 60000 // ms
+
+// The default amount of time to wait from the user being idle to starting a
+// shrinking GC.
+#define NS_DEAULT_INACTIVE_GC_DELAY 300000 // ms
+
+// Maximum amount of time that should elapse between incremental GC slices
+#define NS_INTERSLICE_GC_DELAY 100 // ms
+
+// If we haven't painted in 100ms, we allow for a longer GC budget
+#define NS_INTERSLICE_GC_BUDGET 40 // ms
+
+// The amount of time we wait between a request to CC (after GC ran)
+// and doing the actual CC.
+#define NS_CC_DELAY 6000 // ms
+
+#define NS_CC_SKIPPABLE_DELAY 250 // ms
+
+// Maximum amount of time that should elapse between incremental CC slices
+static const int64_t kICCIntersliceDelay = 32; // ms
+
+// Time budget for an incremental CC slice
+static const int64_t kICCSliceBudget = 5; // ms
+
+// Maximum total duration for an ICC
+static const uint32_t kMaxICCDuration = 2000; // ms
+
+// Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
+// objects in the purple buffer.
+#define NS_CC_FORCED (2 * 60 * PR_USEC_PER_SEC) // 2 min
+#define NS_CC_FORCED_PURPLE_LIMIT 10
+
+// Don't allow an incremental GC to lock out the CC for too long.
+#define NS_MAX_CC_LOCKEDOUT_TIME (30 * PR_USEC_PER_SEC) // 30 seconds
+
+// Trigger a CC if the purple buffer exceeds this size when we check it.
+#define NS_CC_PURPLE_LIMIT 200
+
+// Large value used to specify that a script should run essentially forever
+#define NS_UNLIMITED_SCRIPT_RUNTIME (0x40000000LL << 32)
+
+// if you add statics here, add them to the list in StartupJSEnvironment
+
+static nsITimer *sGCTimer;
+static nsITimer *sShrinkingGCTimer;
+static nsITimer *sCCTimer;
+static nsITimer *sICCTimer;
+static nsITimer *sFullGCTimer;
+static nsITimer *sInterSliceGCTimer;
+
+static TimeStamp sLastCCEndTime;
+
+static bool sCCLockedOut;
+static PRTime sCCLockedOutTime;
+
+static JS::GCSliceCallback sPrevGCSliceCallback;
+
+static bool sHasRunGC;
+
+// The number of currently pending document loads. This count isn't
+// guaranteed to always reflect reality and can't easily as we don't
+// have an easy place to know when a load ends or is interrupted in
+// all cases. This counter also gets reset if we end up GC'ing while
+// we're waiting for a slow page to load. IOW, this count may be 0
+// even when there are pending loads.
+static uint32_t sPendingLoadCount;
+static bool sLoadingInProgress;
+
+static uint32_t sCCollectedWaitingForGC;
+static uint32_t sCCollectedZonesWaitingForGC;
+static uint32_t sLikelyShortLivingObjectsNeedingGC;
+static bool sPostGCEventsToConsole;
+static bool sPostGCEventsToObserver;
+static int32_t sCCTimerFireCount = 0;
+static uint32_t sMinForgetSkippableTime = UINT32_MAX;
+static uint32_t sMaxForgetSkippableTime = 0;
+static uint32_t sTotalForgetSkippableTime = 0;
+static uint32_t sRemovedPurples = 0;
+static uint32_t sForgetSkippableBeforeCC = 0;
+static uint32_t sPreviousSuspectedCount = 0;
+static uint32_t sCleanupsSinceLastGC = UINT32_MAX;
+static bool sNeedsFullCC = false;
+static bool sNeedsFullGC = false;
+static bool sNeedsGCAfterCC = false;
+static bool sIncrementalCC = false;
+static bool sDidPaintAfterPreviousICCSlice = false;
+
+static nsScriptNameSpaceManager *gNameSpaceManager;
+
+static PRTime sFirstCollectionTime;
+
+static JSContext* sContext;
+
+static bool sIsInitialized;
+static bool sDidShutdown;
+static bool sShuttingDown;
+static int32_t sContextCount;
+
+static nsIScriptSecurityManager *sSecurityManager;
+
+// nsJSEnvironmentObserver observes the memory-pressure notifications
+// and forces a garbage collection and cycle collection when it happens, if
+// the appropriate pref is set.
+
+static bool sGCOnMemoryPressure;
+
+// 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.
+
+static bool sCompactOnUserInactive;
+static uint32_t sCompactOnUserInactiveDelay = NS_DEAULT_INACTIVE_GC_DELAY;
+static bool sIsCompactingOnUserInactive = false;
+
+// In testing, we call RunNextCollectorTimer() to ensure that the collectors are run more
+// aggressively than they would be in regular browsing. sExpensiveCollectorPokes keeps
+// us from triggering expensive full collections too frequently.
+static int32_t sExpensiveCollectorPokes = 0;
+static const int32_t kPokesBetweenExpensiveCollectorTriggers = 5;
+
+static const char*
+ProcessNameForCollectorLog()
+{
+ return XRE_GetProcessType() == GeckoProcessType_Default ?
+ "default" : "content";
+}
+
+namespace xpc {
+
+// This handles JS Exceptions (via ExceptionStackOrNull), as well as DOM and XPC
+// Exceptions.
+//
+// Note that the returned object is _not_ wrapped into the compartment of
+// exceptionValue.
+JSObject*
+FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win,
+ JS::HandleValue exceptionValue)
+{
+ if (!exceptionValue.isObject()) {
+ return nullptr;
+ }
+
+ if (win && win->InnerObjectsFreed()) {
+ // Pretend like we have no stack, so we don't end up keeping the global
+ // alive via the stack.
+ return nullptr;
+ }
+
+ JS::RootingContext* rcx = RootingCx();
+ JS::RootedObject exceptionObject(rcx, &exceptionValue.toObject());
+ JSObject* stackObject = ExceptionStackOrNull(exceptionObject);
+ if (stackObject) {
+ return stackObject;
+ }
+
+ // 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) {
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
+ if (!stack) {
+ return nullptr;
+ }
+ JS::RootedValue value(rcx);
+ stack->GetNativeSavedFrame(&value);
+ if (value.isObject()) {
+ return &value.toObject();
+ }
+ return nullptr;
+}
+
+} /* namespace xpc */
+
+static PRTime
+GetCollectionTimeDelta()
+{
+ PRTime now = PR_Now();
+ if (sFirstCollectionTime) {
+ return now - sFirstCollectionTime;
+ }
+ sFirstCollectionTime = now;
+ return 0;
+}
+
+static void
+KillTimers()
+{
+ nsJSContext::KillGCTimer();
+ nsJSContext::KillShrinkingGCTimer();
+ nsJSContext::KillCCTimer();
+ nsJSContext::KillICCTimer();
+ nsJSContext::KillFullGCTimer();
+ nsJSContext::KillInterSliceGCTimer();
+}
+
+// If we collected a substantial amount of cycles, poke the GC since more objects
+// might be unreachable now.
+static bool
+NeedsGCAfterCC()
+{
+ return sCCollectedWaitingForGC > 250 ||
+ sCCollectedZonesWaitingForGC > 0 ||
+ sLikelyShortLivingObjectsNeedingGC > 2500 ||
+ sNeedsGCAfterCC;
+}
+
+class nsJSEnvironmentObserver final : public nsIObserver
+{
+ ~nsJSEnvironmentObserver() {}
+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 (sGCOnMemoryPressure) {
+ if(StringBeginsWith(nsDependentString(aData),
+ NS_LITERAL_STRING("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;
+ }
+ nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE,
+ nsJSContext::NonIncrementalGC,
+ nsJSContext::ShrinkingGC);
+ nsJSContext::CycleCollectNow();
+ if (NeedsGCAfterCC()) {
+ nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE,
+ nsJSContext::NonIncrementalGC,
+ nsJSContext::ShrinkingGC);
+ }
+ }
+ } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) {
+ if (sCompactOnUserInactive) {
+ nsJSContext::PokeShrinkingGC();
+ }
+ } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) {
+ nsJSContext::KillShrinkingGCTimer();
+ if (sIsCompactingOnUserInactive) {
+ JS::AbortIncrementalGC(sContext);
+ }
+ MOZ_ASSERT(!sIsCompactingOnUserInactive);
+ } else if (!nsCRT::strcmp(aTopic, "quit-application") ||
+ !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ sShuttingDown = true;
+ KillTimers();
+ }
+
+ return NS_OK;
+}
+
+/****************************************************************
+ ************************** AutoFree ****************************
+ ****************************************************************/
+
+class AutoFree {
+public:
+ explicit AutoFree(void* aPtr) : mPtr(aPtr) {
+ }
+ ~AutoFree() {
+ if (mPtr)
+ free(mPtr);
+ }
+ void Invalidate() {
+ mPtr = 0;
+ }
+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(getter_AddRefs(presContext));
+
+ 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(nsGlobalWindow::Cast(win),
+ NS_LITERAL_STRING("error"),
+ 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)
+ : mWindow(aWindow)
+ , mReport(aReport)
+ , mError(aRootingCx, aError)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ 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(getter_AddRefs(presContext));
+
+ RootedDictionary<ErrorEventInit> init(rootingCx);
+ init.mCancelable = true;
+ init.mFilename = mReport->mFileName;
+ init.mBubbles = true;
+
+ NS_NAMED_LITERAL_STRING(xoriginMsg, "Script error.");
+ 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(nsGlobalWindow::Cast(win),
+ NS_LITERAL_STRING("error"), init);
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(win, nullptr, event, presContext,
+ &status);
+ }
+
+ if (status != nsEventStatus_eConsumeNoDefault) {
+ JS::Rooted<JSObject*> stack(rootingCx,
+ xpc::FindExceptionStackForConsoleReport(win, mError));
+ mReport->LogToConsoleWithStack(stack);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<xpc::ErrorReport> mReport;
+ JS::PersistentRootedValue mError;
+
+ 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)
+{
+ nsContentUtils::AddScriptRunner(new ScriptErrorEvent(win, rootingCx, xpcReport, exception));
+}
+
+} /* namespace xpc */
+
+#ifdef DEBUG
+// A couple of useful functions to call when you're debugging.
+nsGlobalWindow *
+JSObject2Win(JSObject *obj)
+{
+ return xpc::WindowOrNull(obj);
+}
+
+void
+PrintWinURI(nsGlobalWindow *win)
+{
+ if (!win) {
+ printf("No window passed in.\n");
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> 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
+PrintWinCodebase(nsGlobalWindow *win)
+{
+ if (!win) {
+ printf("No window passed in.\n");
+ return;
+ }
+
+ nsIPrincipal *prin = win->GetPrincipal();
+ if (!prin) {
+ printf("Window doesn't have principals.\n");
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ prin->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ printf("No URI, maybe the system principal.\n");
+ return;
+ }
+
+ printf("%s\n", uri->GetSpecOrDefault().get());
+}
+
+void
+DumpString(const nsAString &str)
+{
+ printf("%s\n", NS_ConvertUTF16toUTF8(str).get());
+}
+#endif
+
+#define JS_OPTIONS_DOT_STR "javascript.options."
+
+static const char js_options_dot_str[] = JS_OPTIONS_DOT_STR;
+
+nsJSContext::nsJSContext(bool aGCOnDestruction,
+ nsIScriptGlobalObject* aGlobalObject)
+ : mWindowProxy(nullptr)
+ , mGCOnDestruction(aGCOnDestruction)
+ , mGlobalObjectRef(aGlobalObject)
+{
+ EnsureStatics();
+
+ ++sContextCount;
+
+ mIsInitialized = false;
+ mProcessingScriptTag = false;
+ HoldJSObjects(this);
+}
+
+nsJSContext::~nsJSContext()
+{
+ mGlobalObjectRef = nullptr;
+
+ Destroy();
+
+ --sContextCount;
+
+ if (!sContextCount && sDidShutdown) {
+ // The last context is being deleted, and we're already in the
+ // process of shutting down, release the security manager.
+
+ NS_IF_RELEASE(sSecurityManager);
+ }
+}
+
+void
+nsJSContext::Destroy()
+{
+ if (mGCOnDestruction) {
+ PokeGC(JS::gcreason::NSJSCONTEXT_DESTROY);
+ }
+
+ 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->mIsInitialized = false;
+ 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_SCRIPT_OBJECTS
+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(nsIAtom *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::InitContext()
+{
+ // Make sure callers of this use
+ // WillInitializeContext/DidInitializeContext around this call.
+ NS_ENSURE_TRUE(!mIsInitialized, NS_ERROR_ALREADY_INITIALIZED);
+
+ // XXXbz Is there still a point to this function?
+ return NS_OK;
+}
+
+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::AutoValueVector args(cx);
+
+ JS::Rooted<JSObject*> global(cx, GetWindowProxy());
+ nsresult rv =
+ ConvertSupportsTojsvals(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(nsISupports* aArgs,
+ JS::Handle<JSObject*> aScope,
+ JS::AutoValueVector& 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);
+ AutoJSContext cx;
+
+ 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(cx, 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(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
+ JSAutoCompartment ac(cx, aScope);
+ rv = nsContentUtils::WrapNative(cx, arg, thisVal);
+ }
+ }
+ }
+ } else {
+ nsCOMPtr<nsIVariant> variant = do_QueryInterface(aArgs);
+ if (variant) {
+ rv = xpc->VariantToJS(cx, 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(nsISupports *aArg, JS::Value *aArgv)
+{
+ NS_PRECONDITION(aArg, "Empty arg");
+
+ nsCOMPtr<nsISupportsPrimitive> argPrimitive(do_QueryInterface(aArg));
+ if (!argPrimitive)
+ return NS_ERROR_NO_INTERFACE;
+
+ AutoJSContext cx;
+ 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(cx, 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(cx, 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(cx, &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(cx, GetWindowProxy());
+ JS::Rooted<JS::Value> v(cx);
+ JSAutoCompartment ac(cx, scope);
+ nsresult rv = nsContentUtils::WrapNative(cx, 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 :
+ case nsISupportsPrimitive::TYPE_VOID : {
+ 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_FS("JProfStartProfiling", JProfStartProfilingJS, 0, 0),
+ JS_FS("JProfStopProfiling", JProfStopProfilingJS, 0, 0),
+ JS_FS("JProfClearCircular", JProfClearCircularJS, 0, 0),
+ JS_FS("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();
+ JSAutoCompartment ac(cx, aGlobalObj);
+
+ // Attempt to initialize profiling functions
+ ::JS_DefineProfilingFunctions(cx, aGlobalObj);
+
+#ifdef MOZ_JPROF
+ // Attempt to initialize JProf functions
+ ::JS_DefineFunctions(cx, aGlobalObj, JProfFunctions);
+#endif
+
+ return NS_OK;
+}
+
+void
+nsJSContext::WillInitializeContext()
+{
+ mIsInitialized = false;
+}
+
+void
+nsJSContext::DidInitializeContext()
+{
+ mIsInitialized = true;
+}
+
+bool
+nsJSContext::IsContextInitialized()
+{
+ return mIsInitialized;
+}
+
+bool
+nsJSContext::GetProcessingScriptTag()
+{
+ return mProcessingScriptTag;
+}
+
+void
+nsJSContext::SetProcessingScriptTag(bool aFlag)
+{
+ mProcessingScriptTag = aFlag;
+}
+
+void
+FullGCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+ nsJSContext::KillFullGCTimer();
+ MOZ_ASSERT(!aClosure, "Don't pass a closure to FullGCTimerFired");
+ nsJSContext::GarbageCollectNow(JS::gcreason::FULL_GC_TIMER,
+ nsJSContext::IncrementalGC);
+}
+
+//static
+void
+nsJSContext::GarbageCollectNow(JS::gcreason::Reason aReason,
+ IsIncremental aIncremental,
+ IsShrinking aShrinking,
+ int64_t aSliceMillis)
+{
+ PROFILER_LABEL("nsJSContext", "GarbageCollectNow",
+ js::ProfileEntry::Category::GC);
+
+ MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC);
+
+ KillGCTimer();
+
+ // Reset sPendingLoadCount in case the timer that fired was a
+ // timer we scheduled due to a normal GC timer firing while
+ // documents were loading. If this happens we're waiting for a
+ // document that is taking a long time to load, and we effectively
+ // ignore the fact that the currently loading documents are still
+ // loading and move on as if they weren't.
+ sPendingLoadCount = 0;
+ sLoadingInProgress = false;
+
+ if (!nsContentUtils::XPConnect() || !sContext) {
+ return;
+ }
+
+ if (sCCLockedOut && aIncremental == IncrementalGC) {
+ // We're in the middle of incremental GC. Do another slice.
+ JS::PrepareForIncrementalGC(sContext);
+ JS::IncrementalGCSlice(sContext, aReason, aSliceMillis);
+ return;
+ }
+
+ JSGCInvocationKind gckind = aShrinking == ShrinkingGC ? GC_SHRINK : GC_NORMAL;
+
+ if (sNeedsFullGC || aReason != JS::gcreason::CC_WAITING) {
+ sNeedsFullGC = false;
+ JS::PrepareForFullGC(sContext);
+ } else {
+ CycleCollectedJSContext::Get()->PrepareWaitingZonesForGC();
+ }
+
+ if (aIncremental == IncrementalGC) {
+ JS::StartIncrementalGC(sContext, gckind, aReason, aSliceMillis);
+ } else {
+ JS::GCForReason(sContext, gckind, aReason);
+ }
+}
+
+static void
+FinishAnyIncrementalGC()
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GC);
+
+ if (sCCLockedOut) {
+ // We're in the middle of an incremental GC, so finish it.
+ JS::PrepareForIncrementalGC(sContext);
+ JS::FinishIncrementalGC(sContext, JS::gcreason::CC_FORCED);
+ }
+}
+
+static void
+FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless)
+{
+ PRTime startTime = PR_Now();
+ FinishAnyIncrementalGC();
+ bool earlyForgetSkippable =
+ sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS;
+ nsCycleCollector_forgetSkippable(aRemoveChildless, earlyForgetSkippable);
+ sPreviousSuspectedCount = nsCycleCollector_suspectedCount();
+ ++sCleanupsSinceLastGC;
+ PRTime delta = PR_Now() - startTime;
+ if (sMinForgetSkippableTime > delta) {
+ sMinForgetSkippableTime = delta;
+ }
+ if (sMaxForgetSkippableTime < delta) {
+ sMaxForgetSkippableTime = delta;
+ }
+ sTotalForgetSkippableTime += delta;
+ sRemovedPurples += (aSuspected - sPreviousSuspectedCount);
+ ++sForgetSkippableBeforeCC;
+}
+
+MOZ_ALWAYS_INLINE
+static uint32_t
+TimeBetween(TimeStamp start, TimeStamp end)
+{
+ MOZ_ASSERT(end >= start);
+ return (uint32_t) ((end - start).ToMilliseconds());
+}
+
+static uint32_t
+TimeUntilNow(TimeStamp start)
+{
+ if (start.IsNull()) {
+ return 0;
+ }
+ return TimeBetween(start, TimeStamp::Now());
+}
+
+struct CycleCollectorStats
+{
+ constexpr CycleCollectorStats() :
+ mMaxGCDuration(0), mRanSyncForgetSkippable(false), mSuspected(0),
+ mMaxSkippableDuration(0), mMaxSliceTime(0), mMaxSliceTimeSinceClear(0),
+ mTotalSliceTime(0), mAnyLockedOut(false), mExtraForgetSkippableCalls(0),
+ mFile(nullptr) {}
+
+ void Init()
+ {
+ Clear();
+ mMaxSliceTimeSinceClear = 0;
+
+ 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 Clear()
+ {
+ if (mFile && mFile != stdout && mFile != stderr) {
+ fclose(mFile);
+ }
+ mBeginSliceTime = TimeStamp();
+ mEndSliceTime = TimeStamp();
+ mBeginTime = TimeStamp();
+ mMaxGCDuration = 0;
+ mRanSyncForgetSkippable = false;
+ mSuspected = 0;
+ mMaxSkippableDuration = 0;
+ mMaxSliceTime = 0;
+ mTotalSliceTime = 0;
+ mAnyLockedOut = false;
+ mExtraForgetSkippableCalls = 0;
+ }
+
+ void PrepareForCycleCollectionSlice(int32_t aExtraForgetSkippableCalls = 0);
+
+ void FinishCycleCollectionSlice()
+ {
+ if (mBeginSliceTime.IsNull()) {
+ // We already called this method from EndCycleCollectionCallback for this slice.
+ return;
+ }
+
+ mEndSliceTime = TimeStamp::Now();
+ uint32_t sliceTime = TimeBetween(mBeginSliceTime, mEndSliceTime);
+ mMaxSliceTime = std::max(mMaxSliceTime, sliceTime);
+ mMaxSliceTimeSinceClear = std::max(mMaxSliceTimeSinceClear, sliceTime);
+ mTotalSliceTime += sliceTime;
+ mBeginSliceTime = TimeStamp();
+ MOZ_ASSERT(mExtraForgetSkippableCalls == 0, "Forget to reset extra forget skippable calls?");
+ }
+
+ void RunForgetSkippable();
+
+ // 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.
+ uint32_t mMaxGCDuration;
+
+ // True if we ran sync forget skippable in any slice of the current CC.
+ bool mRanSyncForgetSkippable;
+
+ // Number of suspected objects at the start of the current CC.
+ uint32_t mSuspected;
+
+ // The longest duration spent on sync forget skippable in any slice of the
+ // current CC.
+ uint32_t mMaxSkippableDuration;
+
+ // The longest pause of any slice in the current CC.
+ uint32_t mMaxSliceTime;
+
+ // The longest slice time since ClearMaxCCSliceTime() was called.
+ uint32_t mMaxSliceTimeSinceClear;
+
+ // The total amount of time spent actually running the current CC.
+ uint32_t mTotalSliceTime;
+
+ // True if we were locked out by the GC in any slice of the current CC.
+ bool mAnyLockedOut;
+
+ int32_t mExtraForgetSkippableCalls;
+
+ // A file to dump CC activity to; set by MOZ_CCTIMER environment variable.
+ FILE* mFile;
+};
+
+CycleCollectorStats gCCStats;
+
+void
+CycleCollectorStats::PrepareForCycleCollectionSlice(int32_t aExtraForgetSkippableCalls)
+{
+ mBeginSliceTime = TimeStamp::Now();
+
+ // Before we begin the cycle collection, make sure there is no active GC.
+ if (sCCLockedOut) {
+ mAnyLockedOut = true;
+ FinishAnyIncrementalGC();
+ uint32_t gcTime = TimeBetween(mBeginSliceTime, TimeStamp::Now());
+ mMaxGCDuration = std::max(mMaxGCDuration, gcTime);
+ }
+
+ mExtraForgetSkippableCalls = aExtraForgetSkippableCalls;
+}
+
+void
+CycleCollectorStats::RunForgetSkippable()
+{
+ // Run forgetSkippable synchronously to reduce the size of the CC graph. This
+ // is particularly useful if we recently finished a GC.
+ if (mExtraForgetSkippableCalls >= 0) {
+ TimeStamp beginForgetSkippable = TimeStamp::Now();
+ bool ranSyncForgetSkippable = false;
+ while (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS) {
+ FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
+ ranSyncForgetSkippable = true;
+ }
+
+ for (int32_t i = 0; i < mExtraForgetSkippableCalls; ++i) {
+ FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
+ ranSyncForgetSkippable = true;
+ }
+
+ if (ranSyncForgetSkippable) {
+ mMaxSkippableDuration =
+ std::max(mMaxSkippableDuration, TimeUntilNow(beginForgetSkippable));
+ mRanSyncForgetSkippable = true;
+ }
+
+ }
+ mExtraForgetSkippableCalls = 0;
+}
+
+//static
+void
+nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener,
+ int32_t aExtraForgetSkippableCalls)
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ PROFILER_LABEL("nsJSContext", "CycleCollectNow",
+ js::ProfileEntry::Category::CC);
+
+ gCCStats.PrepareForCycleCollectionSlice(aExtraForgetSkippableCalls);
+ nsCycleCollector_collect(aListener);
+ gCCStats.FinishCycleCollectionSlice();
+}
+
+//static
+void
+nsJSContext::RunCycleCollectorSlice()
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ PROFILER_LABEL("nsJSContext", "RunCycleCollectorSlice",
+ js::ProfileEntry::Category::CC);
+
+ gCCStats.PrepareForCycleCollectionSlice();
+
+ // Decide how long we want to budget for this slice. By default,
+ // use an unlimited budget.
+ js::SliceBudget budget = js::SliceBudget::unlimited();
+
+ if (sIncrementalCC) {
+ if (gCCStats.mBeginTime.IsNull()) {
+ // If no CC is in progress, use the standard slice time.
+ budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget));
+ } else {
+ TimeStamp now = TimeStamp::Now();
+
+ // Only run a limited slice if we're within the max running time.
+ if (TimeBetween(gCCStats.mBeginTime, now) < kMaxICCDuration) {
+ float sliceMultiplier = std::max(TimeBetween(gCCStats.mEndSliceTime, now) / (float)kICCIntersliceDelay, 1.0f);
+ budget = js::SliceBudget(js::TimeBudget(kICCSliceBudget * sliceMultiplier));
+ }
+ }
+ }
+
+ nsCycleCollector_collectSlice(budget, sDidPaintAfterPreviousICCSlice);
+ sDidPaintAfterPreviousICCSlice = false;
+
+ gCCStats.FinishCycleCollectionSlice();
+}
+
+//static
+void
+nsJSContext::RunCycleCollectorWorkSlice(int64_t aWorkBudget)
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ PROFILER_LABEL("nsJSContext", "RunCycleCollectorWorkSlice",
+ js::ProfileEntry::Category::CC);
+
+ gCCStats.PrepareForCycleCollectionSlice();
+
+ js::SliceBudget budget = js::SliceBudget(js::WorkBudget(aWorkBudget));
+ nsCycleCollector_collectSlice(budget);
+
+ gCCStats.FinishCycleCollectionSlice();
+}
+
+void
+nsJSContext::ClearMaxCCSliceTime()
+{
+ gCCStats.mMaxSliceTimeSinceClear = 0;
+}
+
+uint32_t
+nsJSContext::GetMaxCCSliceTimeSinceClear()
+{
+ return gCCStats.mMaxSliceTimeSinceClear;
+}
+
+static void
+ICCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+ if (sDidShutdown) {
+ return;
+ }
+
+ // Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us
+ // to synchronously finish the GC, which is bad.
+
+ if (sCCLockedOut) {
+ PRTime now = PR_Now();
+ if (sCCLockedOutTime == 0) {
+ sCCLockedOutTime = now;
+ return;
+ }
+ if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
+ return;
+ }
+ }
+
+ nsJSContext::RunCycleCollectorSlice();
+}
+
+//static
+void
+nsJSContext::BeginCycleCollectionCallback()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ gCCStats.mBeginTime = gCCStats.mBeginSliceTime.IsNull() ? TimeStamp::Now() : gCCStats.mBeginSliceTime;
+ gCCStats.mSuspected = nsCycleCollector_suspectedCount();
+
+ KillCCTimer();
+
+ gCCStats.RunForgetSkippable();
+
+ MOZ_ASSERT(!sICCTimer, "Tried to create a new ICC timer when one already existed.");
+
+ // Create an ICC timer even if ICC is globally disabled, because we could be manually triggering
+ // an incremental collection, and we want to be sure to finish it.
+ CallCreateInstance("@mozilla.org/timer;1", &sICCTimer);
+ if (sICCTimer) {
+ sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
+ kICCIntersliceDelay,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "ICCTimerFired");
+ }
+}
+
+static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
+
+//static
+void
+nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsJSContext::KillICCTimer();
+
+ // Update timing information for the current slice before we log it, if
+ // we previously called PrepareForCycleCollectionSlice(). During shutdown
+ // CCs, this won't happen.
+ gCCStats.FinishCycleCollectionSlice();
+
+ sCCollectedWaitingForGC += aResults.mFreedGCed;
+ sCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
+
+ TimeStamp endCCTimeStamp = TimeStamp::Now();
+ uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTimeStamp);
+
+ if (NeedsGCAfterCC()) {
+ PokeGC(JS::gcreason::CC_WAITING,
+ NS_GC_DELAY - std::min(ccNowDuration, kMaxICCDuration));
+ }
+
+ // Log information about the CC via telemetry, JSON and the console.
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, gCCStats.mAnyLockedOut);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, gCCStats.mRanSyncForgetSkippable);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL, ccNowDuration);
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_MAX_PAUSE, gCCStats.mMaxSliceTime);
+
+ if (!sLastCCEndTime.IsNull()) {
+ // TimeBetween returns milliseconds, but we want to report seconds.
+ uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime) / 1000;
+ Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween);
+ }
+ sLastCCEndTime = endCCTimeStamp;
+
+ Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
+ sMaxForgetSkippableTime / PR_USEC_PER_MSEC);
+
+ PRTime delta = GetCollectionTimeDelta();
+
+ uint32_t cleanups = sForgetSkippableBeforeCC ? sForgetSkippableBeforeCC : 1;
+ uint32_t minForgetSkippableTime = (sMinForgetSkippableTime == UINT32_MAX)
+ ? 0 : sMinForgetSkippableTime;
+
+ if (sPostGCEventsToConsole || gCCStats.mFile) {
+ nsCString mergeMsg;
+ if (aResults.mMergedZones) {
+ mergeMsg.AssignLiteral(" merged");
+ }
+
+ nsCString gcMsg;
+ if (aResults.mForcedGC) {
+ gcMsg.AssignLiteral(", forced a GC");
+ }
+
+ NS_NAMED_MULTILINE_LITERAL_STRING(kFmt,
+ u"CC(T+%.1f)[%s] max pause: %lums, total time: %lums, slices: %lu, suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu RCed and %lu GCed (%lu|%lu|%lu waiting for GC)%s\n"
+ u"ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, max sync: %lu ms, removed: %lu");
+ nsString msg;
+ msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC,
+ ProcessNameForCollectorLog(),
+ gCCStats.mMaxSliceTime, gCCStats.mTotalSliceTime,
+ aResults.mNumSlices, gCCStats.mSuspected,
+ aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(),
+ aResults.mFreedRefCounted, aResults.mFreedGCed,
+ sCCollectedWaitingForGC, sCCollectedZonesWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
+ gcMsg.get(),
+ sForgetSkippableBeforeCC,
+ minForgetSkippableTime / PR_USEC_PER_MSEC,
+ sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
+ (sTotalForgetSkippableTime / cleanups) /
+ PR_USEC_PER_MSEC,
+ sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
+ gCCStats.mMaxSkippableDuration, sRemovedPurples));
+ if (sPostGCEventsToConsole) {
+ nsCOMPtr<nsIConsoleService> cs =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+ if (gCCStats.mFile) {
+ fprintf(gCCStats.mFile, "%s\n", NS_ConvertUTF16toUTF8(msg).get());
+ }
+ }
+
+ if (sPostGCEventsToObserver) {
+ NS_NAMED_MULTILINE_LITERAL_STRING(kJSONFmt,
+ u"{ \"timestamp\": %llu, "
+ u"\"duration\": %lu, "
+ u"\"max_slice_pause\": %lu, "
+ u"\"total_slice_pause\": %lu, "
+ u"\"max_finish_gc_duration\": %lu, "
+ u"\"max_sync_skippable_duration\": %lu, "
+ 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\": %lu, "
+ u"\"max\": %lu, "
+ u"\"avg\": %lu, "
+ u"\"total\": %lu, "
+ u"\"removed\": %lu } "
+ u"}");
+ nsString json;
+
+ json.Adopt(nsTextFormatter::smprintf(kJSONFmt.get(), PR_Now(), ccNowDuration,
+ gCCStats.mMaxSliceTime,
+ gCCStats.mTotalSliceTime,
+ gCCStats.mMaxGCDuration,
+ gCCStats.mMaxSkippableDuration,
+ gCCStats.mSuspected,
+ aResults.mVisitedRefCounted, aResults.mVisitedGCed,
+ aResults.mFreedRefCounted, aResults.mFreedGCed,
+ sCCollectedWaitingForGC,
+ sCCollectedZonesWaitingForGC,
+ sLikelyShortLivingObjectsNeedingGC,
+ aResults.mForcedGC,
+ sForgetSkippableBeforeCC,
+ minForgetSkippableTime / PR_USEC_PER_MSEC,
+ sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
+ (sTotalForgetSkippableTime / cleanups) /
+ PR_USEC_PER_MSEC,
+ sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
+ sRemovedPurples));
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, "cycle-collection-statistics", json.get());
+ }
+ }
+
+ // Update global state to indicate we have just run a cycle collection.
+ sMinForgetSkippableTime = UINT32_MAX;
+ sMaxForgetSkippableTime = 0;
+ sTotalForgetSkippableTime = 0;
+ sRemovedPurples = 0;
+ sForgetSkippableBeforeCC = 0;
+ sNeedsFullCC = false;
+ sNeedsGCAfterCC = false;
+ gCCStats.Clear();
+}
+
+// static
+void
+InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure)
+{
+ nsJSContext::KillInterSliceGCTimer();
+ nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC,
+ nsJSContext::IncrementalGC,
+ nsJSContext::NonShrinkingGC,
+ NS_INTERSLICE_GC_BUDGET);
+}
+
+// static
+void
+GCTimerFired(nsITimer *aTimer, void *aClosure)
+{
+ nsJSContext::KillGCTimer();
+ uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
+ nsJSContext::GarbageCollectNow(static_cast<JS::gcreason::Reason>(reason),
+ nsJSContext::IncrementalGC);
+}
+
+// static
+void
+ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure)
+{
+ nsJSContext::KillShrinkingGCTimer();
+ sIsCompactingOnUserInactive = true;
+ nsJSContext::GarbageCollectNow(JS::gcreason::USER_INACTIVE,
+ nsJSContext::IncrementalGC,
+ nsJSContext::ShrinkingGC);
+}
+
+static bool
+ShouldTriggerCC(uint32_t aSuspected)
+{
+ return sNeedsFullCC ||
+ aSuspected > NS_CC_PURPLE_LIMIT ||
+ (aSuspected > NS_CC_FORCED_PURPLE_LIMIT &&
+ TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED);
+}
+
+static void
+CCTimerFired(nsITimer *aTimer, void *aClosure)
+{
+ if (sDidShutdown) {
+ return;
+ }
+
+ static uint32_t ccDelay = NS_CC_DELAY;
+ if (sCCLockedOut) {
+ ccDelay = NS_CC_DELAY / 3;
+
+ PRTime now = PR_Now();
+ if (sCCLockedOutTime == 0) {
+ // Reset sCCTimerFireCount so that we run forgetSkippable
+ // often enough before CC. Because of reduced ccDelay
+ // forgetSkippable will be called just a few times.
+ // NS_MAX_CC_LOCKEDOUT_TIME limit guarantees that we end up calling
+ // forgetSkippable and CycleCollectNow eventually.
+ sCCTimerFireCount = 0;
+ sCCLockedOutTime = now;
+ return;
+ }
+ if (now - sCCLockedOutTime < NS_MAX_CC_LOCKEDOUT_TIME) {
+ return;
+ }
+ }
+
+ ++sCCTimerFireCount;
+
+ // During early timer fires, we only run forgetSkippable. During the first
+ // late timer fire, we decide if we are going to have a second and final
+ // late timer fire, where we may begin to run the CC. Should run at least one
+ // early timer fire to allow cleanup before the CC.
+ int32_t numEarlyTimerFires = std::max((int32_t)ccDelay / NS_CC_SKIPPABLE_DELAY - 2, 1);
+ bool isLateTimerFire = sCCTimerFireCount > numEarlyTimerFires;
+ uint32_t suspected = nsCycleCollector_suspectedCount();
+ if (isLateTimerFire && ShouldTriggerCC(suspected)) {
+ if (sCCTimerFireCount == numEarlyTimerFires + 1) {
+ FireForgetSkippable(suspected, true);
+ if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+ // Our efforts to avoid a CC have failed, so we return to let the
+ // timer fire once more to trigger a CC.
+ return;
+ }
+ } else {
+ // 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.
+ nsJSContext::RunCycleCollectorSlice();
+ }
+ } else if (((sPreviousSuspectedCount + 100) <= suspected) ||
+ (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS)) {
+ // Only do a forget skippable if there are more than a few new objects
+ // or we're doing the initial forget skippables.
+ FireForgetSkippable(suspected, false);
+ }
+
+ if (isLateTimerFire) {
+ ccDelay = NS_CC_DELAY;
+
+ // We have either just run the CC or decided we don't want to run the CC
+ // next time, so kill the timer.
+ sPreviousSuspectedCount = 0;
+ nsJSContext::KillCCTimer();
+ }
+}
+
+// static
+uint32_t
+nsJSContext::CleanupsSinceLastGC()
+{
+ return sCleanupsSinceLastGC;
+}
+
+// static
+void
+nsJSContext::LoadStart()
+{
+ sLoadingInProgress = true;
+ ++sPendingLoadCount;
+}
+
+// static
+void
+nsJSContext::LoadEnd()
+{
+ if (!sLoadingInProgress)
+ return;
+
+ // sPendingLoadCount is not a well managed load counter (and doesn't
+ // need to be), so make sure we don't make it wrap backwards here.
+ if (sPendingLoadCount > 0) {
+ --sPendingLoadCount;
+ return;
+ }
+
+ // Its probably a good idea to GC soon since we have finished loading.
+ sLoadingInProgress = false;
+ PokeGC(JS::gcreason::LOAD_END);
+}
+
+// Only trigger expensive timers when they have been checked a number of times.
+static bool
+ReadyToTriggerExpensiveCollectorTimer()
+{
+ bool ready = kPokesBetweenExpensiveCollectorTriggers < ++sExpensiveCollectorPokes;
+ if (ready) {
+ sExpensiveCollectorPokes = 0;
+ }
+ return ready;
+}
+
+
+// Check all of the various collector timers and see if they are waiting to fire.
+// For the synchronous collector timers, sGCTimer and sCCTimer, we only want to trigger
+// the collection occasionally, because they are expensive. The incremental collector
+// timers, sInterSliceGCTimer and sICCTimer, are fast and need to be run many times, so
+// always run their corresponding timer.
+
+// This does not check sFullGCTimer, as that's an even more expensive collection we run
+// on a long timer.
+
+// static
+void
+nsJSContext::RunNextCollectorTimer()
+{
+ if (sShuttingDown) {
+ return;
+ }
+
+ if (sGCTimer) {
+ if (ReadyToTriggerExpensiveCollectorTimer()) {
+ GCTimerFired(nullptr, reinterpret_cast<void *>(JS::gcreason::DOM_WINDOW_UTILS));
+ }
+ return;
+ }
+
+ if (sInterSliceGCTimer) {
+ InterSliceGCTimerFired(nullptr, nullptr);
+ return;
+ }
+
+ // Check the CC timers after the GC timers, because the CC timers won't do
+ // anything if a GC is in progress.
+ MOZ_ASSERT(!sCCLockedOut, "Don't check the CC timers if the CC is locked out.");
+
+ if (sCCTimer) {
+ if (ReadyToTriggerExpensiveCollectorTimer()) {
+ CCTimerFired(nullptr, nullptr);
+ }
+ return;
+ }
+
+ if (sICCTimer) {
+ ICCTimerFired(nullptr, nullptr);
+ return;
+ }
+}
+
+// static
+void
+nsJSContext::PokeGC(JS::gcreason::Reason aReason, int aDelay)
+{
+ sNeedsFullGC = sNeedsFullGC || aReason != JS::gcreason::CC_WAITING;
+
+ if (sGCTimer || sInterSliceGCTimer || sShuttingDown) {
+ // There's already a timer for GC'ing, just return
+ return;
+ }
+
+ if (sCCTimer) {
+ // Make sure CC is called...
+ sNeedsFullCC = true;
+ // and GC after it.
+ sNeedsGCAfterCC = true;
+ return;
+ }
+
+ if (sICCTimer) {
+ // Make sure GC is called after the current CC completes.
+ // No need to set sNeedsFullCC because we are currently running a CC.
+ sNeedsGCAfterCC = true;
+ return;
+ }
+
+ CallCreateInstance("@mozilla.org/timer;1", &sGCTimer);
+
+ if (!sGCTimer) {
+ // Failed to create timer (probably because we're in XPCOM shutdown)
+ return;
+ }
+
+ static bool first = true;
+
+ sGCTimer->InitWithNamedFuncCallback(GCTimerFired,
+ reinterpret_cast<void *>(aReason),
+ aDelay
+ ? aDelay
+ : (first
+ ? NS_FIRST_GC_DELAY
+ : NS_GC_DELAY),
+ nsITimer::TYPE_ONE_SHOT,
+ "GCTimerFired");
+ first = false;
+}
+
+// static
+void
+nsJSContext::PokeShrinkingGC()
+{
+ if (sShrinkingGCTimer || sShuttingDown) {
+ return;
+ }
+
+ CallCreateInstance("@mozilla.org/timer;1", &sShrinkingGCTimer);
+
+ if (!sShrinkingGCTimer) {
+ // Failed to create timer (probably because we're in XPCOM shutdown)
+ return;
+ }
+
+ sShrinkingGCTimer->InitWithNamedFuncCallback(ShrinkingGCTimerFired, nullptr,
+ sCompactOnUserInactiveDelay,
+ nsITimer::TYPE_ONE_SHOT,
+ "ShrinkingGCTimerFired");
+}
+
+// static
+void
+nsJSContext::MaybePokeCC()
+{
+ if (sCCTimer || sICCTimer || sShuttingDown || !sHasRunGC) {
+ return;
+ }
+
+ if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+ sCCTimerFireCount = 0;
+ CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
+ if (!sCCTimer) {
+ return;
+ }
+ // We can kill some objects before running forgetSkippable.
+ nsCycleCollector_dispatchDeferredDeletion();
+
+ sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
+ NS_CC_SKIPPABLE_DELAY,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "CCTimerFired");
+ }
+}
+
+//static
+void
+nsJSContext::KillGCTimer()
+{
+ if (sGCTimer) {
+ sGCTimer->Cancel();
+ NS_RELEASE(sGCTimer);
+ }
+}
+
+void
+nsJSContext::KillFullGCTimer()
+{
+ if (sFullGCTimer) {
+ sFullGCTimer->Cancel();
+ NS_RELEASE(sFullGCTimer);
+ }
+}
+
+void
+nsJSContext::KillInterSliceGCTimer()
+{
+ if (sInterSliceGCTimer) {
+ sInterSliceGCTimer->Cancel();
+ NS_RELEASE(sInterSliceGCTimer);
+ }
+}
+
+//static
+void
+nsJSContext::KillShrinkingGCTimer()
+{
+ if (sShrinkingGCTimer) {
+ sShrinkingGCTimer->Cancel();
+ NS_RELEASE(sShrinkingGCTimer);
+ }
+}
+
+//static
+void
+nsJSContext::KillCCTimer()
+{
+ sCCLockedOutTime = 0;
+ if (sCCTimer) {
+ sCCTimer->Cancel();
+ NS_RELEASE(sCCTimer);
+ }
+}
+
+//static
+void
+nsJSContext::KillICCTimer()
+{
+ sCCLockedOutTime = 0;
+
+ if (sICCTimer) {
+ sICCTimer->Cancel();
+ NS_RELEASE(sICCTimer);
+ }
+}
+
+class NotifyGCEndRunnable : public Runnable
+{
+ nsString mMessage;
+
+public:
+ explicit NotifyGCEndRunnable(const nsString& aMessage) : mMessage(aMessage) {}
+
+ NS_DECL_NSIRUNNABLE
+};
+
+NS_IMETHODIMP
+NotifyGCEndRunnable::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_OK;
+ }
+
+ const char16_t oomMsg[3] = { '{', '}', 0 };
+ const char16_t *toSend = mMessage.get() ? mMessage.get() : oomMsg;
+ observerService->NotifyObservers(nullptr, "garbage-collection-statistics", toSend);
+
+ return NS_OK;
+}
+
+static void
+DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescription &aDesc)
+{
+ NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
+
+ switch (aProgress) {
+ case JS::GC_CYCLE_BEGIN: {
+ // Prevent cycle collections and shrinking during incremental GC.
+ sCCLockedOut = true;
+ break;
+ }
+
+ case JS::GC_CYCLE_END: {
+ PRTime delta = GetCollectionTimeDelta();
+
+ if (sPostGCEventsToConsole) {
+ NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f)[%s] ");
+ nsString prefix, gcstats;
+ gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
+ prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(),
+ double(delta) / PR_USEC_PER_SEC,
+ ProcessNameForCollectorLog()));
+ nsString msg = prefix + gcstats;
+ nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (cs) {
+ cs->LogStringMessage(msg.get());
+ }
+ }
+
+ if (!sShuttingDown) {
+ if (sPostGCEventsToObserver || Telemetry::CanRecordExtended()) {
+ nsString json;
+ json.Adopt(aDesc.formatJSON(aCx, PR_Now()));
+ RefPtr<NotifyGCEndRunnable> notify = new NotifyGCEndRunnable(json);
+ NS_DispatchToMainThread(notify);
+ }
+ }
+
+ sCCLockedOut = false;
+ sIsCompactingOnUserInactive = false;
+
+ // May need to kill the inter-slice GC timer
+ nsJSContext::KillInterSliceGCTimer();
+
+ sCCollectedWaitingForGC = 0;
+ sCCollectedZonesWaitingForGC = 0;
+ sLikelyShortLivingObjectsNeedingGC = 0;
+ sCleanupsSinceLastGC = 0;
+ sNeedsFullCC = true;
+ sHasRunGC = true;
+ nsJSContext::MaybePokeCC();
+
+ if (aDesc.isZone_) {
+ if (!sFullGCTimer && !sShuttingDown) {
+ CallCreateInstance("@mozilla.org/timer;1", &sFullGCTimer);
+ sFullGCTimer->InitWithNamedFuncCallback(FullGCTimerFired,
+ nullptr,
+ NS_FULL_GC_DELAY,
+ nsITimer::TYPE_ONE_SHOT,
+ "FullGCTimerFired");
+ }
+ } else {
+ nsJSContext::KillFullGCTimer();
+ }
+
+ if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+ nsCycleCollector_dispatchDeferredDeletion();
+ }
+
+ break;
+ }
+
+ case JS::GC_SLICE_BEGIN:
+ break;
+
+ case JS::GC_SLICE_END:
+
+ // The GC has more work to do, so schedule another GC slice.
+ nsJSContext::KillInterSliceGCTimer();
+ if (!sShuttingDown) {
+ CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer);
+ sInterSliceGCTimer->InitWithNamedFuncCallback(InterSliceGCTimerFired,
+ nullptr,
+ NS_INTERSLICE_GC_DELAY,
+ nsITimer::TYPE_ONE_SHOT,
+ "InterSliceGCTimerFired");
+ }
+
+ if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
+ nsCycleCollector_dispatchDeferredDeletion();
+ }
+
+ if (sPostGCEventsToConsole) {
+ NS_NAMED_LITERAL_STRING(kFmt, "[%s] ");
+ nsString prefix, gcstats;
+ gcstats.Adopt(aDesc.formatSliceMessage(aCx));
+ prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(),
+ ProcessNameForCollectorLog()));
+ 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()
+{
+ ++sLikelyShortLivingObjectsNeedingGC;
+}
+
+void
+mozilla::dom::StartupJSEnvironment()
+{
+ // initialize all our statics, so that we can restart XPCOM
+ sGCTimer = sShrinkingGCTimer = sFullGCTimer = sCCTimer = sICCTimer = nullptr;
+ sCCLockedOut = false;
+ sCCLockedOutTime = 0;
+ sLastCCEndTime = TimeStamp();
+ sHasRunGC = false;
+ sPendingLoadCount = 0;
+ sLoadingInProgress = false;
+ sCCollectedWaitingForGC = 0;
+ sCCollectedZonesWaitingForGC = 0;
+ sLikelyShortLivingObjectsNeedingGC = 0;
+ sPostGCEventsToConsole = false;
+ sNeedsFullCC = false;
+ sNeedsFullGC = false;
+ sNeedsGCAfterCC = false;
+ gNameSpaceManager = nullptr;
+ sContext = nullptr;
+ sIsInitialized = false;
+ sDidShutdown = false;
+ sShuttingDown = false;
+ sContextCount = 0;
+ sSecurityManager = nullptr;
+ gCCStats.Init();
+ sExpensiveCollectorPokes = 0;
+}
+
+static void
+SetMemoryHighWaterMarkPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ int32_t highwatermark = Preferences::GetInt(aPrefName, 128);
+
+ JS_SetGCParameter(sContext, JSGC_MAX_MALLOC_BYTES,
+ highwatermark * 1024L * 1024L);
+}
+
+static void
+SetMemoryMaxPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ int32_t pref = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ uint32_t max = (pref <= 0 || pref >= 0x1000) ? -1 : (uint32_t)pref * 1024 * 1024;
+ JS_SetGCParameter(sContext, JSGC_MAX_BYTES, max);
+}
+
+static void
+SetMemoryGCModePrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool enableZoneGC = Preferences::GetBool("javascript.options.mem.gc_per_zone");
+ bool enableIncrementalGC = Preferences::GetBool("javascript.options.mem.gc_incremental");
+ JSGCMode mode;
+ if (enableIncrementalGC) {
+ mode = JSGC_MODE_INCREMENTAL;
+ } else if (enableZoneGC) {
+ mode = JSGC_MODE_ZONE;
+ } else {
+ mode = JSGC_MODE_GLOBAL;
+ }
+ JS_SetGCParameter(sContext, JSGC_MODE, mode);
+}
+
+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)
+ JS_SetGCParameter(sContext, JSGC_SLICE_TIME_BUDGET, pref);
+}
+
+static void
+SetMemoryGCCompactingPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ JS_SetGCParameter(sContext, JSGC_COMPACTING_ENABLED, pref);
+}
+
+static void
+SetMemoryGCPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ int32_t pref = Preferences::GetInt(aPrefName, -1);
+ // handle overflow and negative pref values
+ if (pref >= 0 && pref < 10000)
+ JS_SetGCParameter(sContext, (JSGCParamKey)(intptr_t)aClosure, pref);
+}
+
+static void
+SetMemoryGCDynamicHeapGrowthPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ JS_SetGCParameter(sContext, JSGC_DYNAMIC_HEAP_GROWTH, pref);
+}
+
+static void
+SetMemoryGCDynamicMarkSlicePrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ JS_SetGCParameter(sContext, JSGC_DYNAMIC_MARK_SLICE, pref);
+}
+
+static void
+SetMemoryGCRefreshFrameSlicesEnabledPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ JS_SetGCParameter(sContext, JSGC_REFRESH_FRAME_SLICES_ENABLED, pref);
+}
+
+
+static void
+SetIncrementalCCPrefChangedCallback(const char* aPrefName, void* aClosure)
+{
+ bool pref = Preferences::GetBool(aPrefName);
+ sIncrementalCC = pref;
+}
+
+static bool
+AsmJSCacheOpenEntryForRead(JS::Handle<JSObject*> aGlobal,
+ const char16_t* aBegin,
+ const char16_t* aLimit,
+ size_t* aSize,
+ const uint8_t** aMemory,
+ intptr_t *aHandle)
+{
+ nsIPrincipal* principal =
+ nsJSPrincipals::get(JS_GetCompartmentPrincipals(js::GetObjectCompartment(aGlobal)));
+ return asmjscache::OpenEntryForRead(principal, aBegin, aLimit, aSize, aMemory,
+ aHandle);
+}
+
+static JS::AsmJSCacheResult
+AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
+ bool aInstalled,
+ const char16_t* aBegin,
+ const char16_t* aEnd,
+ size_t aSize,
+ uint8_t** aMemory,
+ intptr_t* aHandle)
+{
+ nsIPrincipal* principal =
+ nsJSPrincipals::get(JS_GetCompartmentPrincipals(js::GetObjectCompartment(aGlobal)));
+ return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd,
+ aSize, aMemory, aHandle);
+}
+
+class AsyncTaskRunnable final : public Runnable
+{
+ ~AsyncTaskRunnable()
+ {
+ MOZ_ASSERT(!mTask);
+ }
+
+public:
+ explicit AsyncTaskRunnable(JS::AsyncTask* aTask)
+ : mTask(aTask)
+ {
+ MOZ_ASSERT(mTask);
+ }
+
+protected:
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sContext == mTask->user);
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ mTask->finish(sContext);
+ mTask = nullptr; // mTask may delete itself
+
+ return NS_OK;
+ }
+
+private:
+ JS::AsyncTask* mTask;
+};
+
+static bool
+StartAsyncTaskCallback(JSContext* aCx, JS::AsyncTask* aTask)
+{
+ MOZ_ASSERT(aCx == sContext);
+ aTask->user = sContext;
+ return true;
+}
+
+static bool
+FinishAsyncTaskCallback(JS::AsyncTask* aTask)
+{
+ // AsyncTasks can finish during shutdown so cannot simply
+ // NS_DispatchToMainThread.
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ if (!mainThread) {
+ return false;
+ }
+
+ RefPtr<AsyncTaskRunnable> r = new AsyncTaskRunnable(aTask);
+ MOZ_ALWAYS_SUCCEEDS(mainThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
+ return true;
+}
+
+void
+nsJSContext::EnsureStatics()
+{
+ if (sIsInitialized) {
+ if (!nsContentUtils::XPConnect()) {
+ MOZ_CRASH();
+ }
+ return;
+ }
+
+ nsresult rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID,
+ &sSecurityManager);
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH();
+ }
+
+ sContext = danger::GetJSContext();
+ if (!sContext) {
+ MOZ_CRASH();
+ }
+
+ // Let's make sure that our main thread is the same as the xpcom main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sPrevGCSliceCallback = JS::SetGCSliceCallback(sContext, DOMGCSliceCallback);
+
+ // Set up the asm.js cache callbacks
+ static const JS::AsmJSCacheOps asmJSCacheOps = {
+ AsmJSCacheOpenEntryForRead,
+ asmjscache::CloseEntryForRead,
+ AsmJSCacheOpenEntryForWrite,
+ asmjscache::CloseEntryForWrite
+ };
+ JS::SetAsmJSCacheOps(sContext, &asmJSCacheOps);
+
+ JS::SetAsyncTaskCallbacks(sContext, StartAsyncTaskCallback, FinishAsyncTaskCallback);
+
+ // Set these global xpconnect options...
+ Preferences::RegisterCallbackAndCall(SetMemoryHighWaterMarkPrefChangedCallback,
+ "javascript.options.mem.high_water_mark");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryMaxPrefChangedCallback,
+ "javascript.options.mem.max");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
+ "javascript.options.mem.gc_per_zone");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
+ "javascript.options.mem.gc_incremental");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCSliceTimePrefChangedCallback,
+ "javascript.options.mem.gc_incremental_slice_ms");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCCompactingPrefChangedCallback,
+ "javascript.options.mem.gc_compacting");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_time_limit_ms",
+ (void *)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCDynamicMarkSlicePrefChangedCallback,
+ "javascript.options.mem.gc_dynamic_mark_slice");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCRefreshFrameSlicesEnabledPrefChangedCallback,
+ "javascript.options.mem.gc_refresh_frame_slices_enabled");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCDynamicHeapGrowthPrefChangedCallback,
+ "javascript.options.mem.gc_dynamic_heap_growth");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_low_frequency_heap_growth",
+ (void *)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_heap_growth_min",
+ (void *)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_heap_growth_max",
+ (void *)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_low_limit_mb",
+ (void *)JSGC_HIGH_FREQUENCY_LOW_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_high_frequency_high_limit_mb",
+ (void *)JSGC_HIGH_FREQUENCY_HIGH_LIMIT);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_allocation_threshold_mb",
+ (void *)JSGC_ALLOCATION_THRESHOLD);
+
+ Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
+ "dom.cycle_collector.incremental");
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_min_empty_chunk_count",
+ (void *)JSGC_MIN_EMPTY_CHUNK_COUNT);
+
+ Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+ "javascript.options.mem.gc_max_empty_chunk_count",
+ (void *)JSGC_MAX_EMPTY_CHUNK_COUNT);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ MOZ_CRASH();
+ }
+
+ Preferences::AddBoolVarCache(&sGCOnMemoryPressure,
+ "javascript.options.gc_on_memory_pressure",
+ true);
+
+ Preferences::AddBoolVarCache(&sCompactOnUserInactive,
+ "javascript.options.compact_on_user_inactive",
+ true);
+
+ Preferences::AddUintVarCache(&sCompactOnUserInactiveDelay,
+ "javascript.options.compact_on_user_inactive_delay",
+ NS_DEAULT_INACTIVE_GC_DELAY);
+
+ Preferences::AddBoolVarCache(&sPostGCEventsToConsole,
+ JS_OPTIONS_DOT_STR "mem.log");
+ Preferences::AddBoolVarCache(&sPostGCEventsToObserver,
+ JS_OPTIONS_DOT_STR "mem.notify");
+
+ 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);
+
+ sIsInitialized = true;
+}
+
+void
+nsJSContext::NotifyDidPaint()
+{
+ sDidPaintAfterPreviousICCSlice = true;
+ if (sICCTimer) {
+ static uint32_t sCount = 0;
+ // 16 here is the common value for refresh driver tick frequency.
+ static const uint32_t kTicksPerSliceDelay = kICCIntersliceDelay / 16;
+ if (++sCount % kTicksPerSliceDelay != 0) {
+ // Don't trigger CC slice all the time after paint, but often still.
+ // The key point is to trigger it right after paint, especially when
+ // we're running RefreshDriver constantly.
+ return;
+ }
+
+ sICCTimer->Cancel();
+ ICCTimerFired(nullptr, nullptr);
+ if (sICCTimer) {
+ sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
+ kICCIntersliceDelay,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "ICCTimerFired");
+ }
+ } else if (sCCTimer) {
+ static uint32_t sCount = 0;
+ static const uint32_t kTicksPerForgetSkippableDelay =
+ NS_CC_SKIPPABLE_DELAY / 16;
+ if (++sCount % kTicksPerForgetSkippableDelay != 0) {
+ // The comment above about triggering CC slice applies to forget skippable
+ // too.
+ return;
+ }
+
+ sCCTimer->Cancel();
+ CCTimerFired(nullptr, nullptr);
+ if (sCCTimer) {
+ sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
+ NS_CC_SKIPPABLE_DELAY,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "CCTimerFired");
+ }
+ }
+}
+
+nsScriptNameSpaceManager*
+mozilla::dom::GetNameSpaceManager()
+{
+ if (sDidShutdown)
+ return nullptr;
+
+ if (!gNameSpaceManager) {
+ gNameSpaceManager = new nsScriptNameSpaceManager;
+ NS_ADDREF(gNameSpaceManager);
+
+ nsresult rv = gNameSpaceManager->Init();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+
+ return gNameSpaceManager;
+}
+
+nsScriptNameSpaceManager*
+mozilla::dom::PeekNameSpaceManager()
+{
+ return gNameSpaceManager;
+}
+
+void
+mozilla::dom::ShutdownJSEnvironment()
+{
+ KillTimers();
+
+ NS_IF_RELEASE(gNameSpaceManager);
+
+ if (!sContextCount) {
+ // We're being shutdown, and there are no more contexts
+ // alive, release the security manager.
+ NS_IF_RELEASE(sSecurityManager);
+ }
+
+ sShuttingDown = true;
+ sDidShutdown = true;
+}
+
+// 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()
+{
+ if (mArgv) {
+ 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_SCRIPT_OBJECTS
+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::Enumerate(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 000000000..c1a9baf52
--- /dev/null
+++ b/dom/base/nsJSEnvironment.h
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIObserver.h"
+#include "prtime.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIXPConnect.h"
+#include "nsIArray.h"
+#include "mozilla/Attributes.h"
+#include "nsThreadUtils.h"
+#include "xpcpublic.h"
+
+class nsICycleCollectorListener;
+class nsScriptNameSpaceManager;
+
+namespace JS {
+class AutoValueVector;
+} // namespace JS
+
+namespace mozilla {
+template <class> class Maybe;
+struct CycleCollectorResults;
+} // namespace mozilla
+
+// The amount of time we wait between a request to GC (due to leaving
+// a page) and doing the actual GC.
+#define NS_GC_DELAY 4000 // ms
+
+#define NS_MAJOR_FORGET_SKIPPABLE_CALLS 5
+
+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 InitContext() override;
+ virtual bool IsContextInitialized() override;
+
+ 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 WillInitializeContext() override;
+ virtual void DidInitializeContext() override;
+
+ virtual void SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) override;
+ virtual JSObject* GetWindowProxy() override;
+
+ static void LoadStart();
+ static void LoadEnd();
+
+ enum IsShrinking {
+ ShrinkingGC,
+ NonShrinkingGC
+ };
+
+ enum IsIncremental {
+ IncrementalGC,
+ NonIncrementalGC
+ };
+
+ // Setup all the statics etc - safe to call multiple times after Startup().
+ void EnsureStatics();
+
+ static void GarbageCollectNow(JS::gcreason::Reason reason,
+ IsIncremental aIncremental = NonIncrementalGC,
+ IsShrinking aShrinking = NonShrinkingGC,
+ int64_t aSliceMillis = 0);
+
+ // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be
+ // called even if the previous collection was GC.
+ static void CycleCollectNow(nsICycleCollectorListener *aListener = nullptr,
+ int32_t aExtraForgetSkippableCalls = 0);
+
+ // Run a cycle collector slice, using a heuristic to decide how long to run it.
+ static void RunCycleCollectorSlice();
+
+ // Run a cycle collector slice, using the given work budget.
+ static void RunCycleCollectorWorkSlice(int64_t aWorkBudget);
+
+ static void BeginCycleCollectionCallback();
+ static void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults);
+
+ // Return the longest CC slice time since ClearMaxCCSliceTime() was last called.
+ static uint32_t GetMaxCCSliceTimeSinceClear();
+ static void ClearMaxCCSliceTime();
+
+ static void RunNextCollectorTimer();
+
+ static void PokeGC(JS::gcreason::Reason aReason, int aDelay = 0);
+ static void KillGCTimer();
+
+ static void PokeShrinkingGC();
+ static void KillShrinkingGCTimer();
+
+ static void MaybePokeCC();
+ static void KillCCTimer();
+ static void KillICCTimer();
+ static void KillFullGCTimer();
+ static void KillInterSliceGCTimer();
+
+ // Calling LikelyShortLivingObjectCreated() makes a GC more likely.
+ static void LikelyShortLivingObjectCreated();
+
+ static uint32_t CleanupsSinceLastGC();
+
+ 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;
+ }
+
+ static void NotifyDidPaint();
+protected:
+ virtual ~nsJSContext();
+
+ // Helper to convert xpcom datatypes to jsvals.
+ nsresult ConvertSupportsTojsvals(nsISupports *aArgs,
+ JS::Handle<JSObject*> aScope,
+ JS::AutoValueVector &aArgsOut);
+
+ nsresult AddSupportsPrimitiveTojsvals(nsISupports *aArg, JS::Value *aArgv);
+
+private:
+ void Destroy();
+
+ JS::Heap<JSObject*> mWindowProxy;
+
+ bool mIsInitialized;
+ bool mGCOnDestruction;
+ bool mProcessingScriptTag;
+
+ PRTime mModalStateTime;
+ uint32_t mModalStateDepth;
+
+ // 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 {
+namespace dom {
+
+void StartupJSEnvironment();
+void ShutdownJSEnvironment();
+
+// Get the NameSpaceManager, creating if necessary
+nsScriptNameSpaceManager* GetNameSpaceManager();
+
+// Peek the NameSpaceManager, without creating it.
+nsScriptNameSpaceManager* PeekNameSpaceManager();
+
+// Runnable that's used to do async error reporting
+class AsyncErrorReporter final : public mozilla::Runnable
+{
+public:
+ // aWindow may be null if this error report is not associated with a window
+ explicit AsyncErrorReporter(xpc::ErrorReport* aReport)
+ : mReport(aReport)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mReport->LogToConsole();
+ return NS_OK;
+ }
+
+protected:
+ RefPtr<xpc::ErrorReport> mReport;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+// 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/nsJSTimeoutHandler.cpp b/dom/base/nsJSTimeoutHandler.cpp
new file mode 100644
index 000000000..8736cd1dd
--- /dev/null
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -0,0 +1,391 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <algorithm>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Function.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "nsAXPCNativeCallContext.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsGlobalWindow.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocument.h"
+#include "nsIScriptTimeoutHandler.h"
+#include "nsIXPConnect.h"
+#include "nsJSUtils.h"
+#include "WorkerPrivate.h"
+
+static const char kSetIntervalStr[] = "setInterval";
+static const char kSetTimeoutStr[] = "setTimeout";
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+// Our JS nsIScriptTimeoutHandler implementation.
+class nsJSScriptTimeoutHandler final : public nsIScriptTimeoutHandler
+{
+public:
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSScriptTimeoutHandler)
+
+ nsJSScriptTimeoutHandler();
+ // This will call SwapElements on aArguments with an empty array.
+ nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow,
+ Function& aFunction,
+ nsTArray<JS::Heap<JS::Value>>&& aArguments,
+ ErrorResult& aError);
+ nsJSScriptTimeoutHandler(JSContext* aCx, nsGlobalWindow* aWindow,
+ const nsAString& aExpression, bool* aAllowEval,
+ ErrorResult& aError);
+ nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ Function& aFunction,
+ nsTArray<JS::Heap<JS::Value>>&& aArguments);
+ nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ const nsAString& aExpression);
+
+ virtual const nsAString& GetHandlerText() override;
+
+ virtual Function* GetCallback() override
+ {
+ return mFunction;
+ }
+
+ virtual const nsTArray<JS::Value>& GetArgs() override
+ {
+ return mArgs;
+ }
+
+ virtual nsresult Call() override
+ {
+ return NS_OK;
+ }
+
+ virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
+ uint32_t* aColumn) override
+ {
+ *aFileName = mFileName.get();
+ *aLineNo = mLineNo;
+ *aColumn = mColumn;
+ }
+
+ virtual void MarkForCC() override
+ {
+ if (mFunction) {
+ mFunction->MarkForCC();
+ }
+ }
+
+ void ReleaseJSObjects();
+
+private:
+ ~nsJSScriptTimeoutHandler();
+
+ void Init(JSContext* aCx,
+ nsTArray<JS::Heap<JS::Value>>&& aArguments);
+ void Init(JSContext* aCx);
+
+ // filename, line number and JS language version string of the
+ // caller of setTimeout()
+ nsCString mFileName;
+ uint32_t mLineNo;
+ uint32_t mColumn;
+ nsTArray<JS::Heap<JS::Value>> mArgs;
+
+ // The expression to evaluate or function to call. If mFunction is non-null
+ // it should be used, else use mExpr.
+ nsString mExpr;
+ RefPtr<Function> mFunction;
+};
+
+
+// nsJSScriptTimeoutHandler
+// QueryInterface implementation for nsJSScriptTimeoutHandler
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSScriptTimeoutHandler)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSScriptTimeoutHandler)
+ tmp->ReleaseJSObjects();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsJSScriptTimeoutHandler)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ nsAutoCString name("nsJSScriptTimeoutHandler");
+ if (tmp->mFunction) {
+ JSObject* obj = tmp->mFunction->CallablePreserveColor();
+ JSFunction* fun = JS_GetObjectFunction(js::UncheckedUnwrap(obj));
+ if (fun && JS_GetFunctionId(fun)) {
+ JSFlatString *funId = JS_ASSERT_STRING_IS_FLAT(JS_GetFunctionId(fun));
+ size_t size = 1 + JS_PutEscapedFlatString(nullptr, 0, funId, 0);
+ char *funIdName = new char[size];
+ if (funIdName) {
+ JS_PutEscapedFlatString(funIdName, size, funId, 0);
+ name.AppendLiteral(" [");
+ name.Append(funIdName);
+ delete[] funIdName;
+ name.Append(']');
+ }
+ }
+ } else {
+ 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(nsJSScriptTimeoutHandler,
+ tmp->mRefCnt.get())
+ }
+
+ if (tmp->mFunction) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFunction)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSScriptTimeoutHandler)
+ for (uint32_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(nsJSScriptTimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptTimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler)
+
+static bool
+CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError)
+{
+ // if CSP is enabled, and setTimeout/setInterval was called with a string,
+ // disable the registration and log an error
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ // if there's no document, we don't have to do anything.
+ return true;
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ aError = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp));
+ if (aError.Failed()) {
+ return false;
+ }
+
+ if (!csp) {
+ return true;
+ }
+
+ bool allowsEval = true;
+ bool reportViolation = false;
+ aError = csp->GetAllowsEval(&reportViolation, &allowsEval);
+ if (aError.Failed()) {
+ return false;
+ }
+
+ if (reportViolation) {
+ // TODO : need actual script sample in violation report.
+ NS_NAMED_LITERAL_STRING(scriptSample,
+ "call to eval() or related function blocked by CSP");
+
+ // Get the calling location.
+ uint32_t lineNum = 0;
+ nsAutoString fileNameString;
+ if (!nsJSUtils::GetCallingLocation(aCx, fileNameString, &lineNum)) {
+ fileNameString.AssignLiteral("unknown");
+ }
+
+ csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
+ fileNameString, scriptSample, lineNum,
+ EmptyString(), EmptyString());
+ }
+
+ return allowsEval;
+}
+
+nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler()
+ : mLineNo(0)
+ , mColumn(0)
+{
+}
+
+nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
+ nsGlobalWindow *aWindow,
+ Function& aFunction,
+ nsTArray<JS::Heap<JS::Value>>&& aArguments,
+ ErrorResult& aError)
+ : mLineNo(0)
+ , mColumn(0)
+ , mFunction(&aFunction)
+{
+ if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
+ // 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;
+ }
+
+ Init(aCx, Move(aArguments));
+}
+
+nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
+ nsGlobalWindow *aWindow,
+ const nsAString& aExpression,
+ bool* aAllowEval,
+ ErrorResult& aError)
+ : mLineNo(0)
+ , mColumn(0)
+ , mExpr(aExpression)
+{
+ if (!aWindow->GetContextInternal() || !aWindow->FastGetGlobalJSObject()) {
+ // 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;
+ }
+
+ *aAllowEval = CheckCSPForEval(aCx, aWindow, aError);
+ if (aError.Failed() || !*aAllowEval) {
+ return;
+ }
+
+ Init(aCx);
+}
+
+nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate,
+ Function& aFunction,
+ nsTArray<JS::Heap<JS::Value>>&& aArguments)
+ : mLineNo(0)
+ , mColumn(0)
+ , mFunction(&aFunction)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ Init(aCx, Move(aArguments));
+}
+
+nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate,
+ const nsAString& aExpression)
+ : mLineNo(0)
+ , mColumn(0)
+ , mExpr(aExpression)
+{
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ Init(aCx);
+}
+
+nsJSScriptTimeoutHandler::~nsJSScriptTimeoutHandler()
+{
+ ReleaseJSObjects();
+}
+
+void
+nsJSScriptTimeoutHandler::Init(JSContext* aCx,
+ nsTArray<JS::Heap<JS::Value>>&& aArguments)
+{
+ mozilla::HoldJSObjects(this);
+ mArgs = Move(aArguments);
+
+ Init(aCx);
+}
+
+void
+nsJSScriptTimeoutHandler::Init(JSContext* aCx)
+{
+ // Get the calling location.
+ nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn);
+}
+
+void
+nsJSScriptTimeoutHandler::ReleaseJSObjects()
+{
+ if (mFunction) {
+ mFunction = nullptr;
+ mArgs.Clear();
+ mozilla::DropJSObjects(this);
+ }
+}
+
+const nsAString&
+nsJSScriptTimeoutHandler::GetHandlerText()
+{
+ NS_ASSERTION(!mFunction, "No expression, so no handler text!");
+ return mExpr;
+}
+
+already_AddRefed<nsIScriptTimeoutHandler>
+NS_CreateJSTimeoutHandler(JSContext *aCx, nsGlobalWindow *aWindow,
+ Function& aFunction,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aError)
+{
+ nsTArray<JS::Heap<JS::Value>> args;
+ if (!args.AppendElements(aArguments, fallible)) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ RefPtr<nsJSScriptTimeoutHandler> handler =
+ new nsJSScriptTimeoutHandler(aCx, aWindow, aFunction, Move(args), aError);
+ return aError.Failed() ? nullptr : handler.forget();
+}
+
+already_AddRefed<nsIScriptTimeoutHandler>
+NS_CreateJSTimeoutHandler(JSContext* aCx, nsGlobalWindow *aWindow,
+ const nsAString& aExpression, ErrorResult& aError)
+{
+ bool allowEval = false;
+ RefPtr<nsJSScriptTimeoutHandler> handler =
+ new nsJSScriptTimeoutHandler(aCx, aWindow, aExpression, &allowEval, aError);
+ if (aError.Failed() || !allowEval) {
+ return nullptr;
+ }
+
+ return handler.forget();
+}
+
+already_AddRefed<nsIScriptTimeoutHandler>
+NS_CreateJSTimeoutHandler(JSContext *aCx, WorkerPrivate* aWorkerPrivate,
+ Function& aFunction,
+ const Sequence<JS::Value>& aArguments,
+ ErrorResult& aError)
+{
+ nsTArray<JS::Heap<JS::Value>> args;
+ if (!args.AppendElements(aArguments, fallible)) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ RefPtr<nsJSScriptTimeoutHandler> handler =
+ new nsJSScriptTimeoutHandler(aCx, aWorkerPrivate, aFunction, Move(args));
+ return handler.forget();
+}
+
+already_AddRefed<nsIScriptTimeoutHandler>
+NS_CreateJSTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ const nsAString& aExpression)
+{
+ RefPtr<nsJSScriptTimeoutHandler> handler =
+ new nsJSScriptTimeoutHandler(aCx, aWorkerPrivate, aExpression);
+ return handler.forget();
+}
diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp
new file mode 100644
index 000000000..98b367b66
--- /dev/null
+++ b/dom/base/nsJSUtils.cpp
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIXPConnect.h"
+#include "nsCOMPtr.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsPIDOMWindow.h"
+#include "GeckoProfiler.h"
+#include "nsJSPrincipals.h"
+#include "xpcpublic.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Date.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+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;
+ }
+
+ aFilename.Assign(filename.get());
+ return true;
+}
+
+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;
+ }
+
+ aFilename.Assign(NS_ConvertUTF8toUTF16(filename.get()));
+ return true;
+}
+
+nsIScriptGlobalObject *
+nsJSUtils::GetStaticScriptGlobal(JSObject* aObj)
+{
+ if (!aObj)
+ return nullptr;
+ return xpc::WindowGlobalOrNull(aObj);
+}
+
+nsIScriptContext *
+nsJSUtils::GetStaticScriptContext(JSObject* aObj)
+{
+ nsIScriptGlobalObject *nativeGlobal = GetStaticScriptGlobal(aObj);
+ if (!nativeGlobal)
+ return nullptr;
+
+ return nativeGlobal->GetScriptContext();
+}
+
+uint64_t
+nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext)
+{
+ if (!aContext)
+ return 0;
+
+ nsGlobalWindow* win = xpc::CurrentWindowOrNull(aContext);
+ return win ? win->WindowID() : 0;
+}
+
+nsresult
+nsJSUtils::CompileFunction(AutoJSAPI& jsapi,
+ JS::AutoObjectVector& 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::GetEnterCompartmentDepth(cx) > 0);
+ MOZ_ASSERT_IF(aScopeChain.length() != 0,
+ js::IsObjectInContextCompartment(aScopeChain[0], cx));
+ MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN);
+
+ // 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.
+ JS::Rooted<JSFunction*> fun(cx);
+ if (!JS::CompileFunction(cx, aScopeChain, aOptions,
+ PromiseFlatCString(aName).get(),
+ aArgCount, aArgArray,
+ PromiseFlatString(aBody).get(),
+ aBody.Length(), &fun))
+ {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aFunctionObject = JS_GetFunctionObject(fun);
+ return NS_OK;
+}
+
+nsresult
+nsJSUtils::EvaluateString(JSContext* aCx,
+ const nsAString& aScript,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions& aCompileOptions,
+ const EvaluateOptions& aEvaluateOptions,
+ JS::MutableHandle<JS::Value> aRetValue)
+{
+ const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
+ JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(),
+ JS::SourceBufferHolder::NoOwnership);
+ return EvaluateString(aCx, srcBuf, aEvaluationGlobal, aCompileOptions,
+ aEvaluateOptions, aRetValue, nullptr);
+}
+
+nsresult
+nsJSUtils::EvaluateString(JSContext* aCx,
+ JS::SourceBufferHolder& aSrcBuf,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions& aCompileOptions,
+ const EvaluateOptions& aEvaluateOptions,
+ JS::MutableHandle<JS::Value> aRetValue,
+ void **aOffThreadToken)
+{
+ PROFILER_LABEL("nsJSUtils", "EvaluateString",
+ js::ProfileEntry::Category::JS);
+
+ MOZ_ASSERT_IF(aCompileOptions.versionSet,
+ aCompileOptions.version != JSVERSION_UNKNOWN);
+ MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, !aCompileOptions.noScriptRval);
+ MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
+ MOZ_ASSERT(aSrcBuf.get());
+ MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) ==
+ aEvaluationGlobal);
+ MOZ_ASSERT_IF(aOffThreadToken, aCompileOptions.noScriptRval);
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(nsContentUtils::IsInMicroTask());
+
+ // Unfortunately, the JS engine actually compiles scripts with a return value
+ // in a different, less efficient way. Furthermore, it can't JIT them in many
+ // cases. So we need to be explicitly told whether the caller cares about the
+ // return value. Callers can do this by calling the other overload of
+ // EvaluateString() which calls this function with
+ // aCompileOptions.noScriptRval set to true.
+ aRetValue.setUndefined();
+
+ nsresult rv = NS_OK;
+
+ NS_ENSURE_TRUE(xpc::Scriptability::Get(aEvaluationGlobal).Allowed(), NS_OK);
+
+ bool ok = true;
+ // Scope the JSAutoCompartment so that we can later wrap the return value
+ // into the caller's cx.
+ {
+ JSAutoCompartment ac(aCx, aEvaluationGlobal);
+
+ // Now make sure to wrap the scope chain into the right compartment.
+ JS::AutoObjectVector scopeChain(aCx);
+ if (!scopeChain.reserve(aEvaluateOptions.scopeChain.length())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (size_t i = 0; i < aEvaluateOptions.scopeChain.length(); ++i) {
+ JS::ExposeObjectToActiveJS(aEvaluateOptions.scopeChain[i]);
+ scopeChain.infallibleAppend(aEvaluateOptions.scopeChain[i]);
+ if (!JS_WrapObject(aCx, scopeChain[i])) {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ok && aOffThreadToken) {
+ JS::Rooted<JSScript*>
+ script(aCx, JS::FinishOffThreadScript(aCx, *aOffThreadToken));
+ *aOffThreadToken = nullptr; // Mark the token as having been finished.
+ if (script) {
+ ok = JS_ExecuteScript(aCx, scopeChain, script);
+ } else {
+ ok = false;
+ }
+ } else if (ok) {
+ ok = JS::Evaluate(aCx, scopeChain, aCompileOptions, aSrcBuf, aRetValue);
+ }
+
+ if (ok && aEvaluateOptions.coerceToString && !aRetValue.isUndefined()) {
+ JS::Rooted<JS::Value> value(aCx, aRetValue);
+ JSString* str = JS::ToString(aCx, value);
+ ok = !!str;
+ aRetValue.set(ok ? JS::StringValue(str) : JS::UndefinedValue());
+ }
+ }
+
+ if (!ok) {
+ if (JS_IsExceptionPending(aCx)) {
+ rv = NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW;
+ } else {
+ rv = NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE;
+ }
+
+ if (!aCompileOptions.noScriptRval) {
+ aRetValue.setUndefined();
+ }
+ }
+
+ // Wrap the return value into whatever compartment aCx was in.
+ if (ok && !aCompileOptions.noScriptRval) {
+ if (!JS_WrapValue(aCx, aRetValue)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsJSUtils::EvaluateString(JSContext* aCx,
+ JS::SourceBufferHolder& aSrcBuf,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions& aCompileOptions,
+ const EvaluateOptions& aEvaluateOptions,
+ JS::MutableHandle<JS::Value> aRetValue)
+{
+ return EvaluateString(aCx, aSrcBuf, aEvaluationGlobal, aCompileOptions,
+ aEvaluateOptions, aRetValue, nullptr);
+}
+
+nsresult
+nsJSUtils::EvaluateString(JSContext* aCx,
+ const nsAString& aScript,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions& aCompileOptions)
+{
+ EvaluateOptions options(aCx);
+ aCompileOptions.setNoScriptRval(true);
+ JS::RootedValue unused(aCx);
+ return EvaluateString(aCx, aScript, aEvaluationGlobal, aCompileOptions,
+ options, &unused);
+}
+
+nsresult
+nsJSUtils::EvaluateString(JSContext* aCx,
+ JS::SourceBufferHolder& aSrcBuf,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions& aCompileOptions,
+ void **aOffThreadToken)
+{
+ EvaluateOptions options(aCx);
+ aCompileOptions.setNoScriptRval(true);
+ JS::RootedValue unused(aCx);
+ return EvaluateString(aCx, aSrcBuf, aEvaluationGlobal, aCompileOptions,
+ options, &unused, aOffThreadToken);
+}
+
+nsresult
+nsJSUtils::CompileModule(JSContext* aCx,
+ JS::SourceBufferHolder& aSrcBuf,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions &aCompileOptions,
+ JS::MutableHandle<JSObject*> aModule)
+{
+ PROFILER_LABEL("nsJSUtils", "CompileModule",
+ js::ProfileEntry::Category::JS);
+
+ MOZ_ASSERT_IF(aCompileOptions.versionSet,
+ aCompileOptions.version != JSVERSION_UNKNOWN);
+ MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
+ MOZ_ASSERT(aSrcBuf.get());
+ MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) ==
+ aEvaluationGlobal);
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == aEvaluationGlobal);
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(nsContentUtils::IsInMicroTask());
+
+ NS_ENSURE_TRUE(xpc::Scriptability::Get(aEvaluationGlobal).Allowed(), NS_OK);
+
+ if (!JS::CompileModule(aCx, aCompileOptions, aSrcBuf, aModule)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsJSUtils::ModuleDeclarationInstantiation(JSContext* aCx, JS::Handle<JSObject*> aModule)
+{
+ PROFILER_LABEL("nsJSUtils", "ModuleDeclarationInstantiation",
+ js::ProfileEntry::Category::JS);
+
+ MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);
+
+ if (!JS::ModuleDeclarationInstantiation(aCx, aModule)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsJSUtils::ModuleEvaluation(JSContext* aCx, JS::Handle<JSObject*> aModule)
+{
+ PROFILER_LABEL("nsJSUtils", "ModuleEvaluation",
+ js::ProfileEntry::Category::JS);
+
+ MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(nsContentUtils::IsInMicroTask());
+
+ NS_ENSURE_TRUE(xpc::Scriptability::Get(aModule).Allowed(), NS_OK);
+
+ if (!JS::ModuleEvaluation(aCx, aModule)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+/* static */
+bool
+nsJSUtils::GetScopeChainForElement(JSContext* aCx,
+ mozilla::dom::Element* aElement,
+ JS::AutoObjectVector& aScopeChain)
+{
+ for (nsINode* cur = aElement; cur; cur = cur->GetScopeChainParent()) {
+ JS::RootedValue val(aCx);
+ if (!GetOrCreateDOMReflector(aCx, cur, &val)) {
+ return false;
+ }
+
+ if (!aScopeChain.append(&val.toObject())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* static */
+void
+nsJSUtils::ResetTimeZone()
+{
+ JS::ResetTimeZone();
+}
+
+//
+// nsDOMJSUtils.h
+//
+
+bool nsAutoJSString::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;
+}
+
diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h
new file mode 100644
index 000000000..4affab2d3
--- /dev/null
+++ b/dom/base/nsJSUtils.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "jsfriendapi.h"
+#include "js/Conversions.h"
+#include "nsString.h"
+
+class nsIScriptContext;
+class nsIScriptGlobalObject;
+
+namespace mozilla {
+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);
+
+ static nsIScriptGlobalObject *GetStaticScriptGlobal(JSObject* aObj);
+
+ static nsIScriptContext *GetStaticScriptContext(JSObject* aObj);
+
+ /**
+ * 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::AutoObjectVector& aScopeChain,
+ JS::CompileOptions& aOptions,
+ const nsACString& aName,
+ uint32_t aArgCount,
+ const char** aArgArray,
+ const nsAString& aBody,
+ JSObject** aFunctionObject);
+
+ struct MOZ_STACK_CLASS EvaluateOptions {
+ bool coerceToString;
+ JS::AutoObjectVector scopeChain;
+
+ explicit EvaluateOptions(JSContext* cx)
+ : coerceToString(false)
+ , scopeChain(cx)
+ {}
+
+ EvaluateOptions& setCoerceToString(bool aCoerce) {
+ coerceToString = aCoerce;
+ return *this;
+ }
+ };
+
+ // aEvaluationGlobal is the global to evaluate in. The return value
+ // will then be wrapped back into the compartment aCx is in when
+ // this function is called. For all the EvaluateString overloads,
+ // the JSContext must come from an AutoJSAPI that has had
+ // TakeOwnershipOfErrorReporting() called on it.
+ static nsresult EvaluateString(JSContext* aCx,
+ const nsAString& aScript,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions &aCompileOptions,
+ const EvaluateOptions& aEvaluateOptions,
+ JS::MutableHandle<JS::Value> aRetValue);
+
+ static nsresult EvaluateString(JSContext* aCx,
+ JS::SourceBufferHolder& aSrcBuf,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions &aCompileOptions,
+ const EvaluateOptions& aEvaluateOptions,
+ JS::MutableHandle<JS::Value> aRetValue);
+
+
+ static nsresult EvaluateString(JSContext* aCx,
+ const nsAString& aScript,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions &aCompileOptions);
+
+ static nsresult EvaluateString(JSContext* aCx,
+ JS::SourceBufferHolder& aSrcBuf,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions &aCompileOptions,
+ void **aOffThreadToken);
+
+ static nsresult CompileModule(JSContext* aCx,
+ JS::SourceBufferHolder& aSrcBuf,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions &aCompileOptions,
+ JS::MutableHandle<JSObject*> aModule);
+
+ static nsresult ModuleDeclarationInstantiation(JSContext* aCx,
+ JS::Handle<JSObject*> aModule);
+
+ static nsresult ModuleEvaluation(JSContext* aCx,
+ JS::Handle<JSObject*> aModule);
+
+ // 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::AutoObjectVector& aScopeChain);
+
+ static void ResetTimeZone();
+
+private:
+ // Implementation for our EvaluateString bits
+ static nsresult EvaluateString(JSContext* aCx,
+ JS::SourceBufferHolder& aSrcBuf,
+ JS::Handle<JSObject*> aEvaluationGlobal,
+ JS::CompileOptions& aCompileOptions,
+ const EvaluateOptions& aEvaluateOptions,
+ JS::MutableHandle<JS::Value> aRetValue,
+ void **aOffThreadToken);
+};
+
+template<typename T>
+inline bool
+AssignJSString(JSContext *cx, T &dest, JSString *s)
+{
+ size_t len = js::GetStringLength(s);
+ static_assert(js::MaxStringLength < (1 << 28),
+ "Shouldn't overflow here or in SetCapacity");
+ if (MOZ_UNLIKELY(!dest.SetLength(len, mozilla::fallible))) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ return js::CopyStringChars(cx, dest.BeginWriting(), s, len);
+}
+
+inline void
+AssignJSFlatString(nsAString &dest, JSFlatString *s)
+{
+ size_t len = js::GetFlatStringLength(s);
+ static_assert(js::MaxStringLength < (1 << 28),
+ "Shouldn't overflow here or in SetCapacity");
+ dest.SetLength(len);
+ js::CopyFlatStringChars(dest.BeginWriting(), s, len);
+}
+
+class nsAutoJSString : public nsAutoString
+{
+public:
+
+ /**
+ * nsAutoJSString should be default constructed, which leaves it empty
+ * (this->IsEmpty()), and initialized with one of the init() methods below.
+ */
+ nsAutoJSString() {}
+
+ 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);
+
+ ~nsAutoJSString() {}
+};
+
+#endif /* nsJSUtils_h__ */
diff --git a/dom/base/nsLineBreaker.cpp b/dom/base/nsLineBreaker.cpp
new file mode 100644
index 000000000..d68336b71
--- /dev/null
+++ b/dom/base/nsLineBreaker.cpp
@@ -0,0 +1,498 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsILineBreaker.h"
+#include "gfxTextRun.h" // for the gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_* values
+#include "nsHyphenationManager.h"
+#include "nsHyphenator.h"
+#include "mozilla/gfx/2D.h"
+
+nsLineBreaker::nsLineBreaker()
+ : mCurrentWordLanguage(nullptr),
+ mCurrentWordContainsMixedLang(false),
+ mCurrentWordContainsComplexChar(false),
+ mAfterBreakableSpace(false), mBreakHere(false),
+ mWordBreak(nsILineBreaker::kWordBreak_Normal)
+{
+}
+
+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 start
+ // of the word.
+ // The only space character a word can contain is NBSP.
+ bool capitalizeNextChar = true;
+ for (uint32_t i = 0; i < aLength; ++i) {
+ uint32_t ch = aWord[i];
+ if (capitalizeNextChar) {
+ if (NS_IS_HIGH_SURROGATE(ch) && i + 1 < aLength &&
+ NS_IS_LOW_SURROGATE(aWord[i + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, aWord[i + 1]);
+ }
+ if (nsContentUtils::IsAlphanumeric(ch)) {
+ aCapitalization[i] = true;
+ capitalizeNextChar = false;
+ }
+ if (!IS_IN_BMP(ch)) {
+ ++i;
+ }
+ }
+ if (ch == 0xA0 /*NBSP*/) {
+ capitalizeNextChar = true;
+ }
+ }
+}
+
+nsresult
+nsLineBreaker::FlushCurrentWord()
+{
+ uint32_t length = mCurrentWord.Length();
+ AutoTArray<uint8_t,4000> breakState;
+ if (!breakState.AppendElements(length))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsTArray<bool> capitalizationState;
+
+ if (!mCurrentWordContainsComplexChar) {
+ // For break-strict set everything internal to "break", otherwise
+ // to "no break"!
+ memset(breakState.Elements(),
+ mWordBreak == nsILineBreaker::kWordBreak_BreakAll ?
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL :
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE,
+ length*sizeof(uint8_t));
+ } else {
+ nsContentUtils::LineBreaker()->
+ GetJISx4051Breaks(mCurrentWord.Elements(), length, mWordBreak,
+ 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());
+ }
+ }
+
+ 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 (ti->mFlags & BREAK_NEED_CAPITALIZATION) {
+ if (capitalizationState.Length() == 0) {
+ if (!capitalizationState.AppendElements(length))
+ 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;
+ 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(nsIAtom* 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))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ bool noCapitalizationNeeded = true;
+ nsTArray<bool> capitalizationState;
+ if (aSink && (aFlags & BREAK_NEED_CAPITALIZATION)) {
+ if (!capitalizationState.AppendElements(aLength))
+ 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 == nsILineBreaker::kWordBreak_BreakAll) ?
+ 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 (wordHasComplexChar) {
+ // Save current start-of-word state because GetJISx4051Breaks will
+ // set it to false
+ uint8_t currentStart = breakState[wordStart];
+ nsContentUtils::LineBreaker()->
+ GetJISx4051Breaks(aText + wordStart, offset - wordStart,
+ mWordBreak,
+ breakState.Elements() + wordStart);
+ breakState[wordStart] = currentStart;
+ }
+ if (hyphenator) {
+ FindHyphenationPoints(hyphenator,
+ aText + wordStart, aText + offset,
+ breakState.Elements() + wordStart);
+ }
+ }
+ if (!noCapitalizationNeeded) {
+ SetupCapitalization(aText + wordStart, offset - wordStart,
+ capitalizationState.Elements() + wordStart);
+ }
+ }
+ wordHasComplexChar = 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(nsIAtom* 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))
+ 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 == nsILineBreaker::kWordBreak_BreakAll) ?
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL :
+ gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
+ }
+ mBreakHere = false;
+ mAfterBreakableSpace = isBreakableSpace;
+
+ if (isSpace) {
+ if (offset > wordStart && wordHasComplexChar) {
+ if (aSink && !(aFlags & BREAK_SUPPRESS_INSIDE)) {
+ // Save current start-of-word state because GetJISx4051Breaks will
+ // set it to false
+ uint8_t currentStart = breakState[wordStart];
+ nsContentUtils::LineBreaker()->
+ GetJISx4051Breaks(aText + wordStart, offset - wordStart,
+ mWordBreak,
+ breakState.Elements() + wordStart);
+ breakState[wordStart] = currentStart;
+ }
+ 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(nsIAtom *aHyphenationLanguage)
+{
+ if (mCurrentWordLanguage && mCurrentWordLanguage != aHyphenationLanguage) {
+ mCurrentWordContainsMixedLang = true;
+ } else {
+ 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;
+ 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 000000000..81c4c334e
--- /dev/null
+++ b/dom/base/nsLineBreaker.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/. */
+
+#ifndef NSLINEBREAKER_H_
+#define NSLINEBREAKER_H_
+
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsILineBreaker.h"
+
+class nsIAtom;
+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 nsILineBreaker
+ * 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 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) ||
+ 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 nsILineBreaker. We pass a whitespace-
+ // delimited word to nsILineBreaker 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(nsIAtom* 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(nsIAtom* 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 linebreaker. This is set by word-break property.
+ * @param aMode is nsILineBreaker::kWordBreak_* value.
+ */
+ void SetWordBreak(uint8_t aMode) { mWordBreak = aMode; }
+
+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(nsIAtom *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;
+ nsIAtom* mCurrentWordLanguage;
+ bool mCurrentWordContainsMixedLang;
+ bool mCurrentWordContainsComplexChar;
+
+ // 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;
+ // line break mode by "word-break" style
+ uint8_t mWordBreak;
+};
+
+#endif /*NSLINEBREAKER_H_*/
diff --git a/dom/base/nsMappedAttributeElement.cpp b/dom/base/nsMappedAttributeElement.cpp
new file mode 100644
index 000000000..1c1f8838f
--- /dev/null
+++ b/dom/base/nsMappedAttributeElement.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 "nsMappedAttributeElement.h"
+#include "nsIDocument.h"
+
+nsresult
+nsMappedAttributeElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
+{
+ mAttrsAndChildren.WalkMappedAttributeStyleRules(aRuleWalker);
+ return NS_OK;
+}
+
+bool
+nsMappedAttributeElement::SetMappedAttribute(nsIDocument* aDocument,
+ nsIAtom* aName,
+ nsAttrValue& aValue,
+ nsresult* aRetval)
+{
+ NS_PRECONDITION(aDocument == GetComposedDoc(), "Unexpected document");
+ nsHTMLStyleSheet* sheet = aDocument ?
+ aDocument->GetAttributeStyleSheet() : nullptr;
+
+ *aRetval = mAttrsAndChildren.SetAndTakeMappedAttr(aName, aValue,
+ this, sheet);
+ return true;
+}
+
+nsMapRuleToAttributesFunc
+nsMappedAttributeElement::GetAttributeMappingFunction() const
+{
+ return &MapNoAttributesInto;
+}
+
+void
+nsMappedAttributeElement::MapNoAttributesInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+}
diff --git a/dom/base/nsMappedAttributeElement.h b/dom/base/nsMappedAttributeElement.h
new file mode 100644
index 000000000..4668b36a1
--- /dev/null
+++ b/dom/base/nsMappedAttributeElement.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/. */
+
+/**
+ * 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"
+
+class nsMappedAttributes;
+struct nsRuleData;
+
+typedef void (*nsMapRuleToAttributesFunc)(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData);
+
+typedef nsStyledElement nsMappedAttributeElementBase;
+
+class nsMappedAttributeElement : public nsMappedAttributeElementBase
+{
+
+protected:
+
+ explicit nsMappedAttributeElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsMappedAttributeElementBase(aNodeInfo)
+ {}
+
+public:
+ virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
+
+ static void MapNoAttributesInto(const nsMappedAttributes* aAttributes,
+ nsRuleData* aRuleData);
+
+ NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) override;
+ virtual bool SetMappedAttribute(nsIDocument* aDocument,
+ nsIAtom* aName,
+ nsAttrValue& aValue,
+ nsresult* aRetval) override;
+};
+
+#endif // NS_MAPPEDATTRIBUTEELEMENT_H_
diff --git a/dom/base/nsMappedAttributes.cpp b/dom/base/nsMappedAttributes.cpp
new file mode 100644
index 000000000..a3accd2a7
--- /dev/null
+++ b/dom/base/nsMappedAttributes.cpp
@@ -0,0 +1,283 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsHTMLStyleSheet.h"
+#include "nsRuleWalker.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MemoryReporting.h"
+
+using namespace mozilla;
+
+nsMappedAttributes::nsMappedAttributes(nsHTMLStyleSheet* aSheet,
+ nsMapRuleToAttributesFunc aMapRuleFunc)
+ : mAttrCount(0),
+ mSheet(aSheet),
+ mRuleMapper(aMapRuleFunc)
+{
+}
+
+nsMappedAttributes::nsMappedAttributes(const nsMappedAttributes& aCopy)
+ : mAttrCount(aCopy.mAttrCount),
+ mSheet(aCopy.mSheet),
+ mRuleMapper(aCopy.mRuleMapper)
+{
+ NS_ASSERTION(mBufferSize >= aCopy.mAttrCount, "can't fit attributes");
+
+ uint32_t i;
+ for (i = 0; i < mAttrCount; ++i) {
+ new (&Attrs()[i]) InternalAttr(aCopy.Attrs()[i]);
+ }
+}
+
+nsMappedAttributes::~nsMappedAttributes()
+{
+ if (mSheet) {
+ mSheet->DropMappedAttributes(this);
+ }
+
+ uint32_t i;
+ for (i = 0; i < mAttrCount; ++i) {
+ Attrs()[i].~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) CPP_THROW_NEW
+{
+ NS_ASSERTION(aAttrCount > 0, "zero-attribute nsMappedAttributes requested");
+
+ // aSize will include the mAttrs buffer so subtract that.
+ void* newAttrs = ::operator new(aSize - sizeof(void*[1]) +
+ aAttrCount * sizeof(InternalAttr));
+
+#ifdef DEBUG
+ static_cast<nsMappedAttributes*>(newAttrs)->mBufferSize = aAttrCount;
+#endif
+
+ return newAttrs;
+}
+
+NS_IMPL_ISUPPORTS(nsMappedAttributes,
+ nsIStyleRule)
+
+void
+nsMappedAttributes::SetAndTakeAttr(nsIAtom* aAttrName, nsAttrValue& aValue)
+{
+ NS_PRECONDITION(aAttrName, "null name");
+
+ uint32_t i;
+ for (i = 0; i < mAttrCount && !Attrs()[i].mName.IsSmaller(aAttrName); ++i) {
+ if (Attrs()[i].mName.Equals(aAttrName)) {
+ Attrs()[i].mValue.Reset();
+ Attrs()[i].mValue.SwapValueWith(aValue);
+ return;
+ }
+ }
+
+ NS_ASSERTION(mBufferSize >= mAttrCount + 1, "can't fit attributes");
+
+ if (mAttrCount != i) {
+ memmove(&Attrs()[i + 1], &Attrs()[i], (mAttrCount - i) * sizeof(InternalAttr));
+ }
+
+ new (&Attrs()[i].mName) nsAttrName(aAttrName);
+ new (&Attrs()[i].mValue) nsAttrValue();
+ Attrs()[i].mValue.SwapValueWith(aValue);
+ ++mAttrCount;
+}
+
+const nsAttrValue*
+nsMappedAttributes::GetAttr(nsIAtom* aAttrName) const
+{
+ NS_PRECONDITION(aAttrName, "null name");
+
+ for (uint32_t i = 0; i < mAttrCount; ++i) {
+ if (Attrs()[i].mName.Equals(aAttrName)) {
+ return &Attrs()[i].mValue;
+ }
+ }
+
+ return nullptr;
+}
+
+const nsAttrValue*
+nsMappedAttributes::GetAttr(const nsAString& aAttrName) const
+{
+ for (uint32_t i = 0; i < mAttrCount; ++i) {
+ if (Attrs()[i].mName.Atom()->Equals(aAttrName)) {
+ return &Attrs()[i].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 (!Attrs()[i].mName.Equals(aOther->Attrs()[i].mName) ||
+ !Attrs()[i].mValue.Equals(aOther->Attrs()[i].mValue)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+uint32_t
+nsMappedAttributes::HashValue() const
+{
+ uint32_t hash = HashGeneric(mRuleMapper);
+
+ uint32_t i;
+ for (i = 0; i < mAttrCount; ++i) {
+ hash = AddToHash(hash,
+ Attrs()[i].mName.HashValue(),
+ Attrs()[i].mValue.HashValue());
+ }
+
+ return hash;
+}
+
+void
+nsMappedAttributes::SetStyleSheet(nsHTMLStyleSheet* aSheet)
+{
+ if (mSheet) {
+ mSheet->DropMappedAttributes(this);
+ }
+ mSheet = aSheet; // not ref counted
+}
+
+/* virtual */ void
+nsMappedAttributes::MapRuleInfoInto(nsRuleData* aRuleData)
+{
+ if (mRuleMapper) {
+ (*mRuleMapper)(this, aRuleData);
+ }
+}
+
+/* virtual */ bool
+nsMappedAttributes::MightMapInheritedStyleData()
+{
+ // Just assume that we do, rather than adding checks to all of the different
+ // kinds of attribute mapping functions we have.
+ return true;
+}
+
+/* virtual */ bool
+nsMappedAttributes::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue)
+{
+ MOZ_ASSERT(false, "GetDiscretelyAnimatedCSSValue is not implemented yet");
+ return false;
+}
+
+#ifdef DEBUG
+/* virtual */ void
+nsMappedAttributes::List(FILE* out, int32_t aIndent) const
+{
+ nsAutoCString str;
+ nsAutoString tmp;
+ uint32_t i;
+
+ for (i = 0; i < mAttrCount; ++i) {
+ int32_t indent;
+ for (indent = aIndent; indent > 0; --indent) {
+ str.AppendLiteral(" ");
+ }
+
+ Attrs()[i].mName.GetQualifiedName(tmp);
+ LossyAppendUTF16toASCII(tmp, str);
+
+ Attrs()[i].mValue.ToString(tmp);
+ LossyAppendUTF16toASCII(tmp, str);
+ str.Append('\n');
+ fprintf_stderr(out, "%s", str.get());
+ }
+}
+#endif
+
+void
+nsMappedAttributes::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue)
+{
+ Attrs()[aPos].mValue.SwapValueWith(aValue);
+ Attrs()[aPos].~InternalAttr();
+ memmove(&Attrs()[aPos], &Attrs()[aPos + 1],
+ (mAttrCount - aPos - 1) * sizeof(InternalAttr));
+ mAttrCount--;
+}
+
+const nsAttrName*
+nsMappedAttributes::GetExistingAttrNameFromQName(const nsAString& aName) const
+{
+ uint32_t i;
+ for (i = 0; i < mAttrCount; ++i) {
+ if (Attrs()[i].mName.IsAtom()) {
+ if (Attrs()[i].mName.Atom()->Equals(aName)) {
+ return &Attrs()[i].mName;
+ }
+ }
+ else {
+ if (Attrs()[i].mName.NodeInfo()->QualifiedNameEquals(aName)) {
+ return &Attrs()[i].mName;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+int32_t
+nsMappedAttributes::IndexOfAttr(nsIAtom* aLocalName) const
+{
+ uint32_t i;
+ for (i = 0; i < mAttrCount; ++i) {
+ if (Attrs()[i].mName.Equals(aLocalName)) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+size_t
+nsMappedAttributes::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ NS_ASSERTION(mAttrCount == mBufferSize,
+ "mBufferSize and mAttrCount are expected to be the same.");
+
+ size_t n = aMallocSizeOf(this);
+ for (uint16_t i = 0; i < mAttrCount; ++i) {
+ n += Attrs()[i].mValue.SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return n;
+}
+
diff --git a/dom/base/nsMappedAttributes.h b/dom/base/nsMappedAttributes.h
new file mode 100644
index 000000000..9fa7572dd
--- /dev/null
+++ b/dom/base/nsMappedAttributes.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsAttrAndChildArray.h"
+#include "nsMappedAttributeElement.h"
+#include "nsIStyleRule.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+
+class nsIAtom;
+class nsHTMLStyleSheet;
+
+class nsMappedAttributes final : public nsIStyleRule
+{
+public:
+ nsMappedAttributes(nsHTMLStyleSheet* aSheet,
+ nsMapRuleToAttributesFunc aMapRuleFunc);
+
+ // Do not return null.
+ void* operator new(size_t size, uint32_t aAttrCount = 1) CPP_THROW_NEW;
+ nsMappedAttributes* Clone(bool aWillAddAttr);
+
+ NS_DECL_ISUPPORTS
+
+ void SetAndTakeAttr(nsIAtom* aAttrName, nsAttrValue& aValue);
+ const nsAttrValue* GetAttr(nsIAtom* aAttrName) const;
+ const nsAttrValue* GetAttr(const nsAString& aAttrName) const;
+
+ uint32_t Count() const
+ {
+ return mAttrCount;
+ }
+
+ bool Equals(const nsMappedAttributes* aAttributes) const;
+ uint32_t HashValue() const;
+
+ void DropStyleSheetReference()
+ {
+ mSheet = nullptr;
+ }
+ void SetStyleSheet(nsHTMLStyleSheet* aSheet);
+ nsHTMLStyleSheet* GetStyleSheet()
+ {
+ return mSheet;
+ }
+
+ 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(nsIAtom* aLocalName) const;
+
+
+ // nsIStyleRule
+ virtual void MapRuleInfoInto(nsRuleData* aRuleData) override;
+ virtual bool MightMapInheritedStyleData() override;
+ virtual bool GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
+ nsCSSValue* aValue) override;
+#ifdef DEBUG
+ virtual void List(FILE* out = stdout, int32_t aIndent = 0) const override;
+#endif
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ nsMappedAttributes(const nsMappedAttributes& aCopy);
+ ~nsMappedAttributes();
+
+ struct InternalAttr
+ {
+ nsAttrName mName;
+ nsAttrValue mValue;
+ };
+
+ /**
+ * Due to a compiler bug in VisualAge C++ for AIX, we need to return the
+ * address of the first index into mAttrs here, instead of simply
+ * returning mAttrs itself.
+ *
+ * See Bug 231104 for more information.
+ */
+ const InternalAttr* Attrs() const
+ {
+ return reinterpret_cast<const InternalAttr*>(&(mAttrs[0]));
+ }
+ InternalAttr* Attrs()
+ {
+ return reinterpret_cast<InternalAttr*>(&(mAttrs[0]));
+ }
+
+ uint16_t mAttrCount;
+#ifdef DEBUG
+ uint16_t mBufferSize;
+#endif
+ nsHTMLStyleSheet* mSheet; //weak
+ nsMapRuleToAttributesFunc mRuleMapper;
+ void* mAttrs[1];
+};
+
+#endif /* nsMappedAttributes_h___ */
diff --git a/dom/base/nsMimeTypeArray.cpp b/dom/base/nsMimeTypeArray.cpp
new file mode 100644
index 000000000..ec8a82557
--- /dev/null
+++ b/dom/base/nsMimeTypeArray.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 "nsMimeTypeArray.h"
+
+#include "mozilla/dom/MimeTypeArrayBinding.h"
+#include "mozilla/dom/MimeTypeBinding.h"
+#include "nsIDOMNavigator.h"
+#include "nsPIDOMWindow.h"
+#include "nsPluginArray.h"
+#include "nsIMIMEService.h"
+#include "nsIMIMEInfo.h"
+#include "Navigator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsPluginTags.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,
+ mCTPMimeTypes)
+
+nsMimeTypeArray::nsMimeTypeArray(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+{
+}
+
+nsMimeTypeArray::~nsMimeTypeArray()
+{
+}
+
+static bool
+ResistFingerprinting() {
+ return !nsContentUtils::ThreadsafeIsCallerChrome() &&
+ nsContentUtils::ResistFingerprinting();
+}
+
+JSObject*
+nsMimeTypeArray::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MimeTypeArrayBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+nsMimeTypeArray::Refresh()
+{
+ mMimeTypes.Clear();
+ mCTPMimeTypes.Clear();
+}
+
+nsPIDOMWindowInner*
+nsMimeTypeArray::GetParentObject() const
+{
+ MOZ_ASSERT(mWindow);
+ return mWindow;
+}
+
+nsMimeType*
+nsMimeTypeArray::Item(uint32_t aIndex)
+{
+ bool unused;
+ return IndexedGetter(aIndex, unused);
+}
+
+nsMimeType*
+nsMimeTypeArray::NamedItem(const nsAString& aName)
+{
+ bool unused;
+ return NamedGetter(aName, unused);
+}
+
+nsMimeType*
+nsMimeTypeArray::IndexedGetter(uint32_t aIndex, bool &aFound)
+{
+ aFound = false;
+
+ if (ResistFingerprinting()) {
+ return nullptr;
+ }
+
+ EnsurePluginMimeTypes();
+
+ if (aIndex >= mMimeTypes.Length()) {
+ return nullptr;
+ }
+
+ aFound = true;
+
+ return mMimeTypes[aIndex];
+}
+
+static nsMimeType*
+FindMimeType(const nsTArray<RefPtr<nsMimeType>>& aMimeTypes,
+ const nsAString& aType)
+{
+ for (uint32_t i = 0; i < aMimeTypes.Length(); ++i) {
+ nsMimeType* mimeType = aMimeTypes[i];
+ if (aType.Equals(mimeType->Type())) {
+ return mimeType;
+ }
+ }
+
+ return nullptr;
+}
+
+nsMimeType*
+nsMimeTypeArray::NamedGetter(const nsAString& aName, bool &aFound)
+{
+ aFound = false;
+
+ if (ResistFingerprinting()) {
+ return nullptr;
+ }
+
+ EnsurePluginMimeTypes();
+
+ nsString lowerName(aName);
+ ToLowerCase(lowerName);
+
+ nsMimeType* mimeType = FindMimeType(mMimeTypes, lowerName);
+ if (mimeType) {
+ aFound = true;
+ return mimeType;
+ }
+ nsMimeType* hiddenType = FindMimeType(mCTPMimeTypes, lowerName);
+ if (hiddenType) {
+ nsPluginArray::NotifyHiddenPluginTouched(hiddenType->GetEnabledPlugin());
+ }
+
+ return nullptr;
+}
+
+uint32_t
+nsMimeTypeArray::Length()
+{
+ if (ResistFingerprinting()) {
+ return 0;
+ }
+
+ EnsurePluginMimeTypes();
+
+ return mMimeTypes.Length();
+}
+
+void
+nsMimeTypeArray::GetSupportedNames(nsTArray<nsString>& aRetval)
+{
+ EnsurePluginMimeTypes();
+
+ for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
+ aRetval.AppendElement(mMimeTypes[i]->Type());
+ }
+}
+
+void
+nsMimeTypeArray::EnsurePluginMimeTypes()
+{
+ if (!mMimeTypes.IsEmpty() || !mWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
+
+ if (!navigator) {
+ return;
+ }
+
+ ErrorResult rv;
+ nsPluginArray *pluginArray =
+ static_cast<Navigator*>(navigator.get())->GetPlugins(rv);
+ if (!pluginArray) {
+ return;
+ }
+
+ pluginArray->GetMimeTypes(mMimeTypes);
+ pluginArray->GetCTPMimeTypes(mCTPMimeTypes);
+}
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsMimeType, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsMimeType, Release)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeType, mWindow, mPluginElement)
+
+nsMimeType::nsMimeType(nsPIDOMWindowInner* aWindow,
+ nsPluginElement* aPluginElement,
+ const nsAString& aType,
+ const nsAString& aDescription,
+ const nsAString& aExtension)
+ : mWindow(aWindow),
+ mPluginElement(aPluginElement),
+ mType(aType),
+ mDescription(aDescription),
+ mExtension(aExtension)
+{
+ MOZ_ASSERT(aPluginElement);
+}
+
+nsMimeType::~nsMimeType()
+{
+}
+
+nsPIDOMWindowInner*
+nsMimeType::GetParentObject() const
+{
+ MOZ_ASSERT(mWindow);
+ return mWindow;
+}
+
+JSObject*
+nsMimeType::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MimeTypeBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+nsMimeType::GetDescription(nsString& aRetval) const
+{
+ aRetval = mDescription;
+}
+
+nsPluginElement*
+nsMimeType::GetEnabledPlugin() const
+{
+ // mPluginElement might be null if we got unlinked but are still somehow being
+ // called into.
+ if (!mPluginElement || !mPluginElement->PluginTag()->IsEnabled()) {
+ return nullptr;
+ }
+ return mPluginElement;
+}
+
+void
+nsMimeType::GetSuffixes(nsString& aRetval) const
+{
+ aRetval = mExtension;
+}
+
+void
+nsMimeType::GetType(nsString& aRetval) const
+{
+ aRetval = mType;
+}
diff --git a/dom/base/nsMimeTypeArray.h b/dom/base/nsMimeTypeArray.h
new file mode 100644
index 000000000..856136414
--- /dev/null
+++ b/dom/base/nsMimeTypeArray.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 nsMimeTypeArray_h___
+#define nsMimeTypeArray_h___
+
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "nsPIDOMWindow.h"
+
+class nsMimeType;
+class nsPluginElement;
+
+class nsMimeTypeArray final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ explicit nsMimeTypeArray(nsPIDOMWindowInner* aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsMimeTypeArray)
+
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void Refresh();
+
+ // MimeTypeArray WebIDL methods
+ nsMimeType* Item(uint32_t index);
+ nsMimeType* NamedItem(const nsAString& name);
+ nsMimeType* IndexedGetter(uint32_t index, bool &found);
+ nsMimeType* NamedGetter(const nsAString& name, bool &found);
+ uint32_t Length();
+ void GetSupportedNames(nsTArray<nsString>& retval);
+
+protected:
+ virtual ~nsMimeTypeArray();
+
+ void EnsurePluginMimeTypes();
+ void Clear();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ // mMimeTypes contains MIME types handled by plugins or by an OS
+ // PreferredApplicationHandler.
+ nsTArray<RefPtr<nsMimeType> > mMimeTypes;
+ nsTArray<RefPtr<nsMimeType> > mCTPMimeTypes;
+};
+
+class nsMimeType final : public nsWrapperCache
+{
+public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsMimeType)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsMimeType)
+
+ nsMimeType(nsPIDOMWindowInner* aWindow,
+ nsPluginElement* aPluginElement,
+ const nsAString& aType,
+ const nsAString& aDescription,
+ const nsAString& aExtension);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ const nsString& Type() const
+ {
+ return mType;
+ }
+
+ // MimeType WebIDL methods
+ void GetDescription(nsString& retval) const;
+ nsPluginElement *GetEnabledPlugin() const;
+ void GetSuffixes(nsString& retval) const;
+ void GetType(nsString& retval) const;
+
+protected:
+ virtual ~nsMimeType();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ // Strong reference to the active plugin. Note that this creates an explicit
+ // reference cycle through the plugin element's mimetype array. We rely on the
+ // cycle collector to break this cycle.
+ RefPtr<nsPluginElement> mPluginElement;
+ nsString mType;
+ nsString mDescription;
+ nsString mExtension;
+};
+
+#endif /* nsMimeTypeArray_h___ */
diff --git a/dom/base/nsNameSpaceManager.cpp b/dom/base/nsNameSpaceManager.cpp
new file mode 100644
index 000000000..0130bb5d2
--- /dev/null
+++ b/dom/base/nsNameSpaceManager.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsCOMArray.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsString.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/XBLChildrenElement.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static const char* kPrefMathMLDisabled = "mathml.disabled";
+static const char* kObservedPrefs[] = {
+ kPrefMathMLDisabled,
+ 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::AddStrongObservers(this, kObservedPrefs);
+ mMathMLDisabled = mozilla::Preferences::GetBool(kPrefMathMLDisabled);
+
+
+ // 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_xbl, kNameSpaceID_XBL);
+ 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);
+
+#undef REGISTER_NAMESPACE
+#undef REGISTER_DISABLED_NAMESPACE
+
+ return true;
+}
+
+nsresult
+nsNameSpaceManager::RegisterNameSpace(const nsAString& aURI,
+ int32_t& aNameSpaceID)
+{
+ if (aURI.IsEmpty()) {
+ aNameSpaceID = kNameSpaceID_None; // xmlns="", see bug 75700 for details
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(aURI);
+ nsresult rv = NS_OK;
+ if (!mURIToIDTable.Get(atom, &aNameSpaceID)) {
+ aNameSpaceID = mURIArray.Length();
+
+ rv = AddNameSpace(atom.forget(), aNameSpaceID);
+ if (NS_FAILED(rv)) {
+ aNameSpaceID = kNameSpaceID_Unknown;
+ }
+ }
+
+ NS_POSTCONDITION(aNameSpaceID >= -1, "Bogus namespace ID");
+
+ return rv;
+}
+
+nsresult
+nsNameSpaceManager::GetNameSpaceURI(int32_t aNameSpaceID, nsAString& aURI)
+{
+ NS_PRECONDITION(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
+ }
+
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(aURI);
+ return GetNameSpaceID(atom, aInChromeDoc);
+}
+
+int32_t
+nsNameSpaceManager::GetNameSpaceID(nsIAtom* aURI,
+ bool aInChromeDoc)
+{
+ if (aURI == nsGkAtoms::_empty) {
+ return kNameSpaceID_None; // xmlns="", see bug 75700 for details
+ }
+
+ int32_t nameSpaceID;
+ if (mMathMLDisabled &&
+ mDisabledURIToIDTable.Get(aURI, &nameSpaceID) &&
+ !aInChromeDoc) {
+ NS_POSTCONDITION(nameSpaceID >= 0, "Bogus namespace ID");
+ return nameSpaceID;
+ }
+ if (mURIToIDTable.Get(aURI, &nameSpaceID)) {
+ NS_POSTCONDITION(nameSpaceID >= 0, "Bogus namespace ID");
+ return nameSpaceID;
+ }
+
+ return kNameSpaceID_Unknown;
+}
+
+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();
+ if (ns == kNameSpaceID_XHTML) {
+ return NS_NewHTMLElement(aResult, ni.forget(), aFromParser, aIs);
+ }
+#ifdef MOZ_XUL
+ if (ns == kNameSpaceID_XUL) {
+ return NS_NewXULElement(aResult, ni.forget());
+ }
+#endif
+ if (ns == kNameSpaceID_MathML) {
+ // If the mathml.disabled pref. is true, convert all MathML nodes into
+ // disabled MathML nodes by swapping the namespace.
+ nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
+ if ((nsmgr && !nsmgr->mMathMLDisabled) ||
+ nsContentUtils::IsChromeDoc(ni->GetDocument())) {
+ 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) {
+ return NS_NewSVGElement(aResult, ni.forget(), aFromParser);
+ }
+ if (ns == kNameSpaceID_XBL && ni->Equals(nsGkAtoms::children)) {
+ NS_ADDREF(*aResult = new XBLChildrenElement(ni.forget()));
+ return NS_OK;
+ }
+
+ return NS_NewXMLElement(aResult, ni.forget());
+}
+
+bool
+nsNameSpaceManager::HasElementCreator(int32_t aNameSpaceID)
+{
+ return aNameSpaceID == kNameSpaceID_XHTML ||
+#ifdef MOZ_XUL
+ aNameSpaceID == kNameSpaceID_XUL ||
+#endif
+ aNameSpaceID == kNameSpaceID_MathML ||
+ aNameSpaceID == kNameSpaceID_SVG ||
+ false;
+}
+
+nsresult nsNameSpaceManager::AddNameSpace(already_AddRefed<nsIAtom> aURI,
+ const int32_t aNameSpaceID)
+{
+ nsCOMPtr<nsIAtom> 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.Put(mURIArray.LastElement(), aNameSpaceID);
+
+ return NS_OK;
+}
+
+nsresult
+nsNameSpaceManager::AddDisabledNameSpace(already_AddRefed<nsIAtom> aURI,
+ const int32_t aNameSpaceID)
+{
+ nsCOMPtr<nsIAtom> 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.Put(mURIArray.LastElement(), aNameSpaceID);
+
+ return NS_OK;
+}
+
+// nsISupports
+NS_IMPL_ISUPPORTS(nsNameSpaceManager,
+ nsIObserver)
+
+// nsIObserver
+NS_IMETHODIMP
+nsNameSpaceManager::Observe(nsISupports* aObject, const char* aTopic,
+ const char16_t* aMessage)
+{
+ mMathMLDisabled = mozilla::Preferences::GetBool(kPrefMathMLDisabled);
+ return NS_OK;
+}
diff --git a/dom/base/nsNameSpaceManager.h b/dom/base/nsNameSpaceManager.h
new file mode 100644
index 000000000..d5c3a25fe
--- /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 "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIAtom.h"
+#include "nsIDocument.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+
+#include "mozilla/StaticPtr.h"
+
+class nsAString;
+
+/**
+ * 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 nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ virtual nsresult RegisterNameSpace(const nsAString& aURI,
+ int32_t& aNameSpaceID);
+
+ virtual 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);
+ nsIAtom* NameSpaceURIAtom(int32_t aNameSpaceID) {
+ MOZ_ASSERT(aNameSpaceID > 0);
+ return NameSpaceURIAtomForServo(aNameSpaceID);
+ }
+
+ // NB: This function should only be called by Servo code (and the above
+ // accessor), which uses the empty atom to represent kNameSpaceID_None.
+ nsIAtom* NameSpaceURIAtomForServo(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(nsIAtom* aURI,
+ bool aInChromeDoc);
+
+ bool HasElementCreator(int32_t aNameSpaceID);
+
+ static nsNameSpaceManager* GetInstance();
+ bool mMathMLDisabled;
+
+private:
+ bool Init();
+ nsresult AddNameSpace(already_AddRefed<nsIAtom> aURI, const int32_t aNameSpaceID);
+ nsresult AddDisabledNameSpace(already_AddRefed<nsIAtom> aURI, const int32_t aNameSpaceID);
+ ~nsNameSpaceManager() {};
+
+ nsDataHashtable<nsISupportsHashKey, int32_t> mURIToIDTable;
+ nsDataHashtable<nsISupportsHashKey, int32_t> mDisabledURIToIDTable;
+ nsTArray<nsCOMPtr<nsIAtom>> 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 000000000..cd1484a22
--- /dev/null
+++ b/dom/base/nsNoDataProtocolContentPolicy.cpp
@@ -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/. */
+
+/*
+ * 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 "nsIDOMWindow.h"
+#include "nsString.h"
+#include "nsIProtocolHandler.h"
+#include "nsIIOService.h"
+#include "nsIExternalProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+
+NS_IMPL_ISUPPORTS(nsNoDataProtocolContentPolicy, nsIContentPolicy)
+
+NS_IMETHODIMP
+nsNoDataProtocolContentPolicy::ShouldLoad(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestingLocation,
+ nsISupports *aRequestingContext,
+ const nsACString &aMimeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+ "We should only see external content policy types here.");
+
+ *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 (aContentType != TYPE_DOCUMENT &&
+ aContentType != TYPE_SUBDOCUMENT &&
+ aContentType != TYPE_OBJECT &&
+ aContentType != 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;
+ }
+
+ bool shouldBlock;
+ nsresult rv = NS_URIChainHasFlags(aContentLocation,
+ nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
+ &shouldBlock);
+ if (NS_SUCCEEDED(rv) && shouldBlock) {
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNoDataProtocolContentPolicy::ShouldProcess(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestingLocation,
+ nsISupports *aRequestingContext,
+ const nsACString &aMimeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ return ShouldLoad(aContentType, aContentLocation, aRequestingLocation,
+ aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal,
+ aDecision);
+}
diff --git a/dom/base/nsNoDataProtocolContentPolicy.h b/dom/base/nsNoDataProtocolContentPolicy.h
new file mode 100644
index 000000000..768a686fd
--- /dev/null
+++ b/dom/base/nsNoDataProtocolContentPolicy.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/. */
+
+/*
+ * 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()
+ {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+
+ nsNoDataProtocolContentPolicy()
+ {}
+};
+
+
+#endif /* nsNoDataProtocolContentPolicy_h__ */
diff --git a/dom/base/nsNodeInfoManager.cpp b/dom/base/nsNodeInfoManager.cpp
new file mode 100644
index 000000000..66c8c84cf
--- /dev/null
+++ b/dom/base/nsNodeInfoManager.cpp
@@ -0,0 +1,433 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/DebugOnly.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/NodeInfoInlines.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIAtom.h"
+#include "nsIDocument.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "nsGkAtoms.h"
+#include "nsComponentManagerUtils.h"
+#include "nsLayoutStatics.h"
+#include "nsBindingManager.h"
+#include "nsHashKeys.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsNameSpaceManager.h"
+#include "nsDocument.h"
+#include "nsNullPrincipal.h"
+
+using namespace mozilla;
+using mozilla::dom::NodeInfo;
+
+#include "mozilla/Logging.h"
+
+static LazyLogModule gNodeInfoManagerLeakPRLog("NodeInfoManagerLeak");
+
+PLHashNumber
+nsNodeInfoManager::GetNodeInfoInnerHashValue(const void *key)
+{
+ MOZ_ASSERT(key, "Null key passed to NodeInfo::GetHashValue!");
+
+ auto *node = reinterpret_cast<const NodeInfo::NodeInfoInner*>(key);
+
+ return node->mName ? node->mName->hash() : HashString(*(node->mNameString));
+}
+
+
+int
+nsNodeInfoManager::NodeInfoInnerKeyCompare(const void *key1, const void *key2)
+{
+ MOZ_ASSERT(key1 && key2, "Null key passed to NodeInfoInnerKeyCompare!");
+
+ auto *node1 = reinterpret_cast<const NodeInfo::NodeInfoInner*>(key1);
+ auto *node2 = reinterpret_cast<const NodeInfo::NodeInfoInner*>(key2);
+
+ if (node1->mPrefix != node2->mPrefix ||
+ node1->mNamespaceID != node2->mNamespaceID ||
+ node1->mNodeType != node2->mNodeType ||
+ node1->mExtraName != node2->mExtraName) {
+ return 0;
+ }
+
+ if (node1->mName) {
+ if (node2->mName) {
+ return (node1->mName == node2->mName);
+ }
+ return (node1->mName->Equals(*(node2->mNameString)));
+ }
+ if (node2->mName) {
+ return (node2->mName->Equals(*(node1->mNameString)));
+ }
+ return (node1->mNameString->Equals(*(node2->mNameString)));
+}
+
+
+static void* PR_CALLBACK
+AllocTable(void* pool, size_t size)
+{
+ return malloc(size);
+}
+
+static void PR_CALLBACK
+FreeTable(void* pool, void* item)
+{
+ free(item);
+}
+
+static PLHashEntry* PR_CALLBACK
+AllocEntry(void* pool, const void* key)
+{
+ return (PLHashEntry*)malloc(sizeof(PLHashEntry));
+}
+
+static void PR_CALLBACK
+FreeEntry(void* pool, PLHashEntry* he, unsigned flag)
+{
+ if (flag == HT_FREE_ENTRY) {
+ free(he);
+ }
+}
+
+static PLHashAllocOps allocOps =
+ { AllocTable, FreeTable, AllocEntry, FreeEntry };
+
+nsNodeInfoManager::nsNodeInfoManager()
+ : mDocument(nullptr),
+ mNonDocumentNodeInfos(0),
+ mTextNodeInfo(nullptr),
+ mCommentNodeInfo(nullptr),
+ mDocumentNodeInfo(nullptr)
+{
+ nsLayoutStatics::AddRef();
+
+ if (gNodeInfoManagerLeakPRLog)
+ MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug,
+ ("NODEINFOMANAGER %p created", this));
+
+ mNodeInfoHash = PL_NewHashTable(32, GetNodeInfoInnerHashValue,
+ NodeInfoInnerKeyCompare,
+ PL_CompareValues, &allocOps, nullptr);
+}
+
+
+nsNodeInfoManager::~nsNodeInfoManager()
+{
+ if (mNodeInfoHash)
+ PL_HashTableDestroy(mNodeInfoHash);
+
+ // Note: mPrincipal may be null here if we never got inited correctly
+ mPrincipal = nullptr;
+
+ mBindingManager = 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(mBindingManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsNodeInfoManager, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsNodeInfoManager, Release)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsNodeInfoManager)
+ if (tmp->mDocument) {
+ return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->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(nsDocument)->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(nsDocument)->CanSkipThis(tmp->mDocument);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+nsresult
+nsNodeInfoManager::Init(nsIDocument *aDocument)
+{
+ NS_ENSURE_TRUE(mNodeInfoHash, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_PRECONDITION(!mPrincipal,
+ "Being inited when we already have a principal?");
+
+ mPrincipal = nsNullPrincipal::Create();
+
+ if (aDocument) {
+ mBindingManager = new nsBindingManager(aDocument);
+ }
+
+ mDefaultPrincipal = mPrincipal;
+
+ mDocument = aDocument;
+
+ if (gNodeInfoManagerLeakPRLog)
+ MOZ_LOG(gNodeInfoManagerLeakPRLog, LogLevel::Debug,
+ ("NODEINFOMANAGER %p Init document=%p", this, aDocument));
+
+ return NS_OK;
+}
+
+// static
+int
+nsNodeInfoManager::DropNodeInfoDocument(PLHashEntry *he, int hashIndex, void *arg)
+{
+ static_cast<mozilla::dom::NodeInfo*>(he->value)->mDocument = nullptr;
+ return HT_ENUMERATE_NEXT;
+}
+
+void
+nsNodeInfoManager::DropDocumentReference()
+{
+ if (mBindingManager) {
+ mBindingManager->DropDocumentReference();
+ }
+
+ // This is probably not needed anymore.
+ PL_HashTableEnumerateEntries(mNodeInfoHash, DropNodeInfoDocument, nullptr);
+
+ NS_ASSERTION(!mNonDocumentNodeInfos, "Shouldn't have non-document nodeinfos!");
+ mDocument = nullptr;
+}
+
+
+already_AddRefed<mozilla::dom::NodeInfo>
+nsNodeInfoManager::GetNodeInfo(nsIAtom *aName, nsIAtom *aPrefix,
+ int32_t aNamespaceID, uint16_t aNodeType,
+ nsIAtom* aExtraName /* = nullptr */)
+{
+ CheckValidNodeInfo(aNodeType, aName, aNamespaceID, aExtraName);
+
+ NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType,
+ aExtraName);
+
+ void *node = PL_HashTableLookup(mNodeInfoHash, &tmpKey);
+
+ if (node) {
+ RefPtr<NodeInfo> nodeInfo = static_cast<NodeInfo*>(node);
+
+ return nodeInfo.forget();
+ }
+
+ RefPtr<NodeInfo> newNodeInfo =
+ new NodeInfo(aName, aPrefix, aNamespaceID, aNodeType, aExtraName, this);
+
+ DebugOnly<PLHashEntry*> he =
+ PL_HashTableAdd(mNodeInfoHash, &newNodeInfo->mInner, newNodeInfo);
+ MOZ_ASSERT(he, "PL_HashTableAdd() failed");
+
+ // Have to do the swap thing, because already_AddRefed<nsNodeInfo>
+ // doesn't cast to already_AddRefed<mozilla::dom::NodeInfo>
+ ++mNonDocumentNodeInfos;
+ if (mNonDocumentNodeInfos == 1) {
+ NS_IF_ADDREF(mDocument);
+ }
+
+ return newNodeInfo.forget();
+}
+
+
+nsresult
+nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsIAtom *aPrefix,
+ int32_t aNamespaceID, uint16_t aNodeType,
+ NodeInfo** aNodeInfo)
+{
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIAtom> nameAtom = NS_Atomize(aName);
+ CheckValidNodeInfo(aNodeType, nameAtom, aNamespaceID, nullptr);
+ }
+#endif
+
+ NodeInfo::NodeInfoInner tmpKey(aName, aPrefix, aNamespaceID, aNodeType);
+
+ void *node = PL_HashTableLookup(mNodeInfoHash, &tmpKey);
+
+ if (node) {
+ RefPtr<NodeInfo> nodeInfo = static_cast<NodeInfo*>(node);
+ nodeInfo.forget(aNodeInfo);
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAtom> nameAtom = NS_Atomize(aName);
+ NS_ENSURE_TRUE(nameAtom, NS_ERROR_OUT_OF_MEMORY);
+
+ RefPtr<NodeInfo> newNodeInfo =
+ new NodeInfo(nameAtom, aPrefix, aNamespaceID, aNodeType, nullptr, this);
+ NS_ENSURE_TRUE(newNodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+ PLHashEntry *he;
+ he = PL_HashTableAdd(mNodeInfoHash, &newNodeInfo->mInner, newNodeInfo);
+ NS_ENSURE_TRUE(he, NS_ERROR_FAILURE);
+
+ ++mNonDocumentNodeInfos;
+ if (mNonDocumentNodeInfos == 1) {
+ NS_IF_ADDREF(mDocument);
+ }
+
+ newNodeInfo.forget(aNodeInfo);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsNodeInfoManager::GetNodeInfo(const nsAString& aName, nsIAtom *aPrefix,
+ const nsAString& aNamespaceURI,
+ uint16_t aNodeType,
+ NodeInfo** aNodeInfo)
+{
+ int32_t nsid = kNameSpaceID_None;
+
+ if (!aNamespaceURI.IsEmpty()) {
+ nsresult rv = nsContentUtils::NameSpaceManager()->
+ 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,
+ nsIDOMNode::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, nsIDOMNode::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, nsIDOMNode::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::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");
+ if (nsContentUtils::IsExpandedPrincipal(aPrincipal)) {
+ Telemetry::Accumulate(Telemetry::DOCUMENT_WITH_EXPANDED_PRINCIPAL, 1);
+ }
+
+ mPrincipal = aPrincipal;
+}
+
+void
+nsNodeInfoManager::RemoveNodeInfo(NodeInfo *aNodeInfo)
+{
+ NS_PRECONDITION(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;
+ }
+ }
+
+#ifdef DEBUG
+ bool ret =
+#endif
+ PL_HashTableRemove(mNodeInfoHash, &aNodeInfo->mInner);
+
+ NS_POSTCONDITION(ret, "Can't find mozilla::dom::NodeInfo to remove!!!");
+}
diff --git a/dom/base/nsNodeInfoManager.h b/dom/base/nsNodeInfoManager.h
new file mode 100644
index 000000000..6ece66577
--- /dev/null
+++ b/dom/base/nsNodeInfoManager.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/. */
+
+/*
+ * 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 "nsCOMPtr.h" // for member
+#include "nsCycleCollectionParticipant.h" // for NS_DECL_CYCLE_*
+#include "plhash.h" // for typedef PLHashNumber
+
+class nsAString;
+class nsBindingManager;
+class nsIAtom;
+class nsIDocument;
+class nsIDOMDocumentType;
+class nsIPrincipal;
+struct PLHashEntry;
+struct PLHashTable;
+template<class T> struct already_AddRefed;
+
+namespace mozilla {
+namespace dom {
+class NodeInfo;
+} // namespace dom
+} // namespace mozilla
+
+class nsNodeInfoManager final
+{
+private:
+ ~nsNodeInfoManager();
+
+public:
+ nsNodeInfoManager();
+
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS(nsNodeInfoManager)
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsNodeInfoManager)
+
+ /**
+ * Initialize the nodeinfo manager with a document.
+ */
+ nsresult Init(nsIDocument *aDocument);
+
+ /**
+ * 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(nsIAtom *aName, nsIAtom *aPrefix, int32_t aNamespaceID,
+ uint16_t aNodeType, nsIAtom* aExtraName = nullptr);
+ nsresult GetNodeInfo(const nsAString& aName, nsIAtom *aPrefix,
+ int32_t aNamespaceID, uint16_t aNodeType,
+ mozilla::dom::NodeInfo** aNodeInfo);
+ nsresult GetNodeInfo(const nsAString& aName, nsIAtom *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.
+ */
+ nsIDocument* 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);
+
+ nsBindingManager* GetBindingManager() const
+ {
+ return mBindingManager;
+ }
+
+protected:
+ friend class nsDocument;
+ friend class nsXULPrototypeDocument;
+ friend nsresult NS_NewDOMDocumentType(nsIDOMDocumentType** ,
+ nsNodeInfoManager *,
+ nsIAtom *,
+ const nsAString& ,
+ const nsAString& ,
+ const nsAString& );
+
+ /**
+ * Sets the principal of the document this nodeinfo manager belongs to.
+ */
+ void SetDocumentPrincipal(nsIPrincipal *aPrincipal);
+
+private:
+ static int NodeInfoInnerKeyCompare(const void *key1, const void *key2);
+ static PLHashNumber GetNodeInfoInnerHashValue(const void *key);
+ static int DropNodeInfoDocument(PLHashEntry *he, int hashIndex,
+ void *arg);
+
+ PLHashTable *mNodeInfoHash;
+ nsIDocument * MOZ_NON_OWNING_REF mDocument; // WEAK
+ uint32_t mNonDocumentNodeInfos;
+ nsCOMPtr<nsIPrincipal> mPrincipal; // Never null after Init() succeeds.
+ nsCOMPtr<nsIPrincipal> mDefaultPrincipal; // Never null after Init() succeeds
+ 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
+ RefPtr<nsBindingManager> mBindingManager;
+};
+
+#endif /* nsNodeInfoManager_h___ */
diff --git a/dom/base/nsNodeUtils.cpp b/dom/base/nsNodeUtils.cpp
new file mode 100644
index 000000000..69a9414fe
--- /dev/null
+++ b/dom/base/nsNodeUtils.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 "nsNodeUtils.h"
+#include "nsContentUtils.h"
+#include "nsCSSPseudoElements.h"
+#include "nsINode.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Element.h"
+#include "nsIMutationObserver.h"
+#include "nsIDocument.h"
+#include "mozilla/EventListenerManager.h"
+#include "nsIXPConnect.h"
+#include "PLDHashTable.h"
+#include "nsIDOMAttr.h"
+#include "nsCOMArray.h"
+#include "nsPIDOMWindow.h"
+#include "nsDocument.h"
+#ifdef MOZ_XUL
+#include "nsXULElement.h"
+#endif
+#include "nsBindingManager.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/AnimationTarget.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/Animation.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/KeyframeEffectReadOnly.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsObjectLoadingContent.h"
+#include "nsDOMMutationObserver.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/ShadowRoot.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using mozilla::AutoJSContext;
+
+// This macro expects the ownerDocument of content_ to be in scope as
+// |nsIDocument* doc|
+#define IMPL_MUTATION_NOTIFICATION(func_, content_, params_) \
+ PR_BEGIN_MACRO \
+ bool needsEnterLeave = doc->MayHaveDOMMutationObservers(); \
+ if (needsEnterLeave) { \
+ nsDOMMutationObserver::EnterMutationHandling(); \
+ } \
+ nsINode* node = content_; \
+ NS_ASSERTION(node->OwnerDoc() == doc, "Bogus document"); \
+ if (doc) { \
+ doc->BindingManager()->func_ params_; \
+ } \
+ do { \
+ nsINode::nsSlots* slots = node->GetExistingSlots(); \
+ if (slots && !slots->mMutationObservers.IsEmpty()) { \
+ /* No need to explicitly notify the first observer first \
+ since that'll happen anyway. */ \
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS( \
+ slots->mMutationObservers, nsIMutationObserver, \
+ func_, params_); \
+ } \
+ ShadowRoot* shadow = ShadowRoot::FromNode(node); \
+ if (shadow) { \
+ node = shadow->GetPoolHost(); \
+ } else { \
+ node = node->GetParentNode(); \
+ } \
+ } while (node); \
+ if (needsEnterLeave) { \
+ nsDOMMutationObserver::LeaveMutationHandling(); \
+ } \
+ PR_END_MACRO
+
+#define IMPL_ANIMATION_NOTIFICATION(func_, content_, params_) \
+ PR_BEGIN_MACRO \
+ bool needsEnterLeave = doc->MayHaveDOMMutationObservers(); \
+ if (needsEnterLeave) { \
+ nsDOMMutationObserver::EnterMutationHandling(); \
+ } \
+ nsINode* node = content_; \
+ do { \
+ nsINode::nsSlots* slots = node->GetExistingSlots(); \
+ if (slots && !slots->mMutationObservers.IsEmpty()) { \
+ /* No need to explicitly notify the first observer first \
+ since that'll happen anyway. */ \
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS_WITH_QI( \
+ slots->mMutationObservers, nsIMutationObserver, \
+ nsIAnimationObserver, func_, params_); \
+ } \
+ ShadowRoot* shadow = ShadowRoot::FromNode(node); \
+ if (shadow) { \
+ node = shadow->GetPoolHost(); \
+ } else { \
+ node = node->GetParentNode(); \
+ } \
+ } while (node); \
+ if (needsEnterLeave) { \
+ nsDOMMutationObserver::LeaveMutationHandling(); \
+ } \
+ PR_END_MACRO
+
+void
+nsNodeUtils::CharacterDataWillChange(nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ nsIDocument* doc = aContent->OwnerDoc();
+ IMPL_MUTATION_NOTIFICATION(CharacterDataWillChange, aContent,
+ (doc, aContent, aInfo));
+}
+
+void
+nsNodeUtils::CharacterDataChanged(nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ nsIDocument* doc = aContent->OwnerDoc();
+ IMPL_MUTATION_NOTIFICATION(CharacterDataChanged, aContent,
+ (doc, aContent, aInfo));
+}
+
+void
+nsNodeUtils::AttributeWillChange(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ nsIDocument* doc = aElement->OwnerDoc();
+ IMPL_MUTATION_NOTIFICATION(AttributeWillChange, aElement,
+ (doc, aElement, aNameSpaceID, aAttribute,
+ aModType, aNewValue));
+}
+
+void
+nsNodeUtils::AttributeChanged(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ nsIDocument* doc = aElement->OwnerDoc();
+ IMPL_MUTATION_NOTIFICATION(AttributeChanged, aElement,
+ (doc, aElement, aNameSpaceID, aAttribute,
+ aModType, aOldValue));
+}
+
+void
+nsNodeUtils::AttributeSetToCurrentValue(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute)
+{
+ nsIDocument* doc = aElement->OwnerDoc();
+ IMPL_MUTATION_NOTIFICATION(AttributeSetToCurrentValue, aElement,
+ (doc, aElement, aNameSpaceID, aAttribute));
+}
+
+void
+nsNodeUtils::ContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ nsIDocument* doc = aContainer->OwnerDoc();
+
+ IMPL_MUTATION_NOTIFICATION(ContentAppended, aContainer,
+ (doc, aContainer, aFirstNewContent,
+ aNewIndexInContainer));
+}
+
+void
+nsNodeUtils::NativeAnonymousChildListChange(nsIContent* aContent,
+ bool aIsRemove)
+{
+ nsIDocument* doc = aContent->OwnerDoc();
+ IMPL_MUTATION_NOTIFICATION(NativeAnonymousChildListChange, aContent,
+ (doc, aContent, aIsRemove));
+}
+
+void
+nsNodeUtils::ContentInserted(nsINode* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ NS_PRECONDITION(aContainer->IsNodeOfType(nsINode::eCONTENT) ||
+ aContainer->IsNodeOfType(nsINode::eDOCUMENT),
+ "container must be an nsIContent or an nsIDocument");
+ nsIContent* container;
+ nsIDocument* doc = aContainer->OwnerDoc();
+ nsIDocument* document;
+ if (aContainer->IsNodeOfType(nsINode::eCONTENT)) {
+ container = static_cast<nsIContent*>(aContainer);
+ document = doc;
+ }
+ else {
+ container = nullptr;
+ document = static_cast<nsIDocument*>(aContainer);
+ }
+
+ IMPL_MUTATION_NOTIFICATION(ContentInserted, aContainer,
+ (document, container, aChild, aIndexInContainer));
+}
+
+void
+nsNodeUtils::ContentRemoved(nsINode* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ NS_PRECONDITION(aContainer->IsNodeOfType(nsINode::eCONTENT) ||
+ aContainer->IsNodeOfType(nsINode::eDOCUMENT),
+ "container must be an nsIContent or an nsIDocument");
+ nsIContent* container;
+ nsIDocument* doc = aContainer->OwnerDoc();
+ nsIDocument* document;
+ if (aContainer->IsNodeOfType(nsINode::eCONTENT)) {
+ container = static_cast<nsIContent*>(aContainer);
+ document = doc;
+ }
+ else {
+ container = nullptr;
+ document = static_cast<nsIDocument*>(aContainer);
+ }
+
+ IMPL_MUTATION_NOTIFICATION(ContentRemoved, aContainer,
+ (document, container, aChild, aIndexInContainer,
+ aPreviousSibling));
+}
+
+Maybe<NonOwningAnimationTarget>
+nsNodeUtils::GetTargetForAnimation(const Animation* aAnimation)
+{
+ AnimationEffectReadOnly* effect = aAnimation->GetEffect();
+ if (!effect || !effect->AsKeyframeEffect()) {
+ return Nothing();
+ }
+ return effect->AsKeyframeEffect()->GetTarget();
+}
+
+void
+nsNodeUtils::AnimationMutated(Animation* aAnimation,
+ AnimationMutationType aMutatedType)
+{
+ Maybe<NonOwningAnimationTarget> target = GetTargetForAnimation(aAnimation);
+ if (!target) {
+ return;
+ }
+
+ // A pseudo element and its parent element use the same owner doc.
+ nsIDocument* 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
+nsNodeUtils::AnimationAdded(Animation* aAnimation)
+{
+ AnimationMutated(aAnimation, AnimationMutationType::Added);
+}
+
+void
+nsNodeUtils::AnimationChanged(Animation* aAnimation)
+{
+ AnimationMutated(aAnimation, AnimationMutationType::Changed);
+}
+
+void
+nsNodeUtils::AnimationRemoved(Animation* aAnimation)
+{
+ AnimationMutated(aAnimation, AnimationMutationType::Removed);
+}
+
+void
+nsNodeUtils::LastRelease(nsINode* aNode)
+{
+ nsINode::nsSlots* slots = aNode->GetExistingSlots();
+ if (slots) {
+ if (!slots->mMutationObservers.IsEmpty()) {
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(slots->mMutationObservers,
+ nsIMutationObserver,
+ NodeWillBeDestroyed, (aNode));
+ }
+
+ if (aNode->IsElement()) {
+ Element* elem = aNode->AsElement();
+ FragmentOrElement::nsDOMSlots* domSlots =
+ static_cast<FragmentOrElement::nsDOMSlots*>(slots);
+ for (auto& reg : domSlots->mRegisteredIntersectionObservers) {
+ reg.observer->UnlinkTarget(*elem);
+ }
+ }
+
+ delete slots;
+ aNode->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 (aNode->IsNodeOfType(nsINode::eDOCUMENT)) {
+ // 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.
+ static_cast<nsIDocument*>(aNode)->DeleteAllProperties();
+ }
+ else {
+ if (aNode->HasProperties()) {
+ // Strong reference to the document so that deleting properties can't
+ // delete the document.
+ nsCOMPtr<nsIDocument> document = aNode->OwnerDoc();
+ document->DeleteAllPropertiesFor(aNode);
+ }
+
+ // I wonder whether it's faster to do the HasFlag check first....
+ if (aNode->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) &&
+ aNode->HasFlag(ADDED_TO_FORM)) {
+ // Tell the form (if any) this node is going away. Don't
+ // notify, since we're being destroyed in any case.
+ static_cast<nsGenericHTMLFormElement*>(aNode)->ClearForm(true);
+ }
+
+ if (aNode->IsHTMLElement(nsGkAtoms::img) &&
+ aNode->HasFlag(ADDED_TO_FORM)) {
+ HTMLImageElement* imageElem = static_cast<HTMLImageElement*>(aNode);
+ imageElem->ClearForm(true);
+ }
+ }
+ aNode->UnsetFlags(NODE_HAS_PROPERTIES);
+
+ if (aNode->NodeType() != nsIDOMNode::DOCUMENT_NODE &&
+ aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) {
+#ifdef DEBUG
+ if (nsContentUtils::IsInitialized()) {
+ EventListenerManager* manager =
+ nsContentUtils::GetExistingListenerManagerForNode(aNode);
+ if (!manager) {
+ NS_ERROR("Huh, our bit says we have a listener manager list, "
+ "but there's nothing in the hash!?!!");
+ }
+ }
+#endif
+
+ nsContentUtils::RemoveListenerManager(aNode);
+ aNode->UnsetFlags(NODE_HAS_LISTENERMANAGER);
+ }
+
+ if (aNode->IsElement()) {
+ nsIDocument* ownerDoc = aNode->OwnerDoc();
+ Element* elem = aNode->AsElement();
+ ownerDoc->ClearBoxObjectFor(elem);
+
+ NS_ASSERTION(aNode->HasFlag(NODE_FORCE_XBL_BINDINGS) ||
+ !elem->GetXBLBinding(),
+ "Non-forced node has binding on destruction");
+
+ // if NODE_FORCE_XBL_BINDINGS is set, the node might still have a binding
+ // attached
+ if (aNode->HasFlag(NODE_FORCE_XBL_BINDINGS) &&
+ ownerDoc->BindingManager()) {
+ ownerDoc->BindingManager()->RemovedFromDocument(elem, ownerDoc,
+ nsBindingManager::eRunDtor);
+ }
+ }
+
+ aNode->ReleaseWrapper(aNode);
+
+ FragmentOrElement::RemoveBlackMarkedNode(aNode);
+}
+
+static void
+NoteUserData(void *aObject, nsIAtom *aKey, void *aXPCOMChild, void *aData)
+{
+ nsCycleCollectionTraversalCallback* cb =
+ static_cast<nsCycleCollectionTraversalCallback*>(aData);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "[user data]");
+ cb->NoteXPCOMChild(static_cast<nsISupports*>(aXPCOMChild));
+}
+
+/* static */
+void
+nsNodeUtils::TraverseUserData(nsINode* aNode,
+ nsCycleCollectionTraversalCallback &aCb)
+{
+ nsIDocument* ownerDoc = aNode->OwnerDoc();
+ ownerDoc->PropertyTable(DOM_USER_DATA)->Enumerate(aNode, NoteUserData, &aCb);
+}
+
+/* static */
+nsresult
+nsNodeUtils::CloneNodeImpl(nsINode *aNode, bool aDeep, nsINode **aResult)
+{
+ *aResult = nullptr;
+
+ nsCOMPtr<nsINode> newNode;
+ nsCOMArray<nsINode> nodesWithProperties;
+ nsresult rv = Clone(aNode, aDeep, nullptr, nodesWithProperties,
+ getter_AddRefs(newNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newNode.forget(aResult);
+ return NS_OK;
+}
+
+/* static */
+nsresult
+nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep,
+ nsNodeInfoManager *aNewNodeInfoManager,
+ JS::Handle<JSObject*> aReparentScope,
+ nsCOMArray<nsINode> &aNodesWithProperties,
+ nsINode *aParent, nsINode **aResult)
+{
+ NS_PRECONDITION((!aClone && aNewNodeInfoManager) || !aReparentScope,
+ "If cloning or not getting a new nodeinfo we shouldn't "
+ "rewrap");
+ NS_PRECONDITION(!aParent || aNode->IsNodeOfType(nsINode::eCONTENT),
+ "Can't insert document or attribute nodes into a parent");
+
+ *aResult = nullptr;
+
+ // 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;
+ nsresult rv;
+
+ nsNodeInfoManager *nodeInfoManager = aNewNodeInfoManager;
+
+ // aNode.
+ NodeInfo *nodeInfo = aNode->mNodeInfo;
+ RefPtr<NodeInfo> newNodeInfo;
+ if (nodeInfoManager) {
+
+ // Don't allow importing/adopting nodes from non-privileged "scriptable"
+ // documents to "non-scriptable" documents.
+ nsIDocument* newDoc = nodeInfoManager->GetDocument();
+ NS_ENSURE_STATE(newDoc);
+ bool hasHadScriptHandlingObject = false;
+ if (!newDoc->GetScriptHandlingObject(hasHadScriptHandlingObject) &&
+ !hasHadScriptHandlingObject) {
+ nsIDocument* currentDoc = aNode->OwnerDoc();
+ NS_ENSURE_STATE((nsContentUtils::IsChromeDoc(currentDoc) ||
+ (!currentDoc->GetScriptHandlingObject(hasHadScriptHandlingObject) &&
+ !hasHadScriptHandlingObject)));
+ }
+
+ newNodeInfo = nodeInfoManager->GetNodeInfo(nodeInfo->NameAtom(),
+ nodeInfo->GetPrefixAtom(),
+ nodeInfo->NamespaceID(),
+ nodeInfo->NodeType(),
+ nodeInfo->GetExtraName());
+
+ nodeInfo = newNodeInfo;
+ }
+
+ Element *elem = aNode->IsElement() ? aNode->AsElement() : nullptr;
+
+ nsCOMPtr<nsINode> clone;
+ if (aClone) {
+ rv = aNode->Clone(nodeInfo, getter_AddRefs(clone));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (clone->IsElement()) {
+ // The cloned node may be a custom element that may require
+ // enqueing created callback and prototype swizzling.
+ Element* elem = clone->AsElement();
+ if (nsContentUtils::IsCustomElementName(nodeInfo->NameAtom())) {
+ nsContentUtils::SetupCustomElement(elem);
+ } else {
+ // Check if node may be custom element by type extension.
+ // ex. <button is="x-button">
+ nsAutoString extension;
+ if (elem->GetAttr(kNameSpaceID_None, nsGkAtoms::is, extension) &&
+ !extension.IsEmpty()) {
+ nsContentUtils::SetupCustomElement(elem, &extension);
+ }
+ }
+ }
+
+ if (aParent) {
+ // If we're cloning we need to insert the cloned children into the cloned
+ // parent.
+ rv = aParent->AppendChildTo(static_cast<nsIContent*>(clone.get()),
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (aDeep && clone->IsNodeOfType(nsINode::eDOCUMENT)) {
+ // 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) {
+ nsIDocument* oldDoc = aNode->OwnerDoc();
+ bool wasRegistered = false;
+ if (aNode->IsElement()) {
+ Element* element = aNode->AsElement();
+ oldDoc->ClearBoxObjectFor(element);
+ wasRegistered = oldDoc->UnregisterActivityObserver(element);
+ }
+
+ aNode->mNodeInfo.swap(newNodeInfo);
+ if (elem) {
+ elem->NodeInfoChanged();
+ }
+
+ nsIDocument* newDoc = aNode->OwnerDoc();
+ if (newDoc) {
+ // 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 (wasRegistered && oldDoc != newDoc) {
+ nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aNode));
+ if (domMediaElem) {
+ HTMLMediaElement* mediaElem = static_cast<HTMLMediaElement*>(aNode);
+ mediaElem->NotifyOwnerDocumentActivityChanged();
+ }
+ nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent(do_QueryInterface(aNode));
+ if (objectLoadingContent) {
+ nsObjectLoadingContent* olc = static_cast<nsObjectLoadingContent*>(objectLoadingContent.get());
+ olc->NotifyOwnerDocumentActivityChanged();
+ }
+ }
+
+ if (oldDoc != newDoc && oldDoc->MayHaveDOMMutationObservers()) {
+ newDoc->SetMayHaveDOMMutationObservers();
+ }
+
+ if (oldDoc != newDoc && 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));
+ JSAutoCompartment ac(cx, wrapper);
+ rv = ReparentWrapper(cx, wrapper);
+ if (NS_FAILED(rv)) {
+ if (wasRegistered) {
+ aNode->OwnerDoc()->UnregisterActivityObserver(aNode->AsElement());
+ }
+ aNode->mNodeInfo.swap(newNodeInfo);
+ if (wasRegistered) {
+ aNode->OwnerDoc()->RegisterActivityObserver(aNode->AsElement());
+ }
+ return rv;
+ }
+ }
+ }
+ }
+
+ if (aNode->HasProperties()) {
+ bool ok = aNodesWithProperties.AppendObject(aNode);
+ MOZ_RELEASE_ASSERT(ok, "Out of memory");
+ if (aClone) {
+ ok = aNodesWithProperties.AppendObject(clone);
+ MOZ_RELEASE_ASSERT(ok, "Out of memory");
+ }
+ }
+
+ if (aDeep && (!aClone || !aNode->IsNodeOfType(nsINode::eATTRIBUTE))) {
+ // aNode's children.
+ for (nsIContent* cloneChild = aNode->GetFirstChild();
+ cloneChild;
+ cloneChild = cloneChild->GetNextSibling()) {
+ nsCOMPtr<nsINode> child;
+ rv = CloneAndAdopt(cloneChild, aClone, true, nodeInfoManager,
+ aReparentScope, aNodesWithProperties, clone,
+ getter_AddRefs(child));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Cloning template element.
+ if (aDeep && aClone && IsTemplateElement(aNode)) {
+ 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;
+ rv = CloneAndAdopt(cloneChild, aClone, aDeep, ownerNodeInfoManager,
+ aReparentScope, aNodesWithProperties, cloneContent,
+ getter_AddRefs(child));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // XXX setting document on some nodes not in a document so XBL will bind
+ // and chrome won't break. Make XBL bind to document-less nodes!
+ // XXXbz Once this is fixed, fix up the asserts in all implementations of
+ // BindToTree to assert what they would like to assert, and fix the
+ // ChangeDocumentFor() call in nsXULElement::BindToTree as well. Also,
+ // remove the UnbindFromTree call in ~nsXULElement, and add back in the
+ // precondition in nsXULElement::UnbindFromTree and remove the line in
+ // nsXULElement.h that makes nsNodeUtils a friend of nsXULElement.
+ // Note: Make sure to do this witchery _after_ we've done any deep
+ // cloning, so kids of the new node aren't confused about whether they're
+ // in a document.
+#ifdef MOZ_XUL
+ if (aClone && !aParent && aNode->IsXULElement()) {
+ if (!aNode->OwnerDoc()->IsLoadedAsInteractiveData()) {
+ clone->SetFlags(NODE_FORCE_XBL_BINDINGS);
+ }
+ }
+#endif
+
+ clone.forget(aResult);
+
+ return NS_OK;
+}
+
+
+/* static */
+void
+nsNodeUtils::UnlinkUserData(nsINode *aNode)
+{
+ NS_ASSERTION(aNode->HasProperties(), "Call to UnlinkUserData not needed.");
+
+ // Strong reference to the document so that deleting properties can't
+ // delete the document.
+ nsCOMPtr<nsIDocument> document = aNode->OwnerDoc();
+ document->PropertyTable(DOM_USER_DATA)->DeleteAllPropertiesFor(aNode);
+}
+
+bool
+nsNodeUtils::IsTemplateElement(const nsINode *aNode)
+{
+ return aNode->IsHTMLElement(nsGkAtoms::_template);
+}
+
+nsIContent*
+nsNodeUtils::GetFirstChildOfTemplateOrNode(nsINode* aNode)
+{
+ if (nsNodeUtils::IsTemplateElement(aNode)) {
+ DocumentFragment* frag =
+ static_cast<HTMLTemplateElement*>(aNode)->Content();
+ return frag->GetFirstChild();
+ }
+
+ return aNode->GetFirstChild();
+}
+
diff --git a/dom/base/nsNodeUtils.h b/dom/base/nsNodeUtils.h
new file mode 100644
index 000000000..283bd4083
--- /dev/null
+++ b/dom/base/nsNodeUtils.h
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsNodeUtils_h___
+#define nsNodeUtils_h___
+
+#include "mozilla/Maybe.h"
+#include "nsIContent.h" // for use in inline function (ParentChainChanged)
+#include "nsIMutationObserver.h" // for use in inline function (ParentChainChanged)
+#include "js/TypeDecls.h"
+#include "nsCOMArray.h"
+
+struct CharacterDataChangeInfo;
+template<class E> class nsCOMArray;
+class nsCycleCollectionTraversalCallback;
+namespace mozilla {
+struct NonOwningAnimationTarget;
+namespace dom {
+class Animation;
+} // namespace dom
+} // namespace mozilla
+
+class nsNodeUtils
+{
+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 CharacterDataWillChange(nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo);
+
+ /**
+ * 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 CharacterDataChanged(nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo);
+
+ /**
+ * 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)
+ * @param aNewValue The parsed new value, but only if BeforeSetAttr
+ * preparsed it!!!
+ * @see nsIMutationObserver::AttributeWillChange
+ */
+ static void AttributeWillChange(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue);
+
+ /**
+ * 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 AttributeChanged(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* 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 AttributeSetToCurrentValue(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute);
+
+ /**
+ * Send ContentAppended notifications to nsIMutationObservers
+ * @param aContainer Node into which new child/children were added
+ * @param aFirstNewContent First new child
+ * @param aNewIndexInContainer Index of first new child
+ * @see nsIMutationObserver::ContentAppended
+ */
+ static void ContentAppended(nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer);
+
+ /**
+ * Send NativeAnonymousChildList notifications to nsIMutationObservers
+ * @param aContent Anonymous node that's been added or removed
+ * @param aIsRemove True if it's a removal, false if an addition
+ * @see nsIMutationObserver::NativeAnonymousChildListChange
+ */
+ static void NativeAnonymousChildListChange(nsIContent* aContent,
+ bool aIsRemove);
+
+ /**
+ * Send ContentInserted notifications to nsIMutationObservers
+ * @param aContainer Node into which new child was inserted
+ * @param aChild Newly inserted child
+ * @param aIndexInContainer Index of new child
+ * @see nsIMutationObserver::ContentInserted
+ */
+ static void ContentInserted(nsINode* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer);
+ /**
+ * Send ContentRemoved notifications to nsIMutationObservers
+ * @param aContainer Node from which child was removed
+ * @param aChild Removed child
+ * @param aIndexInContainer Index of removed child
+ * @see nsIMutationObserver::ContentRemoved
+ */
+ static void ContentRemoved(nsINode* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling);
+ /**
+ * Send ParentChainChanged notifications to nsIMutationObservers
+ * @param aContent The piece of content that had its parent changed.
+ * @see nsIMutationObserver::ParentChainChanged
+ */
+ static inline void ParentChainChanged(nsIContent *aContent)
+ {
+ nsINode::nsSlots* slots = aContent->GetExistingSlots();
+ if (slots && !slots->mMutationObservers.IsEmpty()) {
+ NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(slots->mMutationObservers,
+ nsIMutationObserver,
+ ParentChainChanged,
+ (aContent));
+ }
+ }
+
+ /**
+ * Utility function to get the target (pseudo-)element associated with an
+ * animation.
+ * @param aAnimation The animation whose target is what we want.
+ */
+ static mozilla::Maybe<mozilla::NonOwningAnimationTarget>
+ GetTargetForAnimation(const mozilla::dom::Animation* aAnimation);
+
+ /**
+ * Notify that an animation is added/changed/removed.
+ * @param aAnimation The animation we added/changed/removed.
+ */
+ static void AnimationAdded(mozilla::dom::Animation* aAnimation);
+ static void AnimationChanged(mozilla::dom::Animation* aAnimation);
+ static void AnimationRemoved(mozilla::dom::Animation* aAnimation);
+
+ /**
+ * To be called when reference count of aNode drops to zero.
+ * @param aNode The node which is going to be deleted.
+ */
+ static void LastRelease(nsINode* aNode);
+
+ /**
+ * Clones aNode, 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. aNodesWithProperties will be filled with all the nodes that
+ * have properties, and every node in it will be followed by its clone.
+ *
+ * @param aNode Node to clone.
+ * @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 aNodesWithProperties All nodes (from amongst aNode and its
+ * descendants) with properties. Every node will
+ * be followed by its clone.
+ * @param aResult *aResult will contain the cloned node.
+ */
+ static nsresult Clone(nsINode *aNode, bool aDeep,
+ nsNodeInfoManager *aNewNodeInfoManager,
+ nsCOMArray<nsINode> &aNodesWithProperties,
+ nsINode **aResult)
+ {
+ return CloneAndAdopt(aNode, true, aDeep, aNewNodeInfoManager,
+ nullptr, aNodesWithProperties, nullptr, aResult);
+ }
+
+ /**
+ * Clones aNode, its attributes and, if aDeep is true, its descendant nodes
+ */
+ static nsresult Clone(nsINode *aNode, bool aDeep, nsINode **aResult)
+ {
+ nsCOMArray<nsINode> dummyNodeWithProperties;
+ return CloneAndAdopt(aNode, true, aDeep, nullptr, nullptr,
+ dummyNodeWithProperties, aNode->GetParent(), aResult);
+ }
+
+ /**
+ * Walks aNode, 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.
+ * aNodesWithProperties will be filled with all the nodes that have
+ * properties.
+ *
+ * @param aNode Node to adopt.
+ * @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 New scope for the wrappers, or null if no reparenting
+ * should be done.
+ * @param aNodesWithProperties All nodes (from amongst aNode and its
+ * descendants) with properties.
+ */
+ static nsresult Adopt(nsINode *aNode, nsNodeInfoManager *aNewNodeInfoManager,
+ JS::Handle<JSObject*> aReparentScope,
+ nsCOMArray<nsINode> &aNodesWithProperties)
+ {
+ nsCOMPtr<nsINode> node;
+ nsresult rv = CloneAndAdopt(aNode, false, true, aNewNodeInfoManager,
+ aReparentScope, aNodesWithProperties,
+ nullptr, getter_AddRefs(node));
+
+ nsMutationGuard::DidMutate();
+
+ return rv;
+ }
+
+ /**
+ * Helper for the cycle collector to traverse the DOM UserData for aNode.
+ *
+ * @param aNode the node to traverse UserData for
+ * @param aCb the cycle collection callback
+ */
+ static void TraverseUserData(nsINode* aNode,
+ nsCycleCollectionTraversalCallback &aCb);
+
+ /**
+ * A basic implementation of the DOM cloneNode method. Calls nsINode::Clone to
+ * do the actual cloning of the node.
+ *
+ * @param aNode the node to clone
+ * @param aDeep if true all descendants will be cloned too
+ * @param aResult the clone
+ */
+ static nsresult CloneNodeImpl(nsINode *aNode, bool aDeep, nsINode **aResult);
+
+ /**
+ * Release the UserData for aNode.
+ *
+ * @param aNode the node to release the UserData for
+ */
+ static void UnlinkUserData(nsINode *aNode);
+
+ /**
+ * Returns a true if the node is a HTMLTemplate element.
+ *
+ * @param aNode a node to test for HTMLTemplate elementness.
+ */
+ static bool IsTemplateElement(const nsINode *aNode);
+
+ /**
+ * 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.
+ *
+ * @param aNode A node from which to retrieve the first child.
+ */
+ static nsIContent* GetFirstChildOfTemplateOrNode(nsINode* aNode);
+
+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.
+ * aNodesWithProperties will be filled with all the nodes that have
+ * properties.
+ *
+ * @param aNode Node to adopt/clone.
+ * @param aClone If true the node will be cloned and the cloned node will
+ * be in aResult.
+ * @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 aNodesWithProperties All nodes (from amongst aNode and its
+ * descendants) with properties. If aClone is
+ * true every node will be followed by its
+ * clone.
+ * @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 aResult If aClone is true then *aResult will contain the cloned
+ * node.
+ */
+ static nsresult CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep,
+ nsNodeInfoManager *aNewNodeInfoManager,
+ JS::Handle<JSObject*> aReparentScope,
+ nsCOMArray<nsINode> &aNodesWithProperties,
+ nsINode *aParent, nsINode **aResult);
+
+ 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 AnimationMutated(mozilla::dom::Animation* aAnimation,
+ AnimationMutationType aMutatedType);
+
+};
+
+#endif // nsNodeUtils_h___
diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp
new file mode 100644
index 000000000..9e9dacf01
--- /dev/null
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -0,0 +1,4079 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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>, <applet>, etc).
+ */
+
+// Interface headers
+#include "imgLoader.h"
+#include "nsIConsoleService.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMCustomEvent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMHTMLObjectElement.h"
+#include "nsIDOMHTMLAppletElement.h"
+#include "nsIExternalProtocolHandler.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIObjectFrame.h"
+#include "nsIPermissionManager.h"
+#include "nsPluginHost.h"
+#include "nsPluginInstanceOwner.h"
+#include "nsJSNPRuntime.h"
+#include "nsINestedURI.h"
+#include "nsIPresShell.h"
+#include "nsScriptSecurityManager.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStreamConverterService.h"
+#include "nsIURILoader.h"
+#include "nsIURL.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebNavigationInfo.h"
+#include "nsIScriptChannel.h"
+#include "nsIBlocklistService.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIAppShell.h"
+#include "nsIXULRuntime.h"
+#include "nsIScriptError.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 "nsGkAtoms.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsMimeTypes.h"
+#include "nsStyleUtil.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsSandboxFlags.h"
+
+// Concrete classes
+#include "nsFrameLoader.h"
+
+#include "nsObjectLoadingContent.h"
+#include "mozAutoDocUpdate.h"
+#include "nsIContentSecurityPolicy.h"
+#include "GeckoProfiler.h"
+#include "nsPluginFrame.h"
+#include "nsDOMClassInfo.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsDOMJSUtils.h"
+
+#include "nsWidgetsCID.h"
+#include "nsContentCID.h"
+#include "mozilla/BasicEvents.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/EventStates.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/HTMLObjectElementBinding.h"
+#include "mozilla/dom/HTMLSharedObjectElement.h"
+#include "nsChannelClassifier.h"
+
+#ifdef XP_WIN
+// Thanks so much, Microsoft! :(
+#ifdef CreateEvent
+#undef CreateEvent
+#endif
+#endif // XP_WIN
+
+#ifdef XP_MACOSX
+// HandlePluginCrashed() and HandlePluginInstantiated() needed from here to
+// fix bug 1147521. Should later be replaced by proper interface methods,
+// maybe on nsIObjectLoadingContext.
+#include "mozilla/dom/HTMLObjectElement.h"
+#endif
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
+static const char *kPrefJavaMIME = "plugin.java.mime";
+static const char *kPrefYoutubeRewrite = "plugins.rewrite_youtube_embeds";
+static const char *kPrefBlockURIs = "browser.safebrowsing.blockedURIs.enabled";
+static const char *kPrefFavorFallbackMode = "plugins.favorfallback.mode";
+static const char *kPrefFavorFallbackRules = "plugins.favorfallback.rules";
+
+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
+IsJavaMIME(const nsACString & aMIMEType)
+{
+ return
+ nsPluginHost::GetSpecialType(aMIMEType) == nsPluginHost::eSpecialType_Java;
+}
+
+static bool
+IsFlashMIME(const nsACString & aMIMEType)
+{
+ return
+ nsPluginHost::GetSpecialType(aMIMEType) == nsPluginHost::eSpecialType_Flash;
+}
+
+static bool
+InActiveDocument(nsIContent *aContent)
+{
+ if (!aContent->IsInComposedDoc()) {
+ return false;
+ }
+ nsIDocument *doc = aContent->OwnerDoc();
+ return (doc && doc->IsActive());
+}
+
+///
+/// Runnables and helper classes
+///
+
+class nsAsyncInstantiateEvent : public Runnable {
+public:
+ explicit nsAsyncInstantiateEvent(nsObjectLoadingContent* aContent)
+ : mContent(aContent) {}
+
+ ~nsAsyncInstantiateEvent() {}
+
+ NS_IMETHOD Run();
+
+private:
+ nsCOMPtr<nsIObjectLoadingContent> mContent;
+};
+
+NS_IMETHODIMP
+nsAsyncInstantiateEvent::Run()
+{
+ nsObjectLoadingContent *objLC =
+ static_cast<nsObjectLoadingContent *>(mContent.get());
+
+ // If objLC is no longer tracking this event, we've been canceled or
+ // superseded
+ if (objLC->mPendingInstantiateEvent != this) {
+ return NS_OK;
+ }
+ objLC->mPendingInstantiateEvent = nullptr;
+
+ return objLC->SyncStartPluginInstance();
+}
+
+// Checks to see if the content for a plugin instance should be unloaded
+// (outside an active document) or stopped (in a document but unrendered). This
+// is used to allow scripts to move a plugin around the document hierarchy
+// without re-instantiating it.
+class CheckPluginStopEvent : public Runnable {
+public:
+ explicit CheckPluginStopEvent(nsObjectLoadingContent* aContent)
+ : mContent(aContent) {}
+
+ ~CheckPluginStopEvent() {}
+
+ NS_IMETHOD Run();
+
+private:
+ nsCOMPtr<nsIObjectLoadingContent> mContent;
+};
+
+NS_IMETHODIMP
+CheckPluginStopEvent::Run()
+{
+ nsObjectLoadingContent *objLC =
+ static_cast<nsObjectLoadingContent *>(mContent.get());
+
+ // If objLC is no longer tracking this event, we've been canceled or
+ // superseded. We clear this before we finish - either by calling
+ // UnloadObject/StopPluginInstance, or directly if we took no action.
+ if (objLC->mPendingCheckPluginStopEvent != this) {
+ return NS_OK;
+ }
+
+ // CheckPluginStopEvent is queued when we either lose our frame, are removed
+ // from the document, or the document goes inactive. To avoid stopping the
+ // plugin when script is reparenting us or layout is rebuilding, we wait until
+ // this event to decide to stop.
+
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(static_cast<nsIImageLoadingContent *>(objLC));
+ if (!InActiveDocument(content)) {
+ LOG(("OBJLC [%p]: Unloading plugin outside of document", this));
+ objLC->StopPluginInstance();
+ return NS_OK;
+ }
+
+ if (content->GetPrimaryFrame()) {
+ LOG(("OBJLC [%p]: CheckPluginStopEvent - in active document with frame"
+ ", no action", this));
+ objLC->mPendingCheckPluginStopEvent = nullptr;
+ return NS_OK;
+ }
+
+ // In an active document, but still no frame. Flush layout to see if we can
+ // regain a frame now.
+ LOG(("OBJLC [%p]: CheckPluginStopEvent - No frame, flushing layout", this));
+ nsIDocument* composedDoc = content->GetComposedDoc();
+ if (composedDoc) {
+ composedDoc->FlushPendingNotifications(Flush_Layout);
+ if (objLC->mPendingCheckPluginStopEvent != this) {
+ LOG(("OBJLC [%p]: CheckPluginStopEvent - superseded in layout flush",
+ this));
+ return NS_OK;
+ } else if (content->GetPrimaryFrame()) {
+ LOG(("OBJLC [%p]: CheckPluginStopEvent - frame gained in layout flush",
+ this));
+ objLC->mPendingCheckPluginStopEvent = nullptr;
+ return NS_OK;
+ }
+ }
+
+ // Still no frame, suspend plugin. HasNewFrame will restart us when we
+ // become rendered again
+ LOG(("OBJLC [%p]: Stopping plugin that lost frame", this));
+ objLC->StopPluginInstance();
+
+ return NS_OK;
+}
+
+/**
+ * Helper task for firing simple events
+ */
+class nsSimplePluginEvent : public Runnable {
+public:
+ nsSimplePluginEvent(nsIContent* aTarget, const nsAString &aEvent)
+ : mTarget(aTarget)
+ , mDocument(aTarget->GetComposedDoc())
+ , mEvent(aEvent)
+ {
+ MOZ_ASSERT(aTarget && mDocument);
+ }
+
+ nsSimplePluginEvent(nsIDocument* aTarget, const nsAString& aEvent)
+ : mTarget(aTarget)
+ , mDocument(aTarget)
+ , mEvent(aEvent)
+ {
+ MOZ_ASSERT(aTarget);
+ }
+
+ nsSimplePluginEvent(nsIContent* aTarget,
+ nsIDocument* aDocument,
+ const nsAString& aEvent)
+ : mTarget(aTarget)
+ , mDocument(aDocument)
+ , mEvent(aEvent)
+ {
+ MOZ_ASSERT(aTarget && aDocument);
+ }
+
+ ~nsSimplePluginEvent() {}
+
+ NS_IMETHOD Run();
+
+private:
+ nsCOMPtr<nsISupports> mTarget;
+ nsCOMPtr<nsIDocument> mDocument;
+ nsString mEvent;
+};
+
+NS_IMETHODIMP
+nsSimplePluginEvent::Run()
+{
+ if (mDocument && mDocument->IsActive()) {
+ LOG(("OBJLC [%p]: nsSimplePluginEvent firing event \"%s\"", mTarget.get(),
+ NS_ConvertUTF16toUTF8(mEvent).get()));
+ nsContentUtils::DispatchTrustedEvent(mDocument, mTarget,
+ mEvent, true, true);
+ }
+ return NS_OK;
+}
+
+/**
+ * A task for firing PluginCrashed DOM Events.
+ */
+class nsPluginCrashedEvent : public Runnable {
+public:
+ nsCOMPtr<nsIContent> mContent;
+ nsString mPluginDumpID;
+ nsString mBrowserDumpID;
+ nsString mPluginName;
+ nsString mPluginFilename;
+ bool mSubmittedCrashReport;
+
+ nsPluginCrashedEvent(nsIContent* aContent,
+ const nsAString& aPluginDumpID,
+ const nsAString& aBrowserDumpID,
+ const nsAString& aPluginName,
+ const nsAString& aPluginFilename,
+ bool submittedCrashReport)
+ : mContent(aContent),
+ mPluginDumpID(aPluginDumpID),
+ mBrowserDumpID(aBrowserDumpID),
+ mPluginName(aPluginName),
+ mPluginFilename(aPluginFilename),
+ mSubmittedCrashReport(submittedCrashReport)
+ {}
+
+ ~nsPluginCrashedEvent() {}
+
+ NS_IMETHOD Run();
+};
+
+NS_IMETHODIMP
+nsPluginCrashedEvent::Run()
+{
+ LOG(("OBJLC [%p]: Firing plugin crashed event\n",
+ mContent.get()));
+
+ nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
+ if (!doc) {
+ NS_WARNING("Couldn't get document for PluginCrashed event!");
+ return NS_OK;
+ }
+
+ PluginCrashedEventInit init;
+ init.mPluginDumpID = mPluginDumpID;
+ init.mBrowserDumpID = mBrowserDumpID;
+ init.mPluginName = mPluginName;
+ init.mPluginFilename = mPluginFilename;
+ init.mSubmittedCrashReport = mSubmittedCrashReport;
+ init.mBubbles = true;
+ init.mCancelable = true;
+
+ RefPtr<PluginCrashedEvent> event =
+ PluginCrashedEvent::Constructor(doc, NS_LITERAL_STRING("PluginCrashed"), init);
+
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ EventDispatcher::DispatchDOMEvent(mContent, nullptr, event, nullptr, nullptr);
+ return NS_OK;
+}
+
+class nsStopPluginRunnable : public Runnable, public nsITimerCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsStopPluginRunnable(nsPluginInstanceOwner* aInstanceOwner,
+ nsObjectLoadingContent* aContent)
+ : mInstanceOwner(aInstanceOwner)
+ , mContent(aContent)
+ {
+ NS_ASSERTION(aInstanceOwner, "need an owner");
+ NS_ASSERTION(aContent, "need a nsObjectLoadingContent");
+ }
+
+ // Runnable
+ NS_IMETHOD Run() override;
+
+ // nsITimerCallback
+ NS_IMETHOD Notify(nsITimer* timer) override;
+
+protected:
+ virtual ~nsStopPluginRunnable() {}
+
+private:
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<nsPluginInstanceOwner> mInstanceOwner;
+ nsCOMPtr<nsIObjectLoadingContent> mContent;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsStopPluginRunnable, Runnable, nsITimerCallback)
+
+NS_IMETHODIMP
+nsStopPluginRunnable::Notify(nsITimer *aTimer)
+{
+ return Run();
+}
+
+NS_IMETHODIMP
+nsStopPluginRunnable::Run()
+{
+ // InitWithCallback calls Release before AddRef so we need to hold a
+ // strong ref on 'this' since we fall through to this scope if it fails.
+ nsCOMPtr<nsITimerCallback> kungFuDeathGrip = this;
+ nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+ if (appShell) {
+ uint32_t currentLevel = 0;
+ appShell->GetEventloopNestingLevel(&currentLevel);
+ if (currentLevel > mInstanceOwner->GetLastEventloopNestingLevel()) {
+ if (!mTimer)
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mTimer) {
+ // Fire 100ms timer to try to tear down this plugin as quickly as
+ // possible once the nesting level comes back down.
+ nsresult rv = mTimer->InitWithCallback(this, 100,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ }
+ NS_ERROR("Failed to setup a timer to stop the plugin later (at a safe "
+ "time). Stopping the plugin now, this might crash.");
+ }
+ }
+
+ mTimer = nullptr;
+
+ static_cast<nsObjectLoadingContent*>(mContent.get())->
+ DoStopPlugin(mInstanceOwner, false, true);
+
+ return NS_OK;
+}
+
+// You can't take the address of bitfield members, so we have two separate
+// classes for these :-/
+
+// Sets a object's mInstantiating bit to false when destroyed
+class AutoSetInstantiatingToFalse {
+public:
+ explicit AutoSetInstantiatingToFalse(nsObjectLoadingContent* aContent)
+ : mContent(aContent) {}
+ ~AutoSetInstantiatingToFalse() { mContent->mInstantiating = false; }
+private:
+ nsObjectLoadingContent* mContent;
+};
+
+// Sets a object's mInstantiating bit to false when destroyed
+class AutoSetLoadingToFalse {
+public:
+ explicit AutoSetLoadingToFalse(nsObjectLoadingContent* aContent)
+ : mContent(aContent) {}
+ ~AutoSetLoadingToFalse() { mContent->mIsLoading = false; }
+private:
+ nsObjectLoadingContent* mContent;
+};
+
+///
+/// Helper functions
+///
+
+static bool
+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;
+ }
+
+ nsIIOService* ios = nsContentUtils::GetIOService();
+ 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);
+}
+
+static bool
+IsSupportedImage(const nsCString& aMimeType)
+{
+ return imgLoader::SupportImageWithMimeType(aMimeType.get());
+}
+
+static void
+GetExtensionFromURI(nsIURI* uri, nsCString& ext)
+{
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+ if (url) {
+ url->GetFileExtension(ext);
+ } else {
+ nsCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ // This means we could incorrectly think a plugin is not enabled for
+ // the URI when it is, but that's not so bad.
+ ext.Truncate();
+ return;
+ }
+
+ int32_t offset = spec.RFindChar('.');
+ if (offset != kNotFound) {
+ ext = Substring(spec, offset + 1, spec.Length());
+ }
+ }
+}
+
+/**
+ * Checks whether a plugin exists and is enabled for the extension
+ * in the given URI. The MIME type is returned in the mimeType out parameter.
+ */
+bool
+IsPluginEnabledByExtension(nsIURI* uri, nsCString& mimeType)
+{
+ nsAutoCString ext;
+ GetExtensionFromURI(uri, ext);
+
+ if (ext.IsEmpty()) {
+ return false;
+ }
+
+ // Disables any native PDF plugins, when internal PDF viewer is enabled.
+ if (ext.EqualsIgnoreCase("pdf") && nsContentUtils::IsPDFJSEnabled()) {
+ return false;
+ }
+
+ // Disables any native SWF plugins, when internal SWF player is enabled.
+ if (ext.EqualsIgnoreCase("swf") && nsContentUtils::IsSWFPlayerEnabled()) {
+ return false;
+ }
+
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+
+ if (!pluginHost) {
+ NS_NOTREACHED("No pluginhost");
+ return false;
+ }
+
+ return pluginHost->HavePluginForExtension(ext, mimeType);
+}
+
+///
+/// Member Functions
+///
+
+// Helper to queue a CheckPluginStopEvent for a OBJLC object
+void
+nsObjectLoadingContent::QueueCheckPluginStopEvent()
+{
+ nsCOMPtr<nsIRunnable> event = new CheckPluginStopEvent(this);
+ mPendingCheckPluginStopEvent = event;
+
+ NS_DispatchToCurrentThread(event);
+}
+
+// Tedious syntax to create a plugin stream listener with checks and put it in
+// mFinalListener
+bool
+nsObjectLoadingContent::MakePluginListener()
+{
+ if (!mInstanceOwner) {
+ NS_NOTREACHED("expecting a spawned plugin");
+ return false;
+ }
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ if (!pluginHost) {
+ NS_NOTREACHED("No pluginHost");
+ return false;
+ }
+ NS_ASSERTION(!mFinalListener, "overwriting a final listener");
+ nsresult rv;
+ RefPtr<nsNPAPIPluginInstance> inst;
+ nsCOMPtr<nsIStreamListener> finalListener;
+ rv = mInstanceOwner->GetInstance(getter_AddRefs(inst));
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = pluginHost->NewPluginStreamListener(mURI, inst,
+ getter_AddRefs(finalListener));
+ NS_ENSURE_SUCCESS(rv, false);
+ mFinalListener = finalListener;
+ return true;
+}
+
+
+bool
+nsObjectLoadingContent::IsSupportedDocument(const nsCString& aMimeType)
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+
+ nsCOMPtr<nsIWebNavigationInfo> info(
+ do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID));
+ if (!info) {
+ return false;
+ }
+
+ nsCOMPtr<nsIWebNavigation> webNav;
+ nsIDocument* currentDoc = thisContent->GetComposedDoc();
+ if (currentDoc) {
+ webNav = do_GetInterface(currentDoc->GetWindow());
+ }
+
+ uint32_t supported;
+ nsresult rv = info->IsTypeSupported(aMimeType, webNav, &supported);
+
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (supported != nsIWebNavigationInfo::UNSUPPORTED) {
+ // Don't want to support plugins as documents
+ return supported != nsIWebNavigationInfo::PLUGIN;
+ }
+
+ // 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;
+}
+
+nsresult
+nsObjectLoadingContent::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
+ aCompileEventHandlers);
+
+ if (aDocument) {
+ return aDocument->AddPlugin(this);
+ }
+ return NS_OK;
+}
+
+void
+nsObjectLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
+ MOZ_ASSERT(thisContent);
+ nsIDocument* ownerDoc = thisContent->OwnerDoc();
+ ownerDoc->RemovePlugin(this);
+
+ if (mType == eType_Plugin && (mInstanceOwner || mInstantiating)) {
+ // we'll let the plugin continue to run at least until we get back to
+ // the event loop. If we get back to the event loop and the node
+ // has still not been added back to the document then we tear down the
+ // plugin
+ QueueCheckPluginStopEvent();
+ } else 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();
+ }
+ nsIDocument* doc = thisContent->GetComposedDoc();
+ if (doc && doc->IsActive()) {
+ nsCOMPtr<nsIRunnable> ev = new nsSimplePluginEvent(doc,
+ NS_LITERAL_STRING("PluginRemoved"));
+ NS_DispatchToCurrentThread(ev);
+ }
+}
+
+nsObjectLoadingContent::nsObjectLoadingContent()
+ : mType(eType_Loading)
+ , mFallbackType(eFallbackAlternate)
+ , mRunID(0)
+ , mHasRunID(false)
+ , mChannelLoaded(false)
+ , mInstantiating(false)
+ , mNetworkCreated(true)
+ , mActivated(false)
+ , mContentBlockingEnabled(false)
+ , mIsStopping(false)
+ , mIsLoading(false)
+ , mScriptRequested(false)
+ , mRewrittenYoutubeEmbed(false)
+ , mPreferFallback(false)
+ , mPreferFallbackKnown(false) {}
+
+nsObjectLoadingContent::~nsObjectLoadingContent()
+{
+ // Should have been unbound from the tree at this point, and
+ // CheckPluginStopEvent keeps us alive
+ if (mFrameLoader) {
+ NS_NOTREACHED("Should not be tearing down frame loaders at this point");
+ mFrameLoader->Destroy();
+ }
+ if (mInstanceOwner || mInstantiating) {
+ // This is especially bad as delayed stop will try to hold on to this
+ // object...
+ NS_NOTREACHED("Should not be tearing down a plugin at this point!");
+ StopPluginInstance();
+ }
+ DestroyImageLoadingContent();
+}
+
+nsresult
+nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
+{
+ if (mInstanceOwner || mType != eType_Plugin || (mIsLoading != aIsLoading) ||
+ mInstantiating) {
+ // If we hit this assertion it's probably because LoadObject re-entered :(
+ //
+ // XXX(johns): This hackiness will go away in bug 767635
+ NS_ASSERTION(mIsLoading || !aIsLoading,
+ "aIsLoading should only be true inside LoadObject");
+ return NS_OK;
+ }
+
+ mInstantiating = true;
+ AutoSetInstantiatingToFalse autoInstantiating(this);
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent *>(this));
+
+ nsCOMPtr<nsIDocument> doc = thisContent->GetComposedDoc();
+ if (!doc || !InActiveDocument(thisContent)) {
+ NS_ERROR("Shouldn't be calling "
+ "InstantiatePluginInstance without an active document");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Instantiating an instance can result in script execution, which
+ // can destroy this DOM object. Don't allow that for the scope
+ // of this method.
+ nsCOMPtr<nsIObjectLoadingContent> kungFuDeathGrip = this;
+
+ // Flush layout so that the frame is created if possible and the plugin is
+ // initialized with the latest information.
+ doc->FlushPendingNotifications(Flush_Layout);
+ // Flushing layout may have re-entered and loaded something underneath us
+ NS_ENSURE_TRUE(mInstantiating, NS_OK);
+
+ if (!thisContent->GetPrimaryFrame()) {
+ LOG(("OBJLC [%p]: Not instantiating plugin with no frame", this));
+ return NS_OK;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+
+ if (!pluginHost) {
+ NS_NOTREACHED("No pluginhost");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If you add early return(s), be sure to balance this call to
+ // appShell->SuspendNative() with additional call(s) to
+ // appShell->ReturnNative().
+ nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+ if (appShell) {
+ appShell->SuspendNative();
+ }
+
+ RefPtr<nsPluginInstanceOwner> newOwner;
+ rv = pluginHost->InstantiatePluginInstance(mContentType,
+ mURI.get(), this,
+ getter_AddRefs(newOwner));
+
+ // XXX(johns): We don't suspend native inside stopping plugins...
+ if (appShell) {
+ appShell->ResumeNative();
+ }
+
+ if (!mInstantiating || NS_FAILED(rv)) {
+ LOG(("OBJLC [%p]: Plugin instantiation failed or re-entered, "
+ "killing old instance", this));
+ // XXX(johns): This needs to be de-duplicated with DoStopPlugin, but we
+ // don't want to touch the protochain or delayed stop.
+ // (Bug 767635)
+ if (newOwner) {
+ RefPtr<nsNPAPIPluginInstance> inst;
+ newOwner->GetInstance(getter_AddRefs(inst));
+ newOwner->SetFrame(nullptr);
+ if (inst) {
+ pluginHost->StopPluginInstance(inst);
+ }
+ newOwner->Destroy();
+ }
+ return NS_OK;
+ }
+
+ mInstanceOwner = newOwner;
+
+ if (mInstanceOwner) {
+ RefPtr<nsNPAPIPluginInstance> inst;
+ rv = mInstanceOwner->GetInstance(getter_AddRefs(inst));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = inst->GetRunID(&mRunID);
+ mHasRunID = NS_SUCCEEDED(rv);
+ }
+
+ // Ensure the frame did not change during instantiation re-entry (common).
+ // HasNewFrame would not have mInstanceOwner yet, so the new frame would be
+ // dangling. (Bug 854082)
+ nsIFrame* frame = thisContent->GetPrimaryFrame();
+ if (frame && mInstanceOwner) {
+ mInstanceOwner->SetFrame(static_cast<nsPluginFrame*>(frame));
+
+ // Bug 870216 - Adobe Reader renders with incorrect dimensions until it gets
+ // a second SetWindow call. This is otherwise redundant.
+ mInstanceOwner->CallSetWindow();
+ }
+
+ // Set up scripting interfaces.
+ NotifyContentObjectWrapper();
+
+ RefPtr<nsNPAPIPluginInstance> pluginInstance;
+ GetPluginInstance(getter_AddRefs(pluginInstance));
+ if (pluginInstance) {
+ nsCOMPtr<nsIPluginTag> pluginTag;
+ pluginHost->GetPluginTagForInstance(pluginInstance,
+ getter_AddRefs(pluginTag));
+
+ nsCOMPtr<nsIBlocklistService> blocklist =
+ do_GetService("@mozilla.org/extensions/blocklist;1");
+ if (blocklist) {
+ uint32_t blockState = nsIBlocklistService::STATE_NOT_BLOCKED;
+ blocklist->GetPluginBlocklistState(pluginTag, EmptyString(),
+ EmptyString(), &blockState);
+ if (blockState == nsIBlocklistService::STATE_OUTDATED) {
+ // Fire plugin outdated event if necessary
+ LOG(("OBJLC [%p]: Dispatching plugin outdated event for content %p\n",
+ this));
+ nsCOMPtr<nsIRunnable> ev = new nsSimplePluginEvent(thisContent,
+ NS_LITERAL_STRING("PluginOutdated"));
+ nsresult rv = NS_DispatchToCurrentThread(ev);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to dispatch nsSimplePluginEvent");
+ }
+ }
+ }
+
+ // If we have a URI but didn't open a channel yet (eAllowPluginSkipChannel)
+ // or we did load with a channel but are re-instantiating, re-open the
+ // channel. OpenChannel() performs security checks, and this plugin has
+ // already passed content policy in LoadObject.
+ if ((mURI && !mChannelLoaded) || (mChannelLoaded && !aIsLoading)) {
+ NS_ASSERTION(!mChannel, "should not have an existing channel here");
+ // We intentionally ignore errors here, leaving it up to the plugin to
+ // deal with not having an initial stream.
+ OpenChannel();
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> ev = \
+ new nsSimplePluginEvent(thisContent,
+ doc,
+ NS_LITERAL_STRING("PluginInstantiated"));
+ NS_DispatchToCurrentThread(ev);
+
+#ifdef XP_MACOSX
+ HTMLObjectElement::HandlePluginInstantiated(thisContent->AsElement());
+#endif
+
+ return NS_OK;
+}
+
+void
+nsObjectLoadingContent::GetPluginAttributes(nsTArray<MozPluginParameter>& aAttributes)
+{
+ aAttributes = mCachedAttributes;
+}
+
+void
+nsObjectLoadingContent::GetPluginParameters(nsTArray<MozPluginParameter>& aParameters)
+{
+ aParameters = mCachedParameters;
+}
+
+void
+nsObjectLoadingContent::GetNestedParams(nsTArray<MozPluginParameter>& aParams,
+ bool aIgnoreCodebase)
+{
+ nsCOMPtr<nsIDOMElement> domElement =
+ do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
+
+ nsCOMPtr<nsIDOMHTMLCollection> allParams;
+ NS_NAMED_LITERAL_STRING(xhtml_ns, "http://www.w3.org/1999/xhtml");
+ domElement->GetElementsByTagNameNS(xhtml_ns,
+ NS_LITERAL_STRING("param"), getter_AddRefs(allParams));
+
+ if (!allParams)
+ return;
+
+ uint32_t numAllParams;
+ allParams->GetLength(&numAllParams);
+ for (uint32_t i = 0; i < numAllParams; i++) {
+ nsCOMPtr<nsIDOMNode> pNode;
+ allParams->Item(i, getter_AddRefs(pNode));
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(pNode);
+
+ if (!element)
+ continue;
+
+ nsAutoString name;
+ element->GetAttribute(NS_LITERAL_STRING("name"), name);
+
+ if (name.IsEmpty())
+ continue;
+
+ nsCOMPtr<nsIDOMNode> parent;
+ nsCOMPtr<nsIDOMHTMLObjectElement> domObject;
+ nsCOMPtr<nsIDOMHTMLAppletElement> domApplet;
+ pNode->GetParentNode(getter_AddRefs(parent));
+ while (!(domObject || domApplet) && parent) {
+ domObject = do_QueryInterface(parent);
+ domApplet = do_QueryInterface(parent);
+ nsCOMPtr<nsIDOMNode> temp;
+ parent->GetParentNode(getter_AddRefs(temp));
+ parent = temp;
+ }
+
+ if (domApplet) {
+ parent = do_QueryInterface(domApplet);
+ } else if (domObject) {
+ parent = do_QueryInterface(domObject);
+ } else {
+ continue;
+ }
+
+ nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(domElement);
+ if (parent == domNode) {
+ MozPluginParameter param;
+ element->GetAttribute(NS_LITERAL_STRING("name"), param.mName);
+ element->GetAttribute(NS_LITERAL_STRING("value"), param.mValue);
+
+ param.mName.Trim(" \n\r\t\b", true, true, false);
+ param.mValue.Trim(" \n\r\t\b", true, true, false);
+
+ // ignore codebase param if it was already added in the attributes array.
+ if (aIgnoreCodebase && param.mName.EqualsIgnoreCase("codebase")) {
+ continue;
+ }
+
+ aParams.AppendElement(param);
+ }
+ }
+}
+
+nsresult
+nsObjectLoadingContent::BuildParametersArray()
+{
+ if (mCachedAttributes.Length() || mCachedParameters.Length()) {
+ MOZ_ASSERT(false, "Parameters array should be empty.");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ for (uint32_t i = 0; i != content->GetAttrCount(); i += 1) {
+ MozPluginParameter param;
+ const nsAttrName* attrName = content->GetAttrNameAt(i);
+ nsIAtom* atom = attrName->LocalName();
+ content->GetAttr(attrName->NamespaceID(), atom, param.mValue);
+ atom->ToString(param.mName);
+ mCachedAttributes.AppendElement(param);
+ }
+
+ bool isJava = IsJavaMIME(mContentType);
+
+ nsCString codebase;
+ if (isJava) {
+ nsresult rv = mBaseURI->GetSpec(codebase);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAdoptingCString wmodeOverride = Preferences::GetCString("plugins.force.wmode");
+ for (uint32_t i = 0; i < mCachedAttributes.Length(); i++) {
+ if (!wmodeOverride.IsEmpty() && mCachedAttributes[i].mName.EqualsIgnoreCase("wmode")) {
+ CopyASCIItoUTF16(wmodeOverride, mCachedAttributes[i].mValue);
+ wmodeOverride.Truncate();
+ } else if (!codebase.IsEmpty() && mCachedAttributes[i].mName.EqualsIgnoreCase("codebase")) {
+ CopyASCIItoUTF16(codebase, mCachedAttributes[i].mValue);
+ codebase.Truncate();
+ }
+ }
+
+ if (!wmodeOverride.IsEmpty()) {
+ MozPluginParameter param;
+ param.mName = NS_LITERAL_STRING("wmode");
+ CopyASCIItoUTF16(wmodeOverride, param.mValue);
+ mCachedAttributes.AppendElement(param);
+ }
+
+ if (!codebase.IsEmpty()) {
+ MozPluginParameter param;
+ param.mName = NS_LITERAL_STRING("codebase");
+ CopyASCIItoUTF16(codebase, 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 (content->IsHTMLElement(nsGkAtoms::object) &&
+ !content->HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+ MozPluginParameter param;
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::data, param.mValue);
+ if (!param.mValue.IsEmpty()) {
+ param.mName = NS_LITERAL_STRING("SRC");
+ mCachedAttributes.AppendElement(param);
+ }
+ }
+
+ GetNestedParams(mCachedParameters, isJava);
+
+ return NS_OK;
+}
+
+void
+nsObjectLoadingContent::NotifyOwnerDocumentActivityChanged()
+{
+ // XXX(johns): We cannot touch plugins or run arbitrary script from this call,
+ // as nsDocument is in a non-reentrant state.
+
+ // If we have a plugin we want to queue an event to stop it unless we are
+ // moved into an active document before returning to the event loop.
+ if (mInstanceOwner || mInstantiating) {
+ QueueCheckPluginStopEvent();
+ }
+}
+
+// nsIRequestObserver
+NS_IMETHODIMP
+nsObjectLoadingContent::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ PROFILER_LABEL("nsObjectLoadingContent", "OnStartRequest",
+ js::ProfileEntry::Category::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;
+ }
+
+ // If we already switched to type plugin, this channel can just be passed to
+ // the final listener.
+ if (mType == eType_Plugin) {
+ if (!mInstanceOwner) {
+ // We drop mChannel when stopping plugins, so something is wrong
+ NS_NOTREACHED("Opened a channel in plugin mode, but don't have a plugin");
+ return NS_BINDING_ABORTED;
+ }
+ if (MakePluginListener()) {
+ return mFinalListener->OnStartRequest(aRequest, nullptr);
+ } else {
+ NS_NOTREACHED("Failed to create PluginStreamListener, aborting channel");
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ // Otherwise we should be state loading, and call LoadObject with the channel
+ if (mType != eType_Loading) {
+ NS_NOTREACHED("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;
+
+ 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 (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 = NS_LITERAL_STRING("Blocking ") +
+ NS_ConvertASCIItoUTF16(uri->GetSpecOrDefault().get()) +
+ NS_LITERAL_STRING(" since it was found on an internal Firefox blocklist.");
+ console->LogStringMessage(message.get());
+ }
+ Telemetry::Accumulate(Telemetry::PLUGIN_BLOCKED_FOR_STABILITY, 1);
+ mContentBlockingEnabled = true;
+ return NS_ERROR_FAILURE;
+ } else if (status == NS_ERROR_TRACKING_URI) {
+ 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,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ PROFILER_LABEL("nsObjectLoadingContent", "OnStopRequest",
+ js::ProfileEntry::Category::NETWORK);
+
+ // Handle object not loading error because source was a tracking URL.
+ // We make a note of this object node by including it in a dedicated
+ // array of blocked tracking nodes under its parent document.
+ if (aStatusCode == NS_ERROR_TRACKING_URI) {
+ nsCOMPtr<nsIContent> thisNode =
+ do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
+ if (thisNode && thisNode->IsInComposedDoc()) {
+ thisNode->GetComposedDoc()->AddBlockedTrackingNode(thisNode);
+ }
+ }
+
+ NS_ENSURE_TRUE(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), NS_ERROR_NOT_AVAILABLE);
+
+ 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, aContext, aStatusCode);
+ }
+
+ // Return value doesn't matter
+ return NS_OK;
+}
+
+
+// nsIStreamListener
+NS_IMETHODIMP
+nsObjectLoadingContent::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset, uint32_t aCount)
+{
+ NS_ENSURE_TRUE(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), NS_ERROR_NOT_AVAILABLE);
+
+ 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, aContext, aInputStream,
+ aOffset, aCount);
+ }
+
+ // We shouldn't have a connected channel with no final listener
+ NS_NOTREACHED("Got data for channel with no connected final listener");
+ mChannel = nullptr;
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+// nsIFrameLoaderOwner
+NS_IMETHODIMP
+nsObjectLoadingContent::GetFrameLoaderXPCOM(nsIFrameLoader** aFrameLoader)
+{
+ NS_IF_ADDREF(*aFrameLoader = mFrameLoader);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(already_AddRefed<nsFrameLoader>)
+nsObjectLoadingContent::GetFrameLoader()
+{
+ RefPtr<nsFrameLoader> loader = mFrameLoader;
+ return loader.forget();
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetParentApplication(mozIApplication** aApplication)
+{
+ if (!aApplication) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aApplication = nullptr;
+ return NS_OK;
+}
+
+void
+nsObjectLoadingContent::PresetOpenerWindow(mozIDOMWindowProxy* aWindow, mozilla::ErrorResult& aRv)
+{
+ aRv.Throw(NS_ERROR_FAILURE);
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::SetIsPrerendered()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+nsObjectLoadingContent::InternalSetFrameLoader(nsIFrameLoader* aNewFrameLoader)
+{
+ MOZ_CRASH("You shouldn't be calling this function, it doesn't make any sense on this type.");
+}
+
+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::HasNewFrame(nsIObjectFrame* aFrame)
+{
+ if (mType != eType_Plugin) {
+ return NS_OK;
+ }
+
+ if (!aFrame) {
+ // Lost our frame. If we aren't going to be getting a new frame, e.g. we've
+ // become display:none, we'll want to stop the plugin. Queue a
+ // CheckPluginStopEvent
+ if (mInstanceOwner || mInstantiating) {
+ if (mInstanceOwner) {
+ mInstanceOwner->SetFrame(nullptr);
+ }
+ QueueCheckPluginStopEvent();
+ }
+ return NS_OK;
+ }
+
+ // Have a new frame
+
+ if (!mInstanceOwner) {
+ // We are successfully setup as type plugin, but have not spawned an
+ // instance due to a lack of a frame.
+ AsyncStartPluginInstance();
+ return NS_OK;
+ }
+
+ // Otherwise, we're just changing frames
+ // Set up relationship between instance owner and frame.
+ nsPluginFrame *objFrame = static_cast<nsPluginFrame*>(aFrame);
+ mInstanceOwner->SetFrame(objFrame);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetPluginInstance(nsNPAPIPluginInstance** aInstance)
+{
+ *aInstance = nullptr;
+
+ if (!mInstanceOwner) {
+ return NS_OK;
+ }
+
+ return mInstanceOwner->GetInstance(aInstance);
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetContentTypeForMIMEType(const nsACString& aMIMEType,
+ uint32_t* aType)
+{
+ *aType = GetTypeOfContent(PromiseFlatCString(aMIMEType));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetBaseURI(nsIURI **aResult)
+{
+ NS_IF_ADDREF(*aResult = mBaseURI);
+ 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() {}
+ 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;
+ }
+ 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;
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+// <public>
+EventStates
+nsObjectLoadingContent::ObjectState() const
+{
+ switch (mType) {
+ case eType_Loading:
+ return NS_EVENT_STATE_LOADING;
+ case eType_Image:
+ return ImageState();
+ case eType_Plugin:
+ 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.
+ return EventStates();
+ case eType_Null:
+ switch (mFallbackType) {
+ case eFallbackSuppressed:
+ return NS_EVENT_STATE_SUPPRESSED;
+ case eFallbackUserDisabled:
+ return NS_EVENT_STATE_USERDISABLED;
+ case eFallbackClickToPlay:
+ return NS_EVENT_STATE_TYPE_CLICK_TO_PLAY;
+ case eFallbackDisabled:
+ return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_DISABLED;
+ case eFallbackBlocklisted:
+ return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_BLOCKED;
+ case eFallbackCrashed:
+ return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_CRASHED;
+ case eFallbackUnsupported:
+ case eFallbackOutdated:
+ case eFallbackAlternate:
+ return NS_EVENT_STATE_BROKEN;
+ case eFallbackVulnerableUpdatable:
+ return NS_EVENT_STATE_VULNERABLE_UPDATABLE;
+ case eFallbackVulnerableNoUpdate:
+ return NS_EVENT_STATE_VULNERABLE_NO_UPDATE;
+ }
+ }
+ NS_NOTREACHED("unknown type?");
+ return NS_EVENT_STATE_LOADING;
+}
+
+// Returns false if mBaseURI is not acceptable for java applets.
+bool
+nsObjectLoadingContent::CheckJavaCodebase()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsINetUtil> netutil = do_GetNetUtil();
+ NS_ASSERTION(thisContent && secMan && netutil, "expected interfaces");
+
+
+ // Note that mBaseURI is this tag's requested base URI, not the codebase of
+ // the document for security purposes
+ nsresult rv = secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(),
+ mBaseURI, 0);
+ if (NS_FAILED(rv)) {
+ LOG(("OBJLC [%p]: Java codebase check failed", this));
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> principalBaseURI;
+ rv = thisContent->NodePrincipal()->GetURI(getter_AddRefs(principalBaseURI));
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("Failed to URI from node principal?");
+ return false;
+ }
+ // We currently allow java's codebase to be non-same-origin, with
+ // the exception of URIs that represent local files
+ if (NS_URIIsLocalFile(mBaseURI) &&
+ nsScriptSecurityManager::GetStrictFileOriginPolicy() &&
+ !NS_RelaxStrictFileOriginPolicy(mBaseURI, principalBaseURI, true)) {
+ LOG(("OBJLC [%p]: Java failed RelaxStrictFileOriginPolicy for file URI",
+ this));
+ return false;
+ }
+
+ return true;
+}
+
+void
+nsObjectLoadingContent::MaybeRewriteYoutubeEmbed(nsIURI* aURI, nsIURI* aBaseURI, nsIURI** aOutURI)
+{
+ 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")) {
+ 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->GetPath(path);
+ if (!StringBeginsWith(path, NS_LITERAL_CSTRING("/v/"))) {
+ return;
+ }
+
+ // See if requester is planning on using the JS API.
+ nsAutoCString uri;
+ nsresult rv = aURI->GetSpec(uri);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (uri.Find("enablejsapi=1", true, 0, -1) != kNotFound) {
+ Telemetry::Accumulate(Telemetry::YOUTUBE_NONREWRITABLE_EMBED_SEEN, 1);
+ 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've made it this far, we've got a rewritable embed. Log it in
+ // telemetry.
+ Telemetry::Accumulate(Telemetry::YOUTUBE_REWRITABLE_EMBED_SEEN, 1);
+
+ // 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(NS_LITERAL_CSTRING("/v/"),
+ NS_LITERAL_CSTRING("/embed/"));
+ nsAutoString utf16URI = NS_ConvertUTF8toUTF16(uri);
+ rv = nsContentUtils::NewURIWithDocumentCharset(aOutURI,
+ utf16URI,
+ thisContent->OwnerDoc(),
+ aBaseURI);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ const char16_t* params[] = { utf16OldURI.get(), utf16URI.get() };
+ 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,
+ NS_LITERAL_CSTRING("Plugins"),
+ thisContent->OwnerDoc(),
+ nsContentUtils::eDOM_PROPERTIES,
+ msgName,
+ params, ArrayLength(params));
+}
+
+bool
+nsObjectLoadingContent::CheckLoadPolicy(int16_t *aContentPolicy)
+{
+ if (!aContentPolicy || !mURI) {
+ NS_NOTREACHED("Doing it wrong");
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "Must be an instance of content");
+
+ nsIDocument* doc = thisContent->OwnerDoc();
+
+ nsContentPolicyType contentPolicyType = GetContentPolicyType();
+
+ *aContentPolicy = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(contentPolicyType,
+ mURI,
+ doc->NodePrincipal(),
+ thisContent,
+ mContentType,
+ nullptr, //extra
+ aContentPolicy,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+ 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) {
+ NS_NOTREACHED("Null out variable");
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "Must be an instance of content");
+
+ nsIDocument* doc = thisContent->OwnerDoc();
+
+ int32_t objectType;
+ switch (mType) {
+ case eType_Image:
+ objectType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+ break;
+ case eType_Document:
+ objectType = nsIContentPolicy::TYPE_DOCUMENT;
+ break;
+ case eType_Plugin:
+ objectType = GetContentPolicyType();
+ break;
+ default:
+ NS_NOTREACHED("Calling checkProcessPolicy with a unloadable type");
+ return false;
+ }
+
+ *aContentPolicy = nsIContentPolicy::ACCEPT;
+ nsresult rv =
+ NS_CheckContentProcessPolicy(objectType,
+ mURI ? mURI : mBaseURI,
+ doc->NodePrincipal(),
+ static_cast<nsIImageLoadingContent*>(this),
+ mContentType,
+ nullptr, //extra
+ aContentPolicy,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+ 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(bool aJavaURI)
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "Must be an instance of content");
+
+ 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;
+ bool isJava = false;
+ // 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 (aJavaURI || thisContent->NodeInfo()->Equals(nsGkAtoms::applet)) {
+ nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME);
+ newMime = javaMIME;
+ NS_ASSERTION(IsJavaMIME(newMime),
+ "plugin.mime.java should be recognized as java");
+ isJava = true;
+ } else {
+ nsAutoString rawTypeAttr;
+ thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, rawTypeAttr);
+ if (!rawTypeAttr.IsEmpty()) {
+ typeAttr = rawTypeAttr;
+ CopyUTF16toUTF8(rawTypeAttr, newMime);
+ isJava = IsJavaMIME(newMime);
+ }
+ }
+
+ ///
+ /// classID
+ ///
+
+ if (caps & eSupportClassID) {
+ nsAutoString classIDAttr;
+ thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::classid, classIDAttr);
+ if (!classIDAttr.IsEmpty()) {
+ // Our classid support is limited to 'java:' ids
+ nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME);
+ NS_ASSERTION(IsJavaMIME(javaMIME),
+ "plugin.mime.java should be recognized as java");
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ if (StringBeginsWith(classIDAttr, NS_LITERAL_STRING("java:")) &&
+ pluginHost &&
+ pluginHost->HavePluginForType(javaMIME)) {
+ newMime = javaMIME;
+ isJava = true;
+ } else {
+ // XXX(johns): Our de-facto behavior since forever was to refuse to load
+ // Objects who don't have a classid we support, regardless of other type
+ // or uri info leads to a valid plugin.
+ newMime.Truncate();
+ stateInvalid = true;
+ }
+ }
+ }
+
+ ///
+ /// Codebase
+ ///
+
+ nsAutoString codebaseStr;
+ nsCOMPtr<nsIURI> docBaseURI = thisContent->GetBaseURI();
+ bool hasCodebase = thisContent->HasAttr(kNameSpaceID_None, nsGkAtoms::codebase);
+ if (hasCodebase)
+ thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::codebase, codebaseStr);
+
+
+ // Java wants the codebase attribute even if it occurs in <param> tags
+ if (isJava) {
+ // Find all <param> tags that are nested beneath us, but not beneath another
+ // object/applet tag.
+ nsTArray<MozPluginParameter> params;
+ GetNestedParams(params, false);
+ for (uint32_t i = 0; i < params.Length(); i++) {
+ if (params[i].mName.EqualsIgnoreCase("codebase")) {
+ hasCodebase = true;
+ codebaseStr = params[i].mValue;
+ }
+ }
+ }
+
+ if (isJava && hasCodebase && codebaseStr.IsEmpty()) {
+ // Java treats codebase="" as "/"
+ codebaseStr.Assign('/');
+ // XXX(johns): This doesn't cover the case of "https:" which java would
+ // interpret as "https:///" but we interpret as this document's
+ // URI but with a changed scheme.
+ } else if (isJava && !hasCodebase) {
+ // Java expects a directory as the codebase, or else it will construct
+ // relative URIs incorrectly :(
+ codebaseStr.Assign('.');
+ }
+
+ if (!codebaseStr.IsEmpty()) {
+ rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(newBaseURI),
+ codebaseStr,
+ thisContent->OwnerDoc(),
+ docBaseURI);
+ if (NS_SUCCEEDED(rv)) {
+ NS_TryToSetImmutable(newBaseURI);
+ } else {
+ // 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;
+ }
+
+ ///
+ /// URI
+ ///
+
+ nsAutoString uriStr;
+ // Different elements keep this in various locations
+ if (isJava) {
+ // Applet tags and embed/object with explicit java MIMEs have src/data
+ // attributes that are not meant to be parsed as URIs or opened by the
+ // browser -- act as if they are null. (Setting these attributes triggers a
+ // force-load, so tracking the old value to determine if they have changed
+ // is not necessary.)
+ } else if (thisContent->NodeInfo()->Equals(nsGkAtoms::object)) {
+ thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::data, uriStr);
+ } else if (thisContent->NodeInfo()->Equals(nsGkAtoms::embed)) {
+ thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, uriStr);
+ } else {
+ // Applet tags should always have a java MIME type at this point
+ NS_NOTREACHED("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,
+ thisContent->OwnerDoc(),
+ newBaseURI);
+ nsCOMPtr<nsIURI> rewrittenURI;
+ MaybeRewriteYoutubeEmbed(newURI,
+ newBaseURI,
+ getter_AddRefs(rewrittenURI));
+ if (rewrittenURI) {
+ newURI = rewrittenURI;
+ mRewrittenYoutubeEmbed = true;
+ newMime = NS_LITERAL_CSTRING("text/html");
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ NS_TryToSetImmutable(newURI);
+ } else {
+ stateInvalid = true;
+ }
+ }
+
+ // For eAllowPluginSkipChannel tags, if we have a non-plugin type, but can get
+ // a plugin type from the extension, prefer that to falling back to a channel.
+ if (GetTypeOfContent(newMime) != eType_Plugin && newURI &&
+ (caps & eAllowPluginSkipChannel) &&
+ IsPluginEnabledByExtension(newURI, newMime)) {
+ LOG(("OBJLC [%p]: Using extension as type hint (%s)", this, newMime.get()));
+ if (!isJava && IsJavaMIME(newMime)) {
+ return UpdateObjectParameters(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;
+
+ if (newChannel && mChannel) {
+ nsCString channelType;
+ rv = mChannel->GetContentType(channelType);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("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)) {
+ NS_NOTREACHED("NS_GetFinalChannelURI failure");
+ stateInvalid = true;
+ }
+
+ ObjectType typeHint = newMime.IsEmpty() ?
+ eType_Null : GetTypeOfContent(newMime);
+
+ //
+ // In order of preference:
+ //
+ // 1) Perform typemustmatch check.
+ // If check is sucessful use type without further checks.
+ // If check is unsuccessful set stateInvalid to true
+ // 2) Use our type hint if it matches a plugin
+ // 3) If we have eAllowPluginSkipChannel, use the uri file extension if
+ // it matches a plugin
+ // 4) If the channel returns a binary stream type:
+ // 4a) If we have a type non-null non-document type hint, use that
+ // 4b) If the uri file extension matches a plugin type, use that
+ // 5) Use the channel type
+
+ bool overrideChannelType = false;
+ if (thisContent->HasAttr(kNameSpaceID_None, nsGkAtoms::typemustmatch)) {
+ if (!typeAttr.LowerCaseEqualsASCII(channelType.get())) {
+ stateInvalid = true;
+ }
+ } else if (typeHint == eType_Plugin) {
+ LOG(("OBJLC [%p]: Using plugin type hint in favor of any channel type",
+ this));
+ overrideChannelType = true;
+ } else if ((caps & eAllowPluginSkipChannel) &&
+ IsPluginEnabledByExtension(newURI, newMime)) {
+ LOG(("OBJLC [%p]: Using extension as type hint for "
+ "eAllowPluginSkipChannel tag (%s)", this, newMime.get()));
+ overrideChannelType = true;
+ } else if (binaryChannelType &&
+ typeHint != eType_Null && typeHint != eType_Document) {
+ LOG(("OBJLC [%p]: Using type hint in favor of binary channel type",
+ this));
+ overrideChannelType = true;
+ } else if (binaryChannelType &&
+ IsPluginEnabledByExtension(newURI, newMime)) {
+ LOG(("OBJLC [%p]: Using extension as type hint for binary channel (%s)",
+ this, newMime.get()));
+ 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;
+ if (IsJavaMIME(newMime)) {
+ // Java does not load with a channel, and being java retroactively
+ // changes how we may have interpreted the codebase to construct this
+ // URI above. Because the behavior here is more or less undefined, play
+ // it safe and reject the load.
+ LOG(("OBJLC [%p]: Refusing to load with channel with java MIME",
+ this));
+ stateInvalid = true;
+ }
+ }
+ } 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)
+ //
+
+ if (stateInvalid) {
+ newType = eType_Null;
+ newMime.Truncate();
+ } else if (newChannel) {
+ // If newChannel is set above, we considered it in setting newMime
+ newType = GetTypeOfContent(newMime);
+ LOG(("OBJLC [%p]: Using channel type", this));
+ } else if (((caps & eAllowPluginSkipChannel) || !newURI) &&
+ GetTypeOfContent(newMime) == eType_Plugin) {
+ newType = eType_Plugin;
+ LOG(("OBJLC [%p]: Plugin type with no URI, skipping channel load", this));
+ } else if (newURI) {
+ // We could potentially load this if we opened a channel on mURI, indicate
+ // This by leaving type as loading
+ newType = eType_Loading;
+ } else {
+ // Unloadable - no URI, and no plugin type. Non-plugin types (images,
+ // documents) always load with a channel.
+ newType = eType_Null;
+ }
+
+ ///
+ /// 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;
+ 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)) {
+ if (isJava) {
+ // Java bases its class loading on the base URI, so we consider the state
+ // to have changed if this changes. If the object is using a relative URI,
+ // mURI will have changed below regardless
+ retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
+ }
+ 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.
+ NS_NOTREACHED("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");
+ nsIDocument* doc = thisContent->OwnerDoc();
+ nsresult rv = NS_OK;
+
+ // Sanity check
+ if (!InActiveDocument(thisContent)) {
+ NS_NOTREACHED("LoadObject called while not bound to an active document");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // 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() || doc->IsLoadedAsData()) {
+ 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();
+ EventStates 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));
+
+ // Setup fallback info. We may also change type to fallback below in case of
+ // sanity/OOM/etc. errors. We default to showing alternate content
+ // NOTE LoadFallback can override this in some cases
+ FallbackType fallbackType = eFallbackAlternate;
+
+ // mType can differ with GetTypeOfContent(mContentType) if we support this
+ // type, but the parameters are invalid e.g. a embed tag with type "image/png"
+ // but no URI -- don't show a plugin error or unknown type error in that case.
+ if (mType == eType_Null && GetTypeOfContent(mContentType) == eType_Null) {
+ fallbackType = eFallbackUnsupported;
+ }
+
+ // Explicit user activation should reset if the object changes content types
+ if (mActivated && (stateChange & eParamContentTypeChanged)) {
+ LOG(("OBJLC [%p]: Content type changed, clearing activation state", this));
+ mActivated = false;
+ }
+
+ // 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 subseqent 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
+ NS_NOTREACHED("Loading with a channel, but state doesn't make sense");
+ return NS_OK;
+ }
+
+ //
+ // Security checks
+ //
+
+ if (mType != eType_Null) {
+ bool allowLoad = true;
+ if (IsJavaMIME(mContentType)) {
+ allowLoad = CheckJavaCodebase();
+ }
+ 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 asyncOpen2() 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 fallback and set disabled/suppressed if applicable
+ if (!allowLoad) {
+ LOG(("OBJLC [%p]: Load denied by policy", this));
+ mType = eType_Null;
+ if (contentPolicy == nsIContentPolicy::REJECT_TYPE) {
+ // XXX(johns) This is assuming that we were rejected by
+ // nsContentBlocker, which rejects by type if permissions
+ // reject plugins
+ fallbackType = eFallbackUserDisabled;
+ } else {
+ fallbackType = eFallbackSuppressed;
+ }
+ }
+ }
+
+ // 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.
+ bool isViewSource = false;
+ rv = tempURI->SchemeIs("view-source", &isViewSource);
+ if (NS_FAILED(rv) || isViewSource) {
+ 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 no candidates for content blocking,
+ // as well as invalid plugins (they will not have the mContentType set).
+ if ((mType == eType_Null || mType == eType_Plugin) && ShouldBlockContent()) {
+ LOG(("OBJLC [%p]: Enable content blocking", this));
+ mType = eType_Loading;
+ }
+
+ // If we're a plugin but shouldn't start yet, load fallback with
+ // reason click-to-play instead. Items resolved as Image/Document
+ // will not be checked for previews, as well as invalid plugins
+ // (they will not have the mContentType set).
+ FallbackType clickToPlayReason;
+ if (!mActivated && (mType == eType_Null || mType == eType_Plugin) &&
+ !ShouldPlay(clickToPlayReason, false)) {
+ LOG(("OBJLC [%p]: Marking plugin as click-to-play", this));
+ mType = eType_Null;
+ fallbackType = clickToPlayReason;
+ }
+
+ if (!mActivated && mType == eType_Plugin) {
+ // Object passed ShouldPlay, so it should be considered
+ // activated until it changes content type
+ LOG(("OBJLC [%p]: Object implicitly activated", this));
+ mActivated = true;
+ }
+
+ // Sanity check: We shouldn't have any loaded resources, pending events, or
+ // a final listener at this point
+ if (mFrameLoader || mPendingInstantiateEvent || mInstanceOwner ||
+ mPendingCheckPluginStopEvent || mFinalListener)
+ {
+ NS_NOTREACHED("Trying to load new plugin with existing content");
+ rv = NS_ERROR_UNEXPECTED;
+ return NS_OK;
+ }
+
+ // More sanity-checking:
+ // If mChannel is set, mChannelLoaded should be set, and vice-versa
+ if (mType != eType_Null && !!mChannel != mChannelLoaded) {
+ NS_NOTREACHED("Trying to load with bad channel state");
+ rv = NS_ERROR_UNEXPECTED;
+ return NS_OK;
+ }
+
+ ///
+ /// Attempt to load new type
+ ///
+
+
+ // Cache the current attributes and parameters.
+ if (mType == eType_Plugin || 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;
+ // If we decide to synchronously spawn a plugin, we do it after firing
+ // notifications to avoid re-entry causing notifications to fire out of order.
+ bool doSpawnPlugin = false;
+ 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.
+ NS_NOTREACHED("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_Plugin:
+ {
+ if (mChannel) {
+ // Force a sync state change now, we need the frame created
+ NotifyStateChanged(oldType, oldState, true, aNotify);
+ oldType = mType;
+ oldState = ObjectState();
+
+ if (!thisContent->GetPrimaryFrame()) {
+ // We're un-rendered, and can't instantiate a plugin. HasNewFrame will
+ // re-start us when we can proceed.
+ LOG(("OBJLC [%p]: Aborting load - plugin-type, but no frame", this));
+ CloseChannel();
+ break;
+ }
+
+ // We'll handle this below
+ doSpawnPlugin = true;
+ } else {
+ rv = AsyncStartPluginInstance();
+ }
+ }
+ 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.
+ NS_NOTREACHED("Attempting to load a document without a channel");
+ mType = eType_Null;
+ break;
+ }
+
+ mFrameLoader = nsFrameLoader::Create(thisContent->AsElement(),
+ /* aOpener = */ nullptr,
+ mNetworkCreated);
+ if (!mFrameLoader) {
+ NS_NOTREACHED("nsFrameLoader::Create failed");
+ mType = eType_Null;
+ break;
+ }
+
+ rv = mFrameLoader->CheckForRecursiveLoad(mURI);
+ if (NS_FAILED(rv)) {
+ LOG(("OBJLC [%p]: Aborting recursive load", this));
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ mType = eType_Null;
+ 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<nsIDocShell> docShell;
+ rv = mFrameLoader->GetDocShell(getter_AddRefs(docShell));
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("Could not get DocShell from mFrameLoader?");
+ mType = eType_Null;
+ break;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(docShell));
+ NS_ASSERTION(req, "Docshell must be an ifreq");
+
+ nsCOMPtr<nsIURILoader>
+ uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("Failed to get uriLoader service");
+ mType = eType_Null;
+ break;
+ }
+ rv = uriLoader->OpenChannel(mChannel, nsIURILoader::DONT_RETARGET, req,
+ getter_AddRefs(finalListener));
+ // finalListener will receive OnStartRequest below
+ }
+ 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 (%u)", this, rv));
+ }
+ break;
+ case eType_Null:
+ // Handled below, silence compiler warnings
+ break;
+ }
+
+ //
+ // Loaded, handle notifications and fallback
+ //
+ if (NS_FAILED(rv)) {
+ // If we failed in the loading hunk above, switch to fallback
+ LOG(("OBJLC [%p]: Loading failed, switching to fallback", this));
+ mType = eType_Null;
+ }
+
+ // If we didn't load anything, handle switching to fallback state
+ if (mType == eType_Null) {
+ LOG(("OBJLC [%p]: Loading fallback, type %u", this, fallbackType));
+ NS_ASSERTION(!mFrameLoader && !mInstanceOwner,
+ "switched to type null but also loaded something");
+
+ // Don't fire error events if we're falling back to click-to-play; instead
+ // pretend like this is a really slow-loading plug-in instead.
+ if (fallbackType != eFallbackClickToPlay) {
+ 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
+ doSpawnPlugin = false;
+ finalListener = nullptr;
+
+ // Don't notify, as LoadFallback doesn't know of our previous state
+ // (so really this is just setting mFallbackType)
+ LoadFallback(fallbackType, false);
+ }
+
+ // Notify of our final state
+ NotifyStateChanged(oldType, oldState, false, aNotify);
+ 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 (doSpawnPlugin) {
+ rv = InstantiatePluginInstance(true);
+ NS_ENSURE_TRUE(mIsLoading, NS_OK);
+ // Create the final listener if we're loading with a channel. We can't do
+ // this in the loading block above as it requires an instance.
+ if (aLoadingChannel && NS_SUCCEEDED(rv)) {
+ if (NS_SUCCEEDED(rv) && MakePluginListener()) {
+ rv = mFinalListener->OnStartRequest(mChannel, nullptr);
+ if (NS_FAILED(rv)) {
+ // Plugins can reject their initial stream, but continue to run.
+ CloseChannel();
+ NS_ENSURE_TRUE(mIsLoading, NS_OK);
+ rv = NS_OK;
+ }
+ }
+ }
+ } else if (finalListener) {
+ NS_ASSERTION(mType != eType_Null && mType != eType_Loading,
+ "We should not have a final listener with a non-loaded type");
+ mFinalListener = finalListener;
+ rv = finalListener->OnStartRequest(mChannel, nullptr);
+ }
+
+ if (NS_FAILED(rv) && mIsLoading) {
+ // Since we've already notified of our transition, we can just Unload and
+ // call LoadFallback (which will notify again)
+ mType = eType_Null;
+ UnloadObject(false);
+ NS_ENSURE_TRUE(mIsLoading, NS_OK);
+ CloseChannel();
+ LoadFallback(fallbackType, true);
+ }
+
+ 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->Cancel(NS_BINDING_ABORTED);
+ if (listenerGrip) {
+ // mFinalListener is only set by LoadObject after OnStartRequest, or
+ // by OnStartRequest in the case of late-opened plugin streams
+ listenerGrip->OnStopRequest(channelGrip, nullptr, NS_BINDING_ABORTED);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsObjectLoadingContent::OpenChannel()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+ nsIDocument* 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 isSandBoxed = doc->GetSandboxFlags() & SANDBOXED_ORIGIN;
+ bool inherit = nsContentUtils::ChannelShouldInheritPrincipal(thisContent->NodePrincipal(),
+ mURI,
+ true, // aInheritForAboutBlank
+ false); // aForceInherit
+ nsSecurityFlags securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+ if (inherit) {
+ securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+ if (isSandBoxed) {
+ securityFlags |= nsILoadInfo::SEC_SANDBOXED;
+ }
+
+ nsContentPolicyType contentPolicyType = GetContentPolicyType();
+
+ rv = NS_NewChannel(getter_AddRefs(chan),
+ mURI,
+ thisContent,
+ securityFlags,
+ contentPolicyType,
+ group, // aLoadGroup
+ shim, // aCallbacks
+ nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
+ nsIChannel::LOAD_CLASSIFY_URI |
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (inherit) {
+ nsCOMPtr<nsILoadInfo> loadinfo = chan->GetLoadInfo();
+ loadinfo->SetPrincipalToInherit(thisContent->NodePrincipal());
+ }
+
+ // Referrer
+ nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
+ if (httpChan) {
+ httpChan->SetReferrerWithPolicy(doc->GetDocumentURI(),
+ doc->GetReferrerPolicy());
+
+ // Set the initiator type
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChan));
+ if (timedChannel) {
+ timedChannel->SetInitiatorType(thisContent->LocalName());
+ }
+ }
+
+ nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(chan);
+ if (scriptChannel) {
+ // Allow execution against our context if the principals match
+ scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
+ }
+
+ // AsyncOpen2 can fail if a file does not exist.
+ rv = chan->AsyncOpen2(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::DestroyContent()
+{
+ if (mFrameLoader) {
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ }
+
+ if (mInstanceOwner || mInstantiating) {
+ QueueCheckPluginStopEvent();
+ }
+}
+
+/* static */
+void
+nsObjectLoadingContent::Traverse(nsObjectLoadingContent *tmp,
+ nsCycleCollectionTraversalCallback &cb)
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameLoader");
+ cb.NoteXPCOMChild(static_cast<nsIFrameLoader*>(tmp->mFrameLoader));
+}
+
+void
+nsObjectLoadingContent::UnloadObject(bool aResetState)
+{
+ // Don't notify in CancelImageRequests until we transition to a new loaded
+ // state
+ CancelImageRequests(false);
+ if (mFrameLoader) {
+ mFrameLoader->Destroy();
+ mFrameLoader = nullptr;
+ }
+
+ if (aResetState) {
+ if (mType != eType_Plugin) {
+ // This can re-enter when dealing with plugins, and StopPluginInstance
+ // will handle it
+ CloseChannel();
+ }
+ mChannelLoaded = false;
+ mType = eType_Loading;
+ mURI = mOriginalURI = mBaseURI = nullptr;
+ mContentType.Truncate();
+ mOriginalContentType.Truncate();
+ }
+
+ // InstantiatePluginInstance checks this after re-entrant calls and aborts if
+ // it was cleared from under it
+ mInstantiating = false;
+
+ mScriptRequested = false;
+
+ if (mIsStopping) {
+ // The protochain is normally thrown out after a plugin stops, but if we
+ // re-enter while stopping a plugin and try to load something new, we need
+ // to throw away the old protochain in the nested unload.
+ TeardownProtoChain();
+ mIsStopping = false;
+ }
+
+ mCachedAttributes.Clear();
+ mCachedParameters.Clear();
+
+ // This call should be last as it may re-enter
+ StopPluginInstance();
+}
+
+void
+nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType,
+ EventStates aOldState,
+ bool aSync,
+ bool aNotify)
+{
+ LOG(("OBJLC [%p]: Notifying about state change: (%u, %llx) -> (%u, %llx)"
+ " (sync %i, notify %i)", this, aOldType, aOldState.GetInternalValue(),
+ mType, ObjectState().GetInternalValue(), aSync, aNotify));
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+
+ NS_ASSERTION(thisContent->IsElement(), "Not 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.
+ thisContent->AsElement()->UpdateState(false);
+
+ if (!aNotify) {
+ // We're done here
+ return;
+ }
+
+ nsIDocument* doc = thisContent->GetComposedDoc();
+ if (!doc) {
+ return; // Nothing to do
+ }
+
+ EventStates newState = ObjectState();
+
+ if (newState != aOldState) {
+ NS_ASSERTION(thisContent->IsInComposedDoc(), "Something is confused");
+ // This will trigger frame construction
+ EventStates changedBits = aOldState ^ newState;
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ doc->ContentStateChanged(thisContent, changedBits);
+ }
+ if (aSync) {
+ NS_ASSERTION(InActiveDocument(thisContent), "Something is confused");
+ // Make sure that frames are actually constructed immediately.
+ doc->FlushPendingNotifications(Flush_Frames);
+ }
+ } else if (aOldType != mType) {
+ // If our state changed, then we already recreated frames
+ // Otherwise, need to do that here
+ nsCOMPtr<nsIPresShell> shell = doc->GetShell();
+ if (shell) {
+ shell->RecreateFramesFor(thisContent);
+ }
+ }
+}
+
+nsObjectLoadingContent::ObjectType
+nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType)
+{
+ if (aMIMEType.IsEmpty()) {
+ return eType_Null;
+ }
+
+ uint32_t caps = GetCapabilities();
+
+ if ((caps & eSupportImages) && IsSupportedImage(aMIMEType)) {
+ return eType_Image;
+ }
+
+ // Faking support of the PDF content as a document for EMBED tags
+ // when internal PDF viewer is enabled.
+ if (aMIMEType.LowerCaseEqualsLiteral("application/pdf") &&
+ nsContentUtils::IsPDFJSEnabled()) {
+ return eType_Document;
+ }
+
+ // Faking support of the SWF content as a document for EMBED tags
+ // when internal SWF player is enabled.
+ if (aMIMEType.LowerCaseEqualsLiteral("application/x-shockwave-flash") &&
+ nsContentUtils::IsSWFPlayerEnabled()) {
+ return eType_Document;
+ }
+
+ if ((caps & eSupportDocuments) && IsSupportedDocument(aMIMEType)) {
+ return eType_Document;
+ }
+
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ if ((caps & eSupportPlugins) &&
+ pluginHost &&
+ pluginHost->HavePluginForType(aMIMEType, nsPluginHost::eExcludeNone)) {
+ // ShouldPlay will handle checking for disabled plugins
+ return eType_Plugin;
+ }
+
+ return eType_Null;
+}
+
+nsPluginFrame*
+nsObjectLoadingContent::GetExistingFrame()
+{
+ nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsIFrame* frame = thisContent->GetPrimaryFrame();
+ nsIObjectFrame* objFrame = do_QueryFrame(frame);
+ return static_cast<nsPluginFrame*>(objFrame);
+}
+
+void
+nsObjectLoadingContent::CreateStaticClone(nsObjectLoadingContent* aDest) const
+{
+ nsImageLoadingContent::CreateStaticImageClone(aDest);
+
+ aDest->mType = mType;
+ nsObjectLoadingContent* thisObj = const_cast<nsObjectLoadingContent*>(this);
+ if (thisObj->mPrintFrame.IsAlive()) {
+ aDest->mPrintFrame = thisObj->mPrintFrame;
+ } else {
+ aDest->mPrintFrame = const_cast<nsObjectLoadingContent*>(this)->GetExistingFrame();
+ }
+
+ if (mFrameLoader) {
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(aDest));
+ nsFrameLoader* fl = nsFrameLoader::Create(content->AsElement(), nullptr, false);
+ if (fl) {
+ aDest->mFrameLoader = fl;
+ mFrameLoader->CreateStaticClone(fl);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetPrintFrame(nsIFrame** aFrame)
+{
+ *aFrame = mPrintFrame.GetFrame();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::PluginDestroyed()
+{
+ // Called when our plugin is destroyed from under us, usually when reloading
+ // plugins in plugin host. Invalidate instance owner / prototype but otherwise
+ // don't take any action.
+ TeardownProtoChain();
+ if (mInstanceOwner) {
+ mInstanceOwner->Destroy();
+ mInstanceOwner = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::PluginCrashed(nsIPluginTag* aPluginTag,
+ const nsAString& pluginDumpID,
+ const nsAString& browserDumpID,
+ bool submittedCrashReport)
+{
+ LOG(("OBJLC [%p]: Plugin Crashed, queuing crash event", this));
+ NS_ASSERTION(mType == eType_Plugin, "PluginCrashed at non-plugin type");
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+#ifdef XP_MACOSX
+ HTMLObjectElement::HandlePluginCrashed(thisContent->AsElement());
+#endif
+
+ PluginDestroyed();
+
+ // Switch to fallback/crashed state, notify
+ LoadFallback(eFallbackCrashed, true);
+
+ // send nsPluginCrashedEvent
+
+ // Note that aPluginTag in invalidated after we're called, so copy
+ // out any data we need now.
+ nsAutoCString pluginName;
+ aPluginTag->GetName(pluginName);
+ nsAutoCString pluginFilename;
+ aPluginTag->GetFilename(pluginFilename);
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsPluginCrashedEvent(thisContent,
+ pluginDumpID,
+ browserDumpID,
+ NS_ConvertUTF8toUTF16(pluginName),
+ NS_ConvertUTF8toUTF16(pluginFilename),
+ submittedCrashReport);
+ nsresult rv = NS_DispatchToCurrentThread(ev);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to dispatch nsPluginCrashedEvent");
+ }
+ return NS_OK;
+}
+
+nsresult
+nsObjectLoadingContent::ScriptRequestPluginInstance(JSContext* aCx,
+ nsNPAPIPluginInstance **aResult)
+{
+ // The below methods pull the cx off the stack, so make sure they match.
+ //
+ // NB: Sometimes there's a null cx on the stack, in which case |cx| is the
+ // safe JS context. But in that case, IsCallerChrome() will return true,
+ // so the ensuing expression is short-circuited.
+ MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContext(),
+ aCx == nsContentUtils::GetCurrentJSContext());
+ bool callerIsContentJS = (nsContentUtils::GetCurrentJSContext() &&
+ !nsContentUtils::IsCallerChrome() &&
+ !nsContentUtils::IsCallerContentXBL());
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ *aResult = nullptr;
+
+ // The first time content script attempts to access placeholder content, fire
+ // an event. Fallback types >= eFallbackClickToPlay are plugin-replacement
+ // types, see header.
+ if (callerIsContentJS && !mScriptRequested &&
+ InActiveDocument(thisContent) && mType == eType_Null &&
+ mFallbackType >= eFallbackClickToPlay) {
+ nsCOMPtr<nsIRunnable> ev =
+ new nsSimplePluginEvent(thisContent,
+ NS_LITERAL_STRING("PluginScripted"));
+ nsresult rv = NS_DispatchToCurrentThread(ev);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("failed to dispatch PluginScripted event");
+ }
+ mScriptRequested = true;
+ } else if (callerIsContentJS && mType == eType_Plugin && !mInstanceOwner &&
+ nsContentUtils::IsSafeToRunScript() &&
+ InActiveDocument(thisContent)) {
+ // If we're configured as a plugin in an active document and it's safe to
+ // run scripts right now, try spawning synchronously
+ SyncStartPluginInstance();
+ }
+
+ if (mInstanceOwner) {
+ return mInstanceOwner->GetInstance(aResult);
+ }
+
+ // Note that returning a null plugin is expected (and happens often)
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::SyncStartPluginInstance()
+{
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Must be able to run script in order to instantiate a plugin instance!");
+
+ // Don't even attempt to start an instance unless the content is in
+ // the document and active
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ if (!InActiveDocument(thisContent)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> kungFuURIGrip(mURI);
+ mozilla::Unused << kungFuURIGrip; // This URI is not referred to within this function
+ nsCString contentType(mContentType);
+ return InstantiatePluginInstance();
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::AsyncStartPluginInstance()
+{
+ // OK to have an instance already or a pending spawn.
+ if (mInstanceOwner || mPendingInstantiateEvent) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsIDocument* doc = thisContent->OwnerDoc();
+ if (doc->IsStaticDocument() || doc->IsBeingUsedAsImage()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> event = new nsAsyncInstantiateEvent(this);
+ nsresult rv = NS_DispatchToCurrentThread(event);
+ if (NS_SUCCEEDED(rv)) {
+ // Track pending events
+ mPendingInstantiateEvent = event;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetSrcURI(nsIURI** aURI)
+{
+ NS_IF_ADDREF(*aURI = GetSrcURI());
+ return NS_OK;
+}
+
+static bool
+DoDelayedStop(nsPluginInstanceOwner* aInstanceOwner,
+ nsObjectLoadingContent* aContent,
+ bool aDelayedStop)
+{
+ // Don't delay stopping QuickTime (bug 425157), Flip4Mac (bug 426524),
+ // XStandard (bug 430219), CMISS Zinc (bug 429604).
+ if (aDelayedStop
+#if !(defined XP_WIN || defined MOZ_X11)
+ && !aInstanceOwner->MatchPluginName("QuickTime")
+ && !aInstanceOwner->MatchPluginName("Flip4Mac")
+ && !aInstanceOwner->MatchPluginName("XStandard plugin")
+ && !aInstanceOwner->MatchPluginName("CMISS Zinc Plugin")
+#endif
+ ) {
+ nsCOMPtr<nsIRunnable> evt =
+ new nsStopPluginRunnable(aInstanceOwner, aContent);
+ NS_DispatchToCurrentThread(evt);
+ return true;
+ }
+ return false;
+}
+
+void
+nsObjectLoadingContent::LoadFallback(FallbackType aType, bool aNotify) {
+ EventStates oldState = ObjectState();
+ ObjectType oldType = mType;
+
+ NS_ASSERTION(!mInstanceOwner && !mFrameLoader && !mChannel,
+ "LoadFallback called with loaded content");
+
+ //
+ // Fixup mFallbackType
+ //
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+
+ if (!thisContent->IsHTMLElement() || mContentType.IsEmpty()) {
+ // Don't let custom fallback handlers run outside HTML, tags without a
+ // determined type should always just be alternate content
+ aType = eFallbackAlternate;
+ }
+
+ // We'll set this to null no matter what now, doing it here means we'll load
+ // child embeds as we find them in the upcoming loop.
+ mType = eType_Null;
+
+ // Do a breadth-first traverse of node tree with the current element as root,
+ // looking for the first embed we can find.
+ nsTArray<nsINodeList*> childNodes;
+ if ((thisContent->IsHTMLElement(nsGkAtoms::object) ||
+ thisContent->IsHTMLElement(nsGkAtoms::applet)) &&
+ (aType == eFallbackUnsupported ||
+ aType == eFallbackDisabled ||
+ aType == eFallbackBlocklisted ||
+ aType == eFallbackAlternate))
+ {
+ for (nsIContent* child = thisContent->GetFirstChild(); child;
+ child = child->GetNextNode(thisContent)) {
+ if (aType != eFallbackAlternate &&
+ !child->IsHTMLElement(nsGkAtoms::param) &&
+ nsStyleUtil::IsSignificantChild(child, true, false)) {
+ aType = eFallbackAlternate;
+ }
+ if (child->IsHTMLElement(nsGkAtoms::embed) &&
+ thisContent->IsHTMLElement(nsGkAtoms::object)) {
+ HTMLSharedObjectElement* object = static_cast<HTMLSharedObjectElement*>(child);
+ if (object) {
+ object->StartObjectLoad(true, true);
+ }
+ }
+ }
+ }
+
+ mFallbackType = aType;
+
+ // Notify
+ if (!aNotify) {
+ return; // done
+ }
+
+ NotifyStateChanged(oldType, oldState, false, true);
+}
+
+void
+nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner,
+ bool aDelayedStop,
+ bool aForcedReentry)
+{
+ // DoStopPlugin can process events -- There may be pending
+ // CheckPluginStopEvent events which can drop in underneath us and destroy the
+ // instance we are about to destroy. We prevent that with the mPluginStopping
+ // flag. (aForcedReentry is only true from the callback of an earlier delayed
+ // stop)
+ if (mIsStopping && !aForcedReentry) {
+ return;
+ }
+ mIsStopping = true;
+
+ RefPtr<nsPluginInstanceOwner> kungFuDeathGrip(aInstanceOwner);
+ RefPtr<nsNPAPIPluginInstance> inst;
+ aInstanceOwner->GetInstance(getter_AddRefs(inst));
+ if (inst) {
+ if (DoDelayedStop(aInstanceOwner, this, aDelayedStop)) {
+ return;
+ }
+
+#if defined(XP_MACOSX)
+ aInstanceOwner->HidePluginWindow();
+#endif
+
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ NS_ASSERTION(pluginHost, "No plugin host?");
+ pluginHost->StopPluginInstance(inst);
+ }
+
+ aInstanceOwner->Destroy();
+
+ // If we re-enter in plugin teardown UnloadObject will tear down the
+ // protochain -- the current protochain could be from a new, unrelated, load.
+ if (!mIsStopping) {
+ LOG(("OBJLC [%p]: Re-entered in plugin teardown", this));
+ return;
+ }
+
+ TeardownProtoChain();
+ mIsStopping = false;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::StopPluginInstance()
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+ // Clear any pending events
+ mPendingInstantiateEvent = nullptr;
+ mPendingCheckPluginStopEvent = nullptr;
+
+ // If we're currently instantiating, clearing this will cause
+ // InstantiatePluginInstance's re-entrance check to destroy the created plugin
+ mInstantiating = false;
+
+ if (!mInstanceOwner) {
+ return NS_OK;
+ }
+
+ if (mChannel) {
+ // The plugin has already used data from this channel, we'll need to
+ // re-open it to handle instantiating again, even if we don't invalidate
+ // our loaded state.
+ /// XXX(johns): Except currently, we don't, just leaving re-opening channels
+ /// to plugins...
+ LOG(("OBJLC [%p]: StopPluginInstance - Closing used channel", this));
+ CloseChannel();
+ }
+
+ // We detach the instance owner's frame before destruction, but don't destroy
+ // the instance owner until the plugin is stopped.
+ mInstanceOwner->SetFrame(nullptr);
+
+ bool delayedStop = false;
+#ifdef XP_WIN
+ // Force delayed stop for Real plugin only; see bug 420886, 426852.
+ RefPtr<nsNPAPIPluginInstance> inst;
+ mInstanceOwner->GetInstance(getter_AddRefs(inst));
+ if (inst) {
+ const char* mime = nullptr;
+ if (NS_SUCCEEDED(inst->GetMIMEType(&mime)) && mime) {
+ if (nsPluginHost::GetSpecialType(nsDependentCString(mime)) ==
+ nsPluginHost::eSpecialType_RealPlayer) {
+ delayedStop = true;
+ }
+ }
+ }
+#endif
+
+ RefPtr<nsPluginInstanceOwner> ownerGrip(mInstanceOwner);
+ mInstanceOwner = nullptr;
+
+ // This can/will re-enter
+ DoStopPlugin(ownerGrip, delayedStop);
+
+ return NS_OK;
+}
+
+void
+nsObjectLoadingContent::NotifyContentObjectWrapper()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> obj(cx, thisContent->GetWrapper());
+ if (!obj) {
+ // Nothing to do here if there's no wrapper for mContent. The proto
+ // chain will be fixed appropriately when the wrapper is created.
+ return;
+ }
+
+ SetupProtoChain(cx, obj);
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::PlayPlugin()
+{
+ if (!nsContentUtils::IsCallerChrome())
+ return NS_OK;
+
+ if (!mActivated) {
+ mActivated = true;
+ LOG(("OBJLC [%p]: Activated by user", this));
+ }
+
+ // If we're in a click-to-play state, reload.
+ // Fallback types >= eFallbackClickToPlay are plugin-replacement types, see
+ // header
+ if (mType == eType_Null && mFallbackType >= eFallbackClickToPlay) {
+ return LoadObject(true, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::Reload(bool aClearActivation)
+{
+ if (aClearActivation) {
+ mActivated = false;
+ }
+
+ return LoadObject(true, true);
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetActivated(bool *aActivated)
+{
+ *aActivated = Activated();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetPluginFallbackType(uint32_t* aPluginFallbackType)
+{
+ NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+ *aPluginFallbackType = mFallbackType;
+ return NS_OK;
+}
+
+uint32_t
+nsObjectLoadingContent::DefaultFallbackType()
+{
+ FallbackType reason;
+ bool go = ShouldPlay(reason, true);
+ if (go) {
+ return PLUGIN_ACTIVE;
+ }
+ return reason;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetHasRunningPlugin(bool *aHasPlugin)
+{
+ NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
+ *aHasPlugin = HasRunningPlugin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::GetRunID(uint32_t* aRunID)
+{
+ if (NS_WARN_IF(!nsContentUtils::IsCallerChrome())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (NS_WARN_IF(!aRunID)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (!mHasRunID) {
+ // The plugin instance must not have a run ID, so we must
+ // be running the plugin in-process.
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ *aRunID = mRunID;
+ return NS_OK;
+}
+
+static bool sPrefsInitialized;
+static uint32_t sSessionTimeoutMinutes;
+static uint32_t sPersistentTimeoutDays;
+static bool sBlockURIs;
+
+static void initializeObjectLoadingContentPrefs()
+{
+ if (!sPrefsInitialized) {
+ Preferences::AddUintVarCache(&sSessionTimeoutMinutes,
+ "plugin.sessionPermissionNow.intervalInMinutes", 60);
+ Preferences::AddUintVarCache(&sPersistentTimeoutDays,
+ "plugin.persistentPermissionAlways.intervalInDays", 90);
+
+ Preferences::AddBoolVarCache(&sBlockURIs, kPrefBlockURIs, false);
+ sPrefsInitialized = true;
+ }
+}
+
+bool
+nsObjectLoadingContent::ShouldBlockContent()
+{
+
+ if (!sPrefsInitialized) {
+ initializeObjectLoadingContentPrefs();
+ }
+
+ if (mContentBlockingEnabled && mURI && IsFlashMIME(mContentType) && sBlockURIs ) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsObjectLoadingContent::ShouldPlay(FallbackType &aReason, bool aIgnoreCurrentType)
+{
+ nsresult rv;
+
+ if (!sPrefsInitialized) {
+ initializeObjectLoadingContentPrefs();
+ }
+
+ if (BrowserTabsRemoteAutostart()) {
+ bool shouldLoadInParent = nsPluginHost::ShouldLoadTypeInParent(mContentType);
+ bool inParent = XRE_IsParentProcess();
+
+ if (shouldLoadInParent != inParent) {
+ // Plugins need to be locked to either the parent process or the content
+ // process. If a plugin is locked to one process type, it can't be used in
+ // the other. Otherwise we'll get hangs.
+ aReason = eFallbackDisabled;
+ return false;
+ }
+ }
+
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+
+ // at this point if it's not a plugin, we let it play/fallback
+ if (!aIgnoreCurrentType && mType != eType_Plugin) {
+ return true;
+ }
+
+ // Order of checks:
+ // * Assume a default of click-to-play
+ // * If globally disabled, per-site permissions cannot override.
+ // * If blocklisted, override the reason with the blocklist reason
+ // * Check per-site permissions and follow those if specified.
+ // * Honor per-plugin disabled permission
+ // * Blocklisted plugins are forced to CtP
+ // * Check per-plugin permission and follow that.
+
+ aReason = eFallbackClickToPlay;
+
+ uint32_t enabledState = nsIPluginTag::STATE_DISABLED;
+ pluginHost->GetStateForType(mContentType, nsPluginHost::eExcludeNone,
+ &enabledState);
+ if (nsIPluginTag::STATE_DISABLED == enabledState) {
+ aReason = eFallbackDisabled;
+ return false;
+ }
+
+ // Before we check permissions, get the blocklist state of this plugin to set
+ // the fallback reason correctly. In the content process this will involve
+ // an ipc call to chrome.
+ uint32_t blocklistState = nsIBlocklistService::STATE_BLOCKED;
+ pluginHost->GetBlocklistStateForType(mContentType,
+ nsPluginHost::eExcludeNone,
+ &blocklistState);
+ if (blocklistState == nsIBlocklistService::STATE_BLOCKED) {
+ // no override possible
+ aReason = eFallbackBlocklisted;
+ return false;
+ }
+
+ if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE) {
+ aReason = eFallbackVulnerableUpdatable;
+ }
+ else if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
+ aReason = eFallbackVulnerableNoUpdate;
+ }
+
+ // Check the permission manager for permission based on the principal of
+ // the toplevel content.
+
+ nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
+ MOZ_ASSERT(thisContent);
+ nsIDocument* ownerDoc = thisContent->OwnerDoc();
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = ownerDoc->GetWindow();
+ if (!window) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = window->GetTop();
+ NS_ENSURE_TRUE(topWindow, false);
+ nsCOMPtr<nsIDocument> topDoc = topWindow->GetDoc();
+ NS_ENSURE_TRUE(topDoc, false);
+
+ nsCOMPtr<nsIPermissionManager> permissionManager = services::GetPermissionManager();
+ NS_ENSURE_TRUE(permissionManager, false);
+
+ // For now we always say that the system principal uses click-to-play since
+ // that maintains current behavior and we have tests that expect this.
+ // What we really should do is disable plugins entirely in pages that use
+ // the system principal, i.e. in chrome pages. That way the click-to-play
+ // code here wouldn't matter at all. Bug 775301 is tracking this.
+ if (!nsContentUtils::IsSystemPrincipal(topDoc->NodePrincipal())) {
+ nsAutoCString permissionString;
+ rv = pluginHost->GetPermissionStringForType(mContentType,
+ nsPluginHost::eExcludeNone,
+ permissionString);
+ NS_ENSURE_SUCCESS(rv, false);
+ uint32_t permission;
+ rv = permissionManager->TestPermissionFromPrincipal(topDoc->NodePrincipal(),
+ permissionString.Data(),
+ &permission);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (permission != nsIPermissionManager::UNKNOWN_ACTION) {
+ uint64_t nowms = PR_Now() / 1000;
+ permissionManager->UpdateExpireTime(
+ topDoc->NodePrincipal(), permissionString.Data(), false,
+ nowms + sSessionTimeoutMinutes * 60 * 1000,
+ nowms / 1000 + uint64_t(sPersistentTimeoutDays) * 24 * 60 * 60 * 1000);
+ }
+ switch (permission) {
+ case nsIPermissionManager::ALLOW_ACTION:
+ if (PreferFallback(false /* isPluginClickToPlay */)) {
+ aReason = eFallbackAlternate;
+ return false;
+ }
+
+ return true;
+ case nsIPermissionManager::DENY_ACTION:
+ aReason = eFallbackDisabled;
+ return false;
+ case nsIPermissionManager::PROMPT_ACTION:
+ if (PreferFallback(true /* isPluginClickToPlay */)) {
+ // False is already returned in this case, but
+ // it's important to correctly set aReason too.
+ aReason = eFallbackAlternate;
+ }
+
+ return false;
+ case nsIPermissionManager::UNKNOWN_ACTION:
+ break;
+ default:
+ MOZ_ASSERT(false);
+ return false;
+ }
+ }
+
+ // No site-specific permissions. Vulnerable plugins are automatically CtP
+ if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
+ blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
+ return false;
+ }
+
+ if (PreferFallback(enabledState == nsIPluginTag::STATE_CLICKTOPLAY)) {
+ aReason = eFallbackAlternate;
+ return false;
+ }
+
+ switch (enabledState) {
+ case nsIPluginTag::STATE_ENABLED:
+ return true;
+ case nsIPluginTag::STATE_CLICKTOPLAY:
+ return false;
+ }
+ MOZ_CRASH("Unexpected enabledState");
+}
+
+bool
+nsObjectLoadingContent::FavorFallbackMode(bool aIsPluginClickToPlay) {
+ if (!IsFlashMIME(mContentType)) {
+ return false;
+ }
+
+ nsCString prefString;
+ if (NS_SUCCEEDED(Preferences::GetCString(kPrefFavorFallbackMode, &prefString))) {
+ if (aIsPluginClickToPlay &&
+ prefString.EqualsLiteral("follow-ctp")) {
+ return true;
+ }
+
+ if (prefString.EqualsLiteral("always")) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsObjectLoadingContent::HasGoodFallback() {
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "must be a content");
+
+ if (!thisContent->IsHTMLElement(nsGkAtoms::object) ||
+ mContentType.IsEmpty()) {
+ return false;
+ }
+
+ nsTArray<nsCString> rulesList;
+ nsCString prefString;
+ if (NS_SUCCEEDED(Preferences::GetCString(kPrefFavorFallbackRules, &prefString))) {
+ ParseString(prefString, ',', rulesList);
+ }
+
+ for (uint32_t i = 0; i < rulesList.Length(); ++i) {
+ // RULE "embed":
+ // Don't use fallback content if the object contains an <embed> inside its
+ // fallback content.
+ if (rulesList[i].EqualsLiteral("embed")) {
+ nsTArray<nsINodeList*> childNodes;
+ for (nsIContent* child = thisContent->GetFirstChild();
+ child;
+ child = child->GetNextNode(thisContent)) {
+ if (child->IsHTMLElement(nsGkAtoms::embed)) {
+ return false;
+ }
+ }
+ }
+
+ // RULE "video":
+ // Use fallback content if the object contains a <video> inside its
+ // fallback content.
+ if (rulesList[i].EqualsLiteral("video")) {
+ nsTArray<nsINodeList*> childNodes;
+ for (nsIContent* child = thisContent->GetFirstChild();
+ child;
+ child = child->GetNextNode(thisContent)) {
+ if (child->IsHTMLElement(nsGkAtoms::video)) {
+ return true;
+ }
+ }
+ }
+
+ // RULE "adobelink":
+ // Don't use fallback content when it has a link to adobe's website.
+ if (rulesList[i].EqualsLiteral("adobelink")) {
+ nsTArray<nsINodeList*> childNodes;
+ for (nsIContent* child = thisContent->GetFirstChild();
+ child;
+ child = child->GetNextNode(thisContent)) {
+ if (child->IsHTMLElement(nsGkAtoms::a)) {
+ nsCOMPtr<nsIURI> href = child->GetHrefURI();
+ if (href) {
+ nsAutoCString asciiHost;
+ nsresult rv = href->GetAsciiHost(asciiHost);
+ if (NS_SUCCEEDED(rv) &&
+ !asciiHost.IsEmpty() &&
+ (asciiHost.EqualsLiteral("adobe.com") ||
+ StringEndsWith(asciiHost, NS_LITERAL_CSTRING(".adobe.com")))) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ // RULE "installinstructions":
+ // Don't use fallback content when the text content on the fallback appears
+ // to contain instructions to install or download Flash.
+ if (rulesList[i].EqualsLiteral("installinstructions")) {
+ nsAutoString textContent;
+ ErrorResult rv;
+ thisContent->GetTextContent(textContent, rv);
+ bool hasText =
+ !rv.Failed() &&
+ (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("Flash"), textContent) ||
+ CaseInsensitiveFindInReadable(NS_LITERAL_STRING("Install"), textContent) ||
+ CaseInsensitiveFindInReadable(NS_LITERAL_STRING("Download"), textContent));
+
+ if (hasText) {
+ return false;
+ }
+ }
+
+ // RULE "true":
+ // By having a rule that returns true, we can put it at the end of the rules list
+ // to change the default-to-false behavior to be default-to-true.
+ if (rulesList[i].EqualsLiteral("true")) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsObjectLoadingContent::PreferFallback(bool aIsPluginClickToPlay) {
+ if (mPreferFallbackKnown) {
+ return mPreferFallback;
+ }
+
+ mPreferFallbackKnown = true;
+ mPreferFallback = FavorFallbackMode(aIsPluginClickToPlay) && HasGoodFallback();
+ return mPreferFallback;
+}
+
+nsIDocument*
+nsObjectLoadingContent::GetContentDocument(nsIPrincipal& aSubjectPrincipal)
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ if (!thisContent->IsInComposedDoc()) {
+ return nullptr;
+ }
+
+ nsIDocument *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;
+}
+
+void
+nsObjectLoadingContent::LegacyCall(JSContext* aCx,
+ JS::Handle<JS::Value> aThisVal,
+ const Sequence<JS::Value>& aArguments,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ JS::Rooted<JSObject*> obj(aCx, thisContent->GetWrapper());
+ MOZ_ASSERT(obj, "How did we get called?");
+
+ // Make sure we're not dealing with an Xray. Our DoCall code can't handle
+ // random cross-compartment wrappers, so we're going to have to wrap
+ // everything up into our compartment, but that means we need to check that
+ // this is not an Xray situation by hand.
+ if (!JS_WrapObject(aCx, &obj)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ if (nsDOMClassInfo::ObjectIsNativeWrapper(aCx, obj)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ obj = thisContent->GetWrapper();
+ // Now wrap things up into the compartment of "obj"
+ JSAutoCompartment ac(aCx, obj);
+ JS::AutoValueVector args(aCx);
+ if (!args.append(aArguments.Elements(), aArguments.Length())) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ for (size_t i = 0; i < args.length(); i++) {
+ if (!JS_WrapValue(aCx, args[i])) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+
+ JS::Rooted<JS::Value> thisVal(aCx, aThisVal);
+ if (!JS_WrapValue(aCx, &thisVal)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ RefPtr<nsNPAPIPluginInstance> pi;
+ nsresult rv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ // if there's no plugin around for this object, throw.
+ if (!pi) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ JS::Rooted<JSObject*> pi_obj(aCx);
+ JS::Rooted<JSObject*> pi_proto(aCx);
+
+ rv = GetPluginJSObject(aCx, obj, pi, &pi_obj, &pi_proto);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (!pi_obj) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ bool ok = JS::Call(aCx, thisVal, pi_obj, args, aRetval);
+ if (!ok) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Telemetry::Accumulate(Telemetry::PLUGIN_CALLED_DIRECTLY, true);
+}
+
+void
+nsObjectLoadingContent::SetupProtoChain(JSContext* aCx,
+ JS::Handle<JSObject*> aObject)
+{
+ if (mType != eType_Plugin) {
+ return;
+ }
+
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ RefPtr<SetupProtoChainRunner> runner = new SetupProtoChainRunner(this);
+ nsContentUtils::AddScriptRunner(runner);
+ return;
+ }
+
+ // We get called on random compartments here for some reason
+ // (perhaps because WrapObject can happen on a random compartment?)
+ // so make sure to enter the compartment of aObject.
+ MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
+
+ JSAutoCompartment ac(aCx, aObject);
+
+ RefPtr<nsNPAPIPluginInstance> pi;
+ nsresult rv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (!pi) {
+ // No plugin around for this object.
+ return;
+ }
+
+ JS::Rooted<JSObject*> pi_obj(aCx); // XPConnect-wrapped peer object, when we get it.
+ JS::Rooted<JSObject*> pi_proto(aCx); // 'pi.__proto__'
+
+ rv = GetPluginJSObject(aCx, aObject, pi, &pi_obj, &pi_proto);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (!pi_obj) {
+ // Didn't get a plugin instance JSObject, nothing we can do then.
+ return;
+ }
+
+ // If we got an xpconnect-wrapped plugin object, set obj's
+ // prototype's prototype to the scriptable plugin.
+
+ JS::Handle<JSObject*> my_proto = GetDOMClass(aObject)->mGetProto(aCx);
+ MOZ_ASSERT(my_proto);
+
+ // Set 'this.__proto__' to pi
+ if (!::JS_SetPrototype(aCx, aObject, pi_obj)) {
+ return;
+ }
+
+ if (pi_proto && js::GetObjectClass(pi_proto) != js::ObjectClassPtr) {
+ // The plugin wrapper has a proto that's not Object.prototype, set
+ // 'pi.__proto__.__proto__' to the original 'this.__proto__'
+ if (pi_proto != my_proto && !::JS_SetPrototype(aCx, pi_proto, my_proto)) {
+ return;
+ }
+ } else {
+ // 'pi' didn't have a prototype, or pi's proto was
+ // 'Object.prototype' (i.e. pi is an NPRuntime wrapped JS object)
+ // set 'pi.__proto__' to the original 'this.__proto__'
+ if (!::JS_SetPrototype(aCx, pi_obj, my_proto)) {
+ return;
+ }
+ }
+
+ // Before this proto dance the objects involved looked like this:
+ //
+ // this.__proto__.__proto__
+ // ^ ^ ^
+ // | | |__ Object.prototype
+ // | |
+ // | |__ WebIDL prototype (shared)
+ // |
+ // |__ WebIDL object
+ //
+ // pi.__proto__
+ // ^ ^
+ // | |__ Object.prototype or some other object
+ // |
+ // |__ Plugin NPRuntime JS object wrapper
+ //
+ // Now, after the above prototype setup the prototype chain should
+ // look like this if pi.__proto__ was Object.prototype:
+ //
+ // this.__proto__.__proto__.__proto__
+ // ^ ^ ^ ^
+ // | | | |__ Object.prototype
+ // | | |
+ // | | |__ WebIDL prototype (shared)
+ // | |
+ // | |__ Plugin NPRuntime JS object wrapper
+ // |
+ // |__ WebIDL object
+ //
+ // or like this if pi.__proto__ was some other object:
+ //
+ // this.__proto__.__proto__.__proto__.__proto__
+ // ^ ^ ^ ^ ^
+ // | | | | |__ Object.prototype
+ // | | | |
+ // | | | |__ WebIDL prototype (shared)
+ // | | |
+ // | | |__ old pi.__proto__
+ // | |
+ // | |__ Plugin NPRuntime JS object wrapper
+ // |
+ // |__ WebIDL object
+ //
+}
+
+// static
+nsresult
+nsObjectLoadingContent::GetPluginJSObject(JSContext *cx,
+ JS::Handle<JSObject*> obj,
+ nsNPAPIPluginInstance *plugin_inst,
+ JS::MutableHandle<JSObject*> plugin_obj,
+ JS::MutableHandle<JSObject*> plugin_proto)
+{
+ // NB: We need an AutoEnterCompartment because we can be called from
+ // nsPluginFrame when the plugin loads after the JS object for our content
+ // node has been created.
+ JSAutoCompartment ac(cx, obj);
+
+ if (plugin_inst) {
+ plugin_inst->GetJSObject(cx, plugin_obj.address());
+ if (plugin_obj) {
+ if (!::JS_GetPrototype(cx, plugin_obj, plugin_proto)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsObjectLoadingContent::TeardownProtoChain()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ NS_ENSURE_TRUE_VOID(thisContent->GetWrapper());
+
+ // We don't init the AutoJSAPI with our wrapper because we don't want it
+ // reporting errors to our window's onerror listeners.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> obj(cx, thisContent->GetWrapper());
+ MOZ_ASSERT(obj);
+
+ JS::Rooted<JSObject*> proto(cx);
+ JSAutoCompartment ac(cx, obj);
+
+ // Loop over the DOM element's JS object prototype chain and remove
+ // all JS objects of the class sNPObjectJSWrapperClass
+ DebugOnly<bool> removed = false;
+ while (obj) {
+ if (!::JS_GetPrototype(cx, obj, &proto)) {
+ return;
+ }
+ if (!proto) {
+ break;
+ }
+ // Unwrap while checking the class - if the prototype is a wrapper for
+ // an NP object, that counts too.
+ if (nsNPObjWrapper::IsWrapper(js::UncheckedUnwrap(proto))) {
+ // We found an NPObject on the proto chain, get its prototype...
+ if (!::JS_GetPrototype(cx, proto, &proto)) {
+ return;
+ }
+
+ MOZ_ASSERT(!removed, "more than one NPObject in prototype chain");
+ removed = true;
+
+ // ... and pull it out of the chain.
+ ::JS_SetPrototype(cx, obj, proto);
+ }
+
+ obj = proto;
+ }
+}
+
+bool
+nsObjectLoadingContent::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
+ JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc)
+{
+ // We don't resolve anything; we just try to make sure we're instantiated.
+ // This purposefully does not fire for chrome/xray resolves, see bug 967694
+
+ RefPtr<nsNPAPIPluginInstance> pi;
+ nsresult rv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi));
+ if (NS_FAILED(rv)) {
+ return mozilla::dom::Throw(aCx, rv);
+ }
+ return true;
+}
+
+/* static */
+bool
+nsObjectLoadingContent::MayResolve(jsid aId)
+{
+ // We can resolve anything, really.
+ return true;
+}
+
+void
+nsObjectLoadingContent::GetOwnPropertyNames(JSContext* aCx,
+ nsTArray<nsString>& /* unused */,
+ ErrorResult& aRv)
+{
+ // Just like DoResolve, just make sure we're instantiated. That will do
+ // the work our Enumerate hook needs to do. This purposefully does not fire
+ // for xray resolves, see bug 967694
+ RefPtr<nsNPAPIPluginInstance> pi;
+ aRv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi));
+}
+
+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,
+ NS_LITERAL_STRING("error"),
+ false, false);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+ }
+}
+
+// SetupProtoChainRunner implementation
+nsObjectLoadingContent::SetupProtoChainRunner::SetupProtoChainRunner(
+ nsObjectLoadingContent* aContent)
+ : mContent(aContent)
+{
+}
+
+nsObjectLoadingContent::SetupProtoChainRunner::~SetupProtoChainRunner()
+{
+}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::SetupProtoChainRunner::Run()
+{
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ nsCOMPtr<nsIContent> content;
+ CallQueryInterface(mContent.get(), getter_AddRefs(content));
+ JS::Rooted<JSObject*> obj(cx, content->GetWrapper());
+ if (!obj) {
+ // No need to set up our proto chain if we don't even have an object
+ return NS_OK;
+ }
+ nsObjectLoadingContent* objectLoadingContent =
+ static_cast<nsObjectLoadingContent*>(mContent.get());
+ objectLoadingContent->SetupProtoChain(cx, obj);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsObjectLoadingContent::SetupProtoChainRunner, nsIRunnable)
diff --git a/dom/base/nsObjectLoadingContent.h b/dom/base/nsObjectLoadingContent.h
new file mode 100644
index 000000000..d1513ba22
--- /dev/null
+++ b/dom/base/nsObjectLoadingContent.h
@@ -0,0 +1,721 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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>, <applet>, etc).
+ */
+
+#ifndef NSOBJECTLOADINGCONTENT_H_
+#define NSOBJECTLOADINGCONTENT_H_
+
+#include "mozilla/Attributes.h"
+#include "nsImageLoadingContent.h"
+#include "nsIStreamListener.h"
+#include "nsIChannelEventSink.h"
+#include "nsIContentPolicy.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIRunnable.h"
+#include "nsIThreadInternal.h"
+#include "nsIFrame.h"
+#include "nsIFrameLoader.h"
+
+class nsAsyncInstantiateEvent;
+class nsStopPluginRunnable;
+class AutoSetInstantiatingToFalse;
+class nsIPrincipal;
+class nsFrameLoader;
+class nsPluginFrame;
+class nsXULElement;
+class nsPluginInstanceOwner;
+
+namespace mozilla {
+namespace dom {
+template<typename T> class Sequence;
+struct MozPluginParameter;
+class HTMLIFrameElement;
+} // namespace dom
+} // namespace mozilla
+
+class nsObjectLoadingContent : public nsImageLoadingContent
+ , public nsIStreamListener
+ , public nsIFrameLoaderOwner
+ , public nsIObjectLoadingContent
+ , public nsIChannelEventSink
+{
+ friend class AutoSetInstantiatingToFalse;
+ friend class AutoSetLoadingToFalse;
+ friend class CheckPluginStopEvent;
+ friend class nsStopPluginRunnable;
+ friend class nsAsyncInstantiateEvent;
+
+ 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 plugin
+ eType_Plugin = TYPE_PLUGIN,
+ // Content is a subdocument, possibly SVG
+ eType_Document = TYPE_DOCUMENT,
+ // No content loaded (fallback). May be showing alternate content or
+ // a custom error handler - *including* click-to-play dialogs
+ eType_Null = TYPE_NULL
+ };
+ enum FallbackType {
+ // The content type is not supported (e.g. plugin not installed)
+ eFallbackUnsupported = nsIObjectLoadingContent::PLUGIN_UNSUPPORTED,
+ // Showing alternate content
+ eFallbackAlternate = nsIObjectLoadingContent::PLUGIN_ALTERNATE,
+ // The plugin exists, but is disabled
+ eFallbackDisabled = nsIObjectLoadingContent::PLUGIN_DISABLED,
+ // The plugin is blocklisted and disabled
+ eFallbackBlocklisted = nsIObjectLoadingContent::PLUGIN_BLOCKLISTED,
+ // The plugin is considered outdated, but not disabled
+ eFallbackOutdated = nsIObjectLoadingContent::PLUGIN_OUTDATED,
+ // The plugin has crashed
+ eFallbackCrashed = nsIObjectLoadingContent::PLUGIN_CRASHED,
+ // Suppressed by security policy
+ eFallbackSuppressed = nsIObjectLoadingContent::PLUGIN_SUPPRESSED,
+ // Blocked by content policy
+ eFallbackUserDisabled = nsIObjectLoadingContent::PLUGIN_USER_DISABLED,
+ /// ** All values >= eFallbackClickToPlay are plugin placeholder types
+ /// that would be replaced by a real plugin if activated (PlayPlugin())
+ /// ** Furthermore, values >= eFallbackClickToPlay and
+ /// <= eFallbackVulnerableNoUpdate are click-to-play types.
+ // The plugin is disabled until the user clicks on it
+ eFallbackClickToPlay = nsIObjectLoadingContent::PLUGIN_CLICK_TO_PLAY,
+ // The plugin is vulnerable (update available)
+ eFallbackVulnerableUpdatable = nsIObjectLoadingContent::PLUGIN_VULNERABLE_UPDATABLE,
+ // The plugin is vulnerable (no update available)
+ eFallbackVulnerableNoUpdate = nsIObjectLoadingContent::PLUGIN_VULNERABLE_NO_UPDATE,
+ };
+
+ nsObjectLoadingContent();
+ virtual ~nsObjectLoadingContent();
+
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIFRAMELOADEROWNER
+ NS_DECL_NSIOBJECTLOADINGCONTENT
+ NS_DECL_NSICHANNELEVENTSINK
+
+ /**
+ * Object state. This is a bitmask of NS_EVENT_STATEs epresenting the
+ * current state of the object.
+ */
+ mozilla::EventStates 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);
+
+ /**
+ * Immediately instantiate a plugin instance. This is a no-op if mType !=
+ * eType_Plugin or a plugin is already running.
+ *
+ * aIsLoading indicates that we are in the loading code, and we can bypass
+ * the mIsLoading check.
+ */
+ nsresult InstantiatePluginInstance(bool aIsLoading = false);
+
+ /**
+ * Notify this class the document state has changed
+ * Called by nsDocument so we may suspend plugins in inactive documents)
+ */
+ void NotifyOwnerDocumentActivityChanged();
+
+ /**
+ * When a plug-in is instantiated, it can create a scriptable
+ * object that the page wants to interact with. We expose this
+ * object by placing it on the prototype chain of our element,
+ * between the element itself and its most-derived DOM prototype.
+ *
+ * SetupProtoChain handles actually inserting the plug-in
+ * scriptable object into the proto chain if needed.
+ *
+ * DoResolve is a hook that allows us to find out when the web
+ * page is looking up a property name on our object and make sure
+ * that our plug-in, if any, is instantiated.
+ */
+ // Helper for WebIDL node wrapping
+ void SetupProtoChain(JSContext* aCx, JS::Handle<JSObject*> aObject);
+
+ // Remove plugin from protochain
+ void TeardownProtoChain();
+
+ // Helper for WebIDL NeedResolve
+ bool DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
+ JS::Handle<jsid> aId,
+ JS::MutableHandle<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);
+
+ // Helper for WebIDL enumeration
+ void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& /* unused */,
+ mozilla::ErrorResult& aRv);
+
+ // WebIDL API
+ nsIDocument* 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));
+ }
+ void PlayPlugin(mozilla::ErrorResult& aRv)
+ {
+ aRv = PlayPlugin();
+ }
+ void Reload(bool aClearActivation, mozilla::ErrorResult& aRv)
+ {
+ aRv = Reload(aClearActivation);
+ }
+ bool Activated() const
+ {
+ return mActivated;
+ }
+ nsIURI* GetSrcURI() const
+ {
+ return mURI;
+ }
+
+ /**
+ * The default state that this plugin would be without manual activation.
+ * @returns PLUGIN_ACTIVE if the default state would be active.
+ */
+ uint32_t DefaultFallbackType();
+
+ uint32_t PluginFallbackType() const
+ {
+ return mFallbackType;
+ }
+ bool HasRunningPlugin() const
+ {
+ return !!mInstanceOwner;
+ }
+ void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& aRv)
+ {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+ void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& aRv)
+ {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+ void LegacyCall(JSContext* aCx, JS::Handle<JS::Value> aThisVal,
+ const mozilla::dom::Sequence<JS::Value>& aArguments,
+ JS::MutableHandle<JS::Value> aRetval,
+ mozilla::ErrorResult& aRv);
+
+ uint32_t GetRunID(mozilla::ErrorResult& aRv)
+ {
+ uint32_t runID;
+ nsresult rv = GetRunID(&runID);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return 0;
+ }
+
+ return runID;
+ }
+
+ bool IsRewrittenYoutubeEmbed() const
+ {
+ return mRewrittenYoutubeEmbed;
+ }
+
+ void PresetOpenerWindow(mozIDOMWindowProxy* aOpenerWindow, mozilla::ErrorResult& aRv);
+
+ 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 <applet>,
+ * <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 eSupportClassID 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
+ // (nsIDocumentLoaderFactory)
+ // This flag always includes SVG
+ eSupportClassID = 1u << 3, // The classid attribute is supported
+
+ // 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 DestroyContent();
+
+ static void Traverse(nsObjectLoadingContent *tmp,
+ nsCycleCollectionTraversalCallback &cb);
+
+ void CreateStaticClone(nsObjectLoadingContent* aDest) const;
+
+ void DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner, bool aDelayedStop,
+ bool aForcedReentry = false);
+
+ nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandler);
+ void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true);
+
+ /**
+ * Return the content policy type used for loading the element.
+ */
+ virtual nsContentPolicyType GetContentPolicyType() const = 0;
+
+ 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.
+ * @param aIgnoreCodebase Flag for ignoring the "codebase" param when
+ * building the array. This is useful when loading
+ * java.
+ */
+ void GetNestedParams(nsTArray<mozilla::dom::MozPluginParameter>& aParameters,
+ bool aIgnoreCodebase);
+
+ MOZ_MUST_USE nsresult BuildParametersArray();
+
+ /**
+ * Loads fallback content with the specified FallbackType
+ *
+ * @param aType FallbackType value for type of fallback we're loading
+ * @param aNotify Send notifications and events. If false, caller is
+ * responsible for doing so
+ */
+ void LoadFallback(FallbackType aType, bool aNotify);
+
+ /**
+ * 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);
+
+ /**
+ * Introspects 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 (codebase attribute)
+ * - 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.
+ *
+ * @param aJavaURI Specify that the URI will be consumed by java, which
+ * changes codebase parsing and URI construction. Used
+ * internally.
+ *
+ * @return Returns a bitmask of ParameterUpdateFlags values
+ */
+ ParameterUpdateFlags UpdateObjectParameters(bool aJavaURI = false);
+
+ /**
+ * Queue a CheckPluginStopEvent and track it in mPendingCheckPluginStopEvent
+ */
+ void QueueCheckPluginStopEvent();
+
+ void NotifyContentObjectWrapper();
+
+ /**
+ * 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();
+
+ /**
+ * If this object is allowed to play plugin content, or if it would display
+ * click-to-play instead.
+ * NOTE that this does not actually check if the object is a loadable plugin
+ * NOTE This ignores the current activated state. The caller should check this if appropriate.
+ */
+ bool ShouldPlay(FallbackType &aReason, bool aIgnoreCurrentType);
+
+ /**
+ * This method tells if the fallback content should be attempted to be used
+ * over the original object content.
+ * It will look at prefs and this plugin's CTP state to make a decision.
+ *
+ * NOTE that this doesn't say whether the fallback _will_ be used, only whether
+ * we should look into it to possibly use it. The final answer will be
+ * given by the PreferFallback method.
+ *
+ * @param aIsPluginClickToPlay Whether this object instance is CTP.
+ */
+ bool FavorFallbackMode(bool aIsPluginClickToPlay);
+
+ /**
+ * Whether the page has provided good fallback content to this object.
+ */
+ bool HasGoodFallback();
+
+ /**
+ * 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 mBaseURI can be used by java as a codebase
+ */
+ bool CheckJavaCodebase();
+
+ /**
+ * 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);
+
+ /**
+ * Checks whether the given type is a supported document type
+ *
+ * NOTE Does not take content policy or capabilities into account
+ */
+ bool IsSupportedDocument(const nsCString& aType);
+
+ /**
+ * Gets the plugin instance and creates a plugin stream listener, assigning
+ * it to mFinalListener
+ */
+ bool MakePluginListener();
+
+ /**
+ * 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 aSync If a synchronous frame construction is required. If false,
+ * the construction may either be sync or async.
+ * @param aNotify if false, only need to update the state of our element.
+ */
+ void NotifyStateChanged(ObjectType aOldType,
+ mozilla::EventStates aOldState,
+ bool aSync, bool aNotify);
+
+ /**
+ * 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
+ *
+ * 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);
+
+ /**
+ * Gets the frame that's associated with this content node.
+ * Does not flush.
+ */
+ nsPluginFrame* GetExistingFrame();
+
+ /**
+ * 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, and without enablejsapi=1 in the path
+ *
+ * Having the enablejsapi flag means the document that contains the element
+ * could possibly be manipulating the youtube video elsewhere on the page
+ * via javascript. We can't rewrite these kinds of elements without possibly
+ * breaking content, which we want to avoid.
+ *
+ * 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);
+
+ // Helper class for SetupProtoChain
+ class SetupProtoChainRunner final : public nsIRunnable
+ {
+ ~SetupProtoChainRunner();
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit SetupProtoChainRunner(nsObjectLoadingContent* aContent);
+
+ NS_IMETHOD Run() override;
+
+ private:
+ // We store an nsIObjectLoadingContent because we can
+ // unambiguously refcount that.
+ RefPtr<nsIObjectLoadingContent> mContent;
+ };
+
+ // Utility getter for getting our nsNPAPIPluginInstance in a safe way.
+ nsresult ScriptRequestPluginInstance(JSContext* aCx,
+ nsNPAPIPluginInstance** aResult);
+
+ // Utility method for getting our plugin JSObject
+ static nsresult GetPluginJSObject(JSContext *cx,
+ JS::Handle<JSObject*> obj,
+ nsNPAPIPluginInstance *plugin_inst,
+ JS::MutableHandle<JSObject*> plugin_obj,
+ JS::MutableHandle<JSObject*> plugin_proto);
+
+ // Utility for firing an error event, if we're an <object>.
+ void MaybeFireErrorEvent();
+
+ // The final listener for mChannel (uriloader, pluginstreamlistener, etc.)
+ nsCOMPtr<nsIStreamListener> mFinalListener;
+
+ // Frame loader, for content documents we load.
+ RefPtr<nsFrameLoader> mFrameLoader;
+
+ // Track if we have a pending AsyncInstantiateEvent
+ nsCOMPtr<nsIRunnable> mPendingInstantiateEvent;
+
+ // Tracks if we have a pending CheckPluginStopEvent
+ nsCOMPtr<nsIRunnable> mPendingCheckPluginStopEvent;
+
+ // 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 (codebase, and
+ // src/data). May differ from mURI due to redirects
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ // The baseURI used for constructing mURI, and used by some plugins (java)
+ // as a root for other resource requests.
+ nsCOMPtr<nsIURI> mBaseURI;
+
+
+
+ // Type of the currently-loaded content.
+ ObjectType mType : 8;
+ // The type of fallback content we're showing (see ObjectState())
+ FallbackType mFallbackType : 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;
+
+ // Whether we are about to call instantiate on our frame. If we aren't,
+ // SetFrame needs to asynchronously call Instantiate.
+ bool mInstantiating : 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;
+
+ // Used to keep track of whether or not a plugin has been explicitly
+ // activated by PlayPlugin(). (see ShouldPlay())
+ bool mActivated : 1;
+
+ // Whether content blocking is enabled or not for this object.
+ bool mContentBlockingEnabled : 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;
+
+ // Cache the answer of PreferFallback() because ShouldPlay is called several
+ // times during the load process.
+ bool mPreferFallback : 1;
+ bool mPreferFallbackKnown : 1;
+
+ nsWeakFrame mPrintFrame;
+
+ RefPtr<nsPluginInstanceOwner> mInstanceOwner;
+ nsTArray<mozilla::dom::MozPluginParameter> mCachedAttributes;
+ nsTArray<mozilla::dom::MozPluginParameter> mCachedParameters;
+};
+
+#endif
diff --git a/dom/base/nsOpenURIInFrameParams.cpp b/dom/base/nsOpenURIInFrameParams.cpp
new file mode 100644
index 000000000..fafd98d3f
--- /dev/null
+++ b/dom/base/nsOpenURIInFrameParams.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 "nsOpenURIInFrameParams.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/ToJSValue.h"
+
+NS_IMPL_ISUPPORTS(nsOpenURIInFrameParams, nsIOpenURIInFrameParams)
+
+nsOpenURIInFrameParams::nsOpenURIInFrameParams(const mozilla::DocShellOriginAttributes& aOriginAttributes)
+ : mOpenerOriginAttributes(aOriginAttributes)
+ , mIsPrivate(false)
+{
+}
+
+nsOpenURIInFrameParams::~nsOpenURIInFrameParams() {
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetReferrer(nsAString& aReferrer)
+{
+ aReferrer = mReferrer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::SetReferrer(const nsAString& aReferrer)
+{
+ mReferrer = aReferrer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetIsPrivate(bool* aIsPrivate)
+{
+ NS_ENSURE_ARG_POINTER(aIsPrivate);
+ *aIsPrivate = mIsPrivate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::SetIsPrivate(bool aIsPrivate)
+{
+ mIsPrivate = aIsPrivate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOpenURIInFrameParams::GetOpenerOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue)
+{
+ bool ok = ToJSValue(aCx, mOpenerOriginAttributes, aValue);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+ return NS_OK;
+}
diff --git a/dom/base/nsOpenURIInFrameParams.h b/dom/base/nsOpenURIInFrameParams.h
new file mode 100644
index 000000000..5d6c299b0
--- /dev/null
+++ b/dom/base/nsOpenURIInFrameParams.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/. */
+
+#include "mozilla/BasePrincipal.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsString.h"
+
+namespace mozilla {
+class DocShellOriginAttributes;
+}
+
+class nsOpenURIInFrameParams final : public nsIOpenURIInFrameParams
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOPENURIINFRAMEPARAMS
+
+ explicit nsOpenURIInFrameParams(const mozilla::DocShellOriginAttributes& aOriginAttributes);
+
+private:
+ ~nsOpenURIInFrameParams();
+
+ mozilla::DocShellOriginAttributes mOpenerOriginAttributes;
+ nsString mReferrer;
+ bool mIsPrivate;
+};
diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
new file mode 100644
index 000000000..45823057a
--- /dev/null
+++ b/dom/base/nsPIDOMWindow.h
@@ -0,0 +1,1007 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/dom/EventTarget.h"
+#include "js/TypeDecls.h"
+#include "nsRefPtrHashtable.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 nsGlobalWindow;
+class nsIArray;
+class nsIContent;
+class nsICSSDeclaration;
+class nsIDocShell;
+class nsIDocShellLoadInfo;
+class nsIDocument;
+class nsIIdleObserver;
+class nsIPrincipal;
+class nsIScriptTimeoutHandler;
+class nsIURI;
+class nsPIDOMWindowInner;
+class nsPIDOMWindowOuter;
+class nsPIWindowRoot;
+class nsXBLPrototypeHandler;
+
+typedef uint32_t SuspendTypes;
+
+namespace mozilla {
+class ThrottledEventQueue;
+namespace dom {
+class AudioContext;
+class DocGroup;
+class TabGroup;
+class Element;
+class Performance;
+class ServiceWorkerRegistration;
+class Timeout;
+class CustomElementRegistry;
+enum class CallerType : uint32_t;
+} // namespace dom
+} // namespace mozilla
+
+// 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 (see
+// nsGlobalWindow::PushPopupControlState()).
+enum PopupControlState {
+ openAllowed = 0, // open that window without worries
+ openControlled, // it's a popup, but allow it
+ openAbused, // it's a popup. disallow it, but allow domain override.
+ openOverridden // disallow window open
+};
+
+enum UIStateChangeType
+{
+ UIStateChangeType_NoChange,
+ UIStateChangeType_Set,
+ UIStateChangeType_Clear,
+ UIStateChangeType_Invalid // used for serialization only
+};
+
+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
+};
+
+
+// nsPIDOMWindowInner and nsPIDOMWindowOuter are identical in all respects
+// except for the type name. They *must* remain identical so that we can
+// reinterpret_cast between them.
+template<class T>
+class nsPIDOMWindow : public T
+{
+public:
+ nsPIDOMWindowInner* AsInner();
+ const nsPIDOMWindowInner* AsInner() const;
+ nsPIDOMWindowOuter* AsOuter();
+ const nsPIDOMWindowOuter* AsOuter() const;
+
+ virtual nsPIDOMWindowOuter* GetPrivateRoot() = 0;
+ virtual mozilla::dom::CustomElementRegistry* CustomElements() = 0;
+ // Outer windows only.
+ virtual void ActivateOrDeactivate(bool aActivate) = 0;
+
+ // this is called GetTopWindowRoot to avoid conflicts with nsIDOMWindow::GetWindowRoot
+ /**
+ * |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 GetScriptableTop, 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> GetTop() = 0; // Outer only
+ virtual already_AddRefed<nsPIDOMWindowOuter> GetParent() = 0;
+ virtual nsPIDOMWindowOuter* GetScriptableTop() = 0;
+ virtual nsPIDOMWindowOuter* GetScriptableParent() = 0;
+ virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() = 0;
+
+ bool IsRootOuterWindow()
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ return mIsRootOuterWindow;
+ }
+
+ /**
+ * Behavies identically to GetScriptableParent extept that it returns null
+ * if GetScriptableParent would return this window.
+ */
+ virtual nsPIDOMWindowOuter* GetScriptableParentOrNull() = 0;
+
+ // Inner windows only.
+ virtual nsresult RegisterIdleObserver(nsIIdleObserver* aIdleObserver) = 0;
+ virtual nsresult UnregisterIdleObserver(nsIIdleObserver* aIdleObserver) = 0;
+
+ virtual bool IsTopLevelWindowActive() = 0;
+
+ // Outer windows only.
+ virtual void SetActive(bool aActive)
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ mIsActive = aActive;
+ }
+
+ virtual void SetIsBackground(bool aIsBackground)
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ mIsBackground = aIsBackground;
+ }
+
+ mozilla::dom::EventTarget* GetChromeEventHandler() const
+ {
+ return mChromeEventHandler;
+ }
+
+ // Outer windows only.
+ virtual void SetChromeEventHandler(mozilla::dom::EventTarget* aChromeEventHandler) = 0;
+
+ mozilla::dom::EventTarget* GetParentTarget()
+ {
+ if (!mParentTarget) {
+ UpdateParentTarget();
+ }
+ return mParentTarget;
+ }
+
+ virtual void MaybeUpdateTouchState() {}
+
+ nsIDocument* GetExtantDoc() const
+ {
+ return mDoc;
+ }
+ nsIURI* GetDocumentURI() const;
+ nsIURI* GetDocBaseURI() const;
+
+ nsIDocument* GetDoc()
+ {
+ if (!mDoc) {
+ MaybeCreateDoc();
+ }
+ return mDoc;
+ }
+
+ virtual bool IsRunningTimeout() = 0;
+
+protected:
+ // Lazily instantiate an about:blank document if necessary, and if
+ // we have what it takes to do so.
+ void MaybeCreateDoc();
+
+public:
+ inline bool IsLoadingOrRunningTimeout() const;
+
+ // Check whether a document is currently loading
+ inline bool IsLoading() const;
+ inline bool IsHandlingResizeEvent() const;
+
+ // Set the window up with an about:blank document with the current subject
+ // principal.
+ // Outer windows only.
+ virtual void SetInitialPrincipalToSubject() = 0;
+
+ virtual PopupControlState PushPopupControlState(PopupControlState aState,
+ bool aForce) const = 0;
+ virtual void PopPopupControlState(PopupControlState state) const = 0;
+ virtual PopupControlState GetPopupControlState() const = 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() = 0;
+
+ nsPIDOMWindowOuter* GetOuterWindow()
+ {
+ return mIsInnerWindow ? mOuterWindow.get() : AsOuter();
+ }
+
+ bool IsInnerWindow() const
+ {
+ return mIsInnerWindow;
+ }
+
+ bool IsOuterWindow() const
+ {
+ return !IsInnerWindow();
+ }
+
+ // Outer windows only.
+ virtual bool WouldReuseInnerWindow(nsIDocument* aNewDocument) = 0;
+
+ /**
+ * Get the docshell in this window.
+ */
+ nsIDocShell *GetDocShell() const;
+
+ /**
+ * Set the docshell in the window. Must not be called with a null docshell
+ * (use DetachFromDocShell for that).
+ */
+ virtual void SetDocShell(nsIDocShell *aDocShell) = 0;
+
+ /**
+ * Detach an outer window from its docshell.
+ */
+ virtual void DetachFromDocShell() = 0;
+
+ /**
+ * Set a new document in the window. Calling this method will in
+ * most cases create a new inner window. If this method is called on
+ * an inner window the call will be forewarded to the outer window,
+ * if the inner window is not the current inner window an
+ * NS_ERROR_NOT_AVAILABLE error code will be returned. 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(nsIDocument *aDocument,
+ nsISupports *aState,
+ bool aForceReuseInnerWindow) = 0;
+
+ /**
+ * Set the opener window. aOriginalOpener is true if and only if this is the
+ * original opener for the window. That is, it can only be true at most once
+ * during the life cycle of a window, and then only the first time
+ * SetOpenerWindow is called. It might never be true, of course, if the
+ * window does not have an opener when it's created.
+ */
+ virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
+ bool aOriginalOpener) = 0;
+
+ virtual void EnsureSizeUpToDate() = 0;
+
+ /**
+ * Callback for notifying a window about a modal dialog being
+ * opened/closed with the window as a parent.
+ */
+ virtual void EnterModalState() = 0;
+ virtual void LeaveModalState() = 0;
+
+ // Outer windows only.
+ virtual bool CanClose() = 0;
+ virtual void ForceClose() = 0;
+
+ bool IsModalContentWindow() const
+ {
+ return mIsModalContentWindow;
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * Moves the top-level window into fullscreen mode if aIsFullScreen is true,
+ * otherwise exits fullscreen.
+ *
+ * Outer windows only.
+ */
+ virtual nsresult SetFullscreenInternal(
+ FullscreenReason aReason, 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.
+ *
+ * Outer windows only.
+ */
+ virtual void FinishFullscreenChange(bool aIsFullscreen) = 0;
+
+ virtual JSObject* GetCachedXBLPrototypeHandler(nsXBLPrototypeHandler* aKey) = 0;
+ virtual void CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey,
+ JS::Handle<JSObject*> aHandler) = 0;
+
+ /*
+ * 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.
+ */
+ nsIContent* GetFocusedNode() const;
+ virtual void SetFocusedNode(nsIContent* aNode,
+ uint32_t aFocusMethod = 0,
+ bool aNeedsFocus = false) = 0;
+
+ /**
+ * 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;
+
+ /**
+ * Set the keyboard indicator state for accelerators and focus rings.
+ */
+ virtual void SetKeyboardIndicators(UIStateChangeType aShowAccelerators,
+ UIStateChangeType aShowFocusRings) = 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.
+ *
+ * Inner windows only.
+ */
+ virtual void EnableDeviceSensor(uint32_t aType) = 0;
+
+ /**
+ * Tell this window that it should remove itself from sensor change
+ * notifications.
+ *
+ * Inner windows only.
+ */
+ virtual void DisableDeviceSensor(uint32_t aType) = 0;
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+ virtual void EnableOrientationChangeListener() = 0;
+ virtual void DisableOrientationChangeListener() = 0;
+#endif
+
+ virtual void EnableTimeChangeNotifications() = 0;
+ virtual void DisableTimeChangeNotifications() = 0;
+
+#ifdef MOZ_B2G
+ /**
+ * Tell the window that it should start to listen to the network event of the
+ * given aType.
+ *
+ * Inner windows only.
+ */
+ virtual void EnableNetworkEvent(mozilla::EventMessage aEventMessage) = 0;
+
+ /**
+ * Tell the window that it should stop to listen to the network event of the
+ * given aType.
+ *
+ * Inner windows only.
+ */
+ virtual void DisableNetworkEvent(mozilla::EventMessage aEventMessage) = 0;
+#endif // MOZ_B2G
+
+ /**
+ * Tell this window that there is an observer for gamepad input
+ *
+ * Inner windows only.
+ */
+ virtual void SetHasGamepadEventListener(bool aHasGamepad = true) = 0;
+
+ /**
+ * 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 serves double-duty for passing both |arguments| and
+ * |dialogArguments| back from nsWindowWatcher to nsGlobalWindow. For the
+ * latter, the array is an array of length 0 whose only element is a
+ * DialogArgumentsHolder representing the JS value passed to showModalDialog.
+ *
+ * Outer windows only.
+ */
+ virtual nsresult SetArguments(nsIArray *aArguments) = 0;
+
+ /**
+ * NOTE! This function *will* be called on multiple threads so the
+ * implementation must not do any AddRef/Release or other actions that will
+ * mutate internal state.
+ */
+ virtual uint32_t GetSerial() = 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) = 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, nsPIDOMWindowOuter **_retval) = 0;
+
+ /**
+ * Fire a popup blocked event on the document.
+ */
+ virtual void
+ FirePopupBlockedEvent(nsIDocument* 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;
+ }
+
+ virtual nsIDOMScreen* GetScreen() = 0;
+ virtual nsIDOMNavigator* GetNavigator() = 0;
+ virtual nsIDOMLocation* GetLocation() = 0;
+ virtual nsresult GetPrompter(nsIPrompt** aPrompt) = 0;
+ virtual nsresult GetControllers(nsIControllers** aControllers) = 0;
+ virtual already_AddRefed<nsISelection> GetSelection() = 0;
+ virtual already_AddRefed<nsPIDOMWindowOuter> GetOpener() = 0;
+ virtual already_AddRefed<nsIDOMWindowCollection> GetFrames() = 0;
+ // aLoadInfo 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,
+ nsIDocShellLoadInfo* aLoadInfo,
+ bool aForceNoOpener,
+ nsPIDOMWindowOuter **_retval) = 0;
+ virtual nsresult OpenDialog(const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions,
+ nsISupports* aExtraArgument,
+ nsPIDOMWindowOuter** _retval) = 0;
+
+ virtual nsresult GetInnerWidth(int32_t* aWidth) = 0;
+ virtual nsresult GetInnerHeight(int32_t* aHeight) = 0;
+ virtual already_AddRefed<nsICSSDeclaration>
+ GetComputedStyle(mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
+ mozilla::ErrorResult& aError) = 0;
+ virtual already_AddRefed<nsIDOMElement> GetFrameElement() = 0;
+ virtual already_AddRefed<nsIDOMOfflineResourceList> GetApplicationCache() = 0;
+ virtual bool Closed() = 0;
+ virtual bool GetFullScreen() = 0;
+ virtual nsresult SetFullScreen(bool aFullScreen) = 0;
+
+ virtual nsresult Focus() = 0;
+ virtual nsresult Close() = 0;
+
+ virtual nsresult MoveBy(int32_t aXDif, int32_t aYDif) = 0;
+ virtual nsresult UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason) = 0;
+
+ mozilla::dom::TabGroup* TabGroup();
+
+ mozilla::dom::DocGroup* GetDocGroup();
+
+ virtual mozilla::ThrottledEventQueue* GetThrottledEventQueue() = 0;
+
+protected:
+ // The nsPIDOMWindow constructor. The aOuterWindow argument should
+ // be null if and only if the created window itself is an outer
+ // window. In all other cases aOuterWindow should be the outer
+ // window for the inner window that is being created.
+ explicit nsPIDOMWindow<T>(nsPIDOMWindowOuter *aOuterWindow);
+
+ ~nsPIDOMWindow<T>();
+
+ 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
+ nsCOMPtr<nsIDocument> mDoc; // strong
+ // Cache the URI when mDoc is cleared.
+ nsCOMPtr<nsIURI> mDocumentURI; // strong
+ nsCOMPtr<nsIURI> mDocBaseURI; // strong
+
+ nsCOMPtr<mozilla::dom::EventTarget> mParentTarget; // strong
+
+ // These members are only used on outer windows.
+ nsCOMPtr<mozilla::dom::Element> mFrameElement;
+ // This reference is used by the subclass nsGlobalWindow, and cleared in it's
+ // DetachFromDocShell() method. This method is called by nsDocShell::Destroy(),
+ // which is called before the nsDocShell is destroyed.
+ nsIDocShell* MOZ_NON_OWNING_REF mDocShell; // Weak Reference
+
+ // mPerformance is only used on inner windows.
+ RefPtr<mozilla::dom::Performance> mPerformance;
+
+ typedef nsRefPtrHashtable<nsStringHashKey,
+ mozilla::dom::ServiceWorkerRegistration>
+ ServiceWorkerRegistrationTable;
+ ServiceWorkerRegistrationTable mServiceWorkerRegistrationTable;
+
+ uint32_t mModalStateDepth;
+
+ // These variables are only used on inner windows.
+ mozilla::dom::Timeout *mRunningTimeout;
+
+ uint32_t mMutationBits;
+
+ bool mIsDocumentLoaded;
+ bool mIsHandlingResizeEvent;
+ bool mIsInnerWindow;
+ bool mMayHavePaintEventListener;
+ bool mMayHaveTouchEventListener;
+ bool mMayHaveMouseEnterLeaveEventListener;
+ bool mMayHavePointerEnterLeaveEventListener;
+
+ // Used to detect whether we have called FreeInnerObjects() (e.g. to ensure
+ // that a call to ResumeTimeouts() after FreeInnerObjects() does nothing).
+ // This member is only used by inner windows.
+ bool mInnerObjectsFreed;
+
+
+ // This variable is used on both inner and outer windows (and they
+ // should match).
+ bool mIsModalContentWindow;
+
+ // Tracks activation state that's used for :-moz-window-inactive.
+ // Only used on outer windows.
+ bool mIsActive;
+
+ // Tracks whether our docshell is active. If it is, mIsBackground
+ // is false. Too bad we have so many different concepts of
+ // "active". Only used on outer windows.
+ bool mIsBackground;
+
+ /**
+ * The suspended types can be "disposable" or "permanent". This varable only
+ * stores the value about permanent suspend.
+ * - disposable
+ * To pause all playing media in that window, but doesn't affect the media
+ * which starts after that.
+ *
+ * - permanent
+ * To pause all media in that window, and also affect the media which starts
+ * after that.
+ */
+ SuspendTypes mMediaSuspend;
+
+ bool mAudioMuted;
+ float mAudioVolume;
+
+ bool mAudioCaptured;
+
+ // current desktop mode flag.
+ bool mDesktopModeViewport;
+
+ bool mIsRootOuterWindow;
+
+ // And these are the references between inner and outer windows.
+ nsPIDOMWindowInner* MOZ_NON_OWNING_REF mInnerWindow;
+ nsCOMPtr<nsPIDOMWindowOuter> mOuterWindow;
+
+ // the element within the document that is currently focused when this
+ // window is active
+ nsCOMPtr<nsIContent> mFocusedNode;
+
+ // The AudioContexts created for the current document, if any.
+ nsTArray<mozilla::dom::AudioContext*> mAudioContexts; // Weak
+
+ // This is present both on outer and inner windows.
+ RefPtr<mozilla::dom::TabGroup> mTabGroup;
+
+ // A unique (as long as our 64-bit counter doesn't roll over) id for
+ // this window.
+ uint64_t mWindowID;
+
+ // This is only used by the inner window. Set to true once we've sent
+ // the (chrome|content)-document-global-created notification.
+ bool mHasNotifiedGlobalCreated;
+
+ uint32_t mMarkedCCGeneration;
+
+ // Let the service workers plumbing know that some feature are enabled while
+ // testing.
+ bool mServiceWorkersTestingEnabled;
+};
+
+#define NS_PIDOMWINDOWINNER_IID \
+{ 0x775dabc9, 0x8f43, 0x4277, \
+ { 0x9a, 0xdb, 0xf1, 0x99, 0x0d, 0x77, 0xcf, 0xfb } }
+
+#define NS_PIDOMWINDOWOUTER_IID \
+ { 0x769693d4, 0xb009, 0x4fe2, \
+ { 0xaf, 0x18, 0x7d, 0xc8, 0xdf, 0x74, 0x96, 0xdf } }
+
+// NB: It's very very important that these two classes have identical vtables
+// and memory layout!
+class nsPIDOMWindowInner : public nsPIDOMWindow<mozIDOMWindow>
+{
+ friend nsGlobalWindow;
+
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOWINNER_IID)
+
+ static nsPIDOMWindowInner* From(mozIDOMWindow* aFrom) {
+ return static_cast<nsPIDOMWindowInner*>(aFrom);
+ }
+
+ // Returns true if this object has an outer window and it is the current inner
+ // window of that outer.
+ inline bool IsCurrentInnerWindow() const;
+
+ // Returns true if the document of this window is the active document. This
+ // is not identical to IsCurrentInnerWindow() because document.open() will
+ // keep the same document active but create a new window.
+ inline bool HasActiveDocument();
+
+ bool AddAudioContext(mozilla::dom::AudioContext* aAudioContext);
+ void RemoveAudioContext(mozilla::dom::AudioContext* aAudioContext);
+ void MuteAudioContexts();
+ void UnmuteAudioContexts();
+
+ bool GetAudioCaptured() const;
+ nsresult SetAudioCapture(bool aCapture);
+
+ already_AddRefed<mozilla::dom::ServiceWorkerRegistration>
+ GetServiceWorkerRegistration(const nsAString& aScope);
+ void InvalidateServiceWorkerRegistration(const nsAString& aScope);
+
+ mozilla::dom::Performance* GetPerformance();
+
+ 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()
+ {
+ 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()
+ {
+ 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;
+ }
+
+ /**
+ * Check whether this has had inner objects freed.
+ */
+ bool InnerObjectsFreed() const
+ {
+ return mInnerObjectsFreed;
+ }
+
+ /**
+ * 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 also suspends its children and workers. 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();
+ void Resume();
+
+ // Calling Freeze() on a window will automatically Suspend() it. In
+ // addition, the window and its children 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();
+ void Thaw();
+
+ // Apply the parent window's suspend, freeze, and modal state to the current
+ // window.
+ void SyncStateFromParentWindow();
+
+protected:
+ void CreatePerformanceObjectIfNeeded();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
+
+// NB: It's very very important that these two classes have identical vtables
+// and memory layout!
+class nsPIDOMWindowOuter : public nsPIDOMWindow<mozIDOMWindowProxy>
+{
+protected:
+ void RefreshMediaElementsVolume();
+ void RefreshMediaElementsSuspend(SuspendTypes aSuspend);
+ bool IsDisposableSuspend(SuspendTypes aSuspend) const;
+
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOWOUTER_IID)
+
+ 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);
+
+ nsPIDOMWindowInner* GetCurrentInnerWindow() const
+ {
+ return mInnerWindow;
+ }
+
+ nsPIDOMWindowInner* EnsureInnerWindow()
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ // GetDoc forces inner window creation if there isn't one already
+ GetDoc();
+ return GetCurrentInnerWindow();
+ }
+
+ /**
+ * Set initial keyboard indicator state for accelerators and focus rings.
+ */
+ void SetInitialKeyboardIndicators(UIStateChangeType aShowAccelerators,
+ UIStateChangeType aShowFocusRings);
+
+ // 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 IsActive()
+ {
+ return mIsActive;
+ }
+
+ void SetDesktopModeViewport(bool aDesktopModeViewport)
+ {
+ mDesktopModeViewport = aDesktopModeViewport;
+ }
+ bool IsDesktopModeViewport() const
+ {
+ return mDesktopModeViewport;
+ }
+ bool IsBackground()
+ {
+ return mIsBackground;
+ }
+
+ // Audio API
+ SuspendTypes GetMediaSuspend() const;
+ void SetMediaSuspend(SuspendTypes aSuspend);
+
+ bool GetAudioMuted() const;
+ void SetAudioMuted(bool aMuted);
+
+ float GetAudioVolume() const;
+ nsresult SetAudioVolume(float aVolume);
+
+ void SetServiceWorkersTestingEnabled(bool aEnabled);
+ bool GetServiceWorkersTestingEnabled();
+
+ float GetDevicePixelRatio(mozilla::dom::CallerType aCallerType);
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowOuter, NS_PIDOMWINDOWOUTER_IID)
+
+#include "nsPIDOMWindowInlines.h"
+
+#ifdef MOZILLA_INTERNAL_API
+PopupControlState
+PushPopupControlState(PopupControlState aState, bool aForce);
+
+void
+PopPopupControlState(PopupControlState aState);
+
+#define NS_AUTO_POPUP_STATE_PUSHER nsAutoPopupStatePusherInternal
+#else
+#define NS_AUTO_POPUP_STATE_PUSHER nsAutoPopupStatePusherExternal
+#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
+// |nsAutoPopupStatePusher|.
+class NS_AUTO_POPUP_STATE_PUSHER
+{
+public:
+#ifdef MOZILLA_INTERNAL_API
+ explicit NS_AUTO_POPUP_STATE_PUSHER(PopupControlState aState, bool aForce = false)
+ : mOldState(::PushPopupControlState(aState, aForce))
+ {
+ }
+
+ ~NS_AUTO_POPUP_STATE_PUSHER()
+ {
+ PopPopupControlState(mOldState);
+ }
+#else
+ NS_AUTO_POPUP_STATE_PUSHER(nsPIDOMWindowOuter *aWindow, PopupControlState aState)
+ : mWindow(aWindow), mOldState(openAbused)
+ {
+ if (aWindow) {
+ mOldState = aWindow->PushPopupControlState(aState, false);
+ }
+ }
+
+ ~NS_AUTO_POPUP_STATE_PUSHER()
+ {
+ if (mWindow) {
+ mWindow->PopPopupControlState(mOldState);
+ }
+ }
+#endif
+
+protected:
+#ifndef MOZILLA_INTERNAL_API
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+#endif
+ PopupControlState mOldState;
+
+private:
+ // Hide so that this class can only be stack-allocated
+ static void* operator new(size_t /*size*/) CPP_THROW_NEW { return nullptr; }
+ static void operator delete(void* /*memory*/) {}
+};
+
+#define nsAutoPopupStatePusher NS_AUTO_POPUP_STATE_PUSHER
+
+#endif // nsPIDOMWindow_h__
diff --git a/dom/base/nsPIDOMWindowInlines.h b/dom/base/nsPIDOMWindowInlines.h
new file mode 100644
index 000000000..fc4afa7ed
--- /dev/null
+++ b/dom/base/nsPIDOMWindowInlines.h
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+template<class T>
+nsPIDOMWindowInner*
+nsPIDOMWindow<T>::AsInner()
+{
+ MOZ_ASSERT(IsInnerWindow());
+ return reinterpret_cast<nsPIDOMWindowInner*>(this);
+}
+
+template<class T>
+const nsPIDOMWindowInner*
+nsPIDOMWindow<T>::AsInner() const
+{
+ MOZ_ASSERT(IsInnerWindow());
+ return reinterpret_cast<const nsPIDOMWindowInner*>(this);
+}
+
+template<class T>
+nsPIDOMWindowOuter*
+nsPIDOMWindow<T>::AsOuter()
+{
+ MOZ_ASSERT(IsOuterWindow());
+ return reinterpret_cast<nsPIDOMWindowOuter*>(this);
+}
+
+template<class T>
+const nsPIDOMWindowOuter*
+nsPIDOMWindow<T>::AsOuter() const
+{
+ MOZ_ASSERT(IsOuterWindow());
+ return reinterpret_cast<const nsPIDOMWindowOuter*>(this);
+}
+
+template <class T>
+bool
+nsPIDOMWindow<T>::IsLoadingOrRunningTimeout() const
+{
+ if (IsOuterWindow()) {
+ return AsOuter()->GetCurrentInnerWindow()->IsLoadingOrRunningTimeout();
+ }
+ return !mIsDocumentLoaded || mRunningTimeout;
+}
+
+template <class T>
+bool
+nsPIDOMWindow<T>::IsLoading() const
+{
+ if (IsOuterWindow()) {
+ auto* win = AsOuter()->GetCurrentInnerWindow();
+
+ if (!win) {
+ NS_ERROR("No current inner window available!");
+
+ return false;
+ }
+
+ return win->IsLoading();
+ }
+
+ if (!mOuterWindow) {
+ NS_ERROR("IsLoading() called on orphan inner window!");
+
+ return false;
+ }
+
+ return !mIsDocumentLoaded;
+}
+
+template <class T>
+bool
+nsPIDOMWindow<T>::IsHandlingResizeEvent() const
+{
+ if (IsOuterWindow()) {
+ auto* win = AsOuter()->GetCurrentInnerWindow();
+
+ if (!win) {
+ NS_ERROR("No current inner window available!");
+
+ return false;
+ }
+
+ return win->IsHandlingResizeEvent();
+ }
+
+ if (!mOuterWindow) {
+ NS_ERROR("IsHandlingResizeEvent() called on orphan inner window!");
+
+ return false;
+ }
+
+ return mIsHandlingResizeEvent;
+}
+
+bool
+nsPIDOMWindowInner::IsCurrentInnerWindow() const
+{
+ return mOuterWindow && mOuterWindow->GetCurrentInnerWindow() == AsInner();
+}
+
+bool
+nsPIDOMWindowInner::HasActiveDocument()
+{
+ return IsCurrentInnerWindow() ||
+ (mOuterWindow &&
+ mOuterWindow->GetCurrentInnerWindow() &&
+ mOuterWindow->GetCurrentInnerWindow()->GetDoc() == mDoc);
+}
+
+template <class T>
+nsIDocShell*
+nsPIDOMWindow<T>::GetDocShell() const
+{
+ if (mOuterWindow) {
+ return mOuterWindow->GetDocShell();
+ }
+
+ return mDocShell;
+}
+
+template <class T>
+nsIContent*
+nsPIDOMWindow<T>::GetFocusedNode() const
+{
+ if (IsOuterWindow()) {
+ return mInnerWindow ? mInnerWindow->GetFocusedNode() : nullptr;
+ }
+
+ return mFocusedNode;
+}
diff --git a/dom/base/nsPIWindowRoot.h b/dom/base/nsPIWindowRoot.h
new file mode 100644
index 000000000..ec39e48a8
--- /dev/null
+++ b/dom/base/nsPIWindowRoot.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 nsPIWindowRoot_h__
+#define nsPIWindowRoot_h__
+
+#include "nsISupports.h"
+#include "mozilla/dom/EventTarget.h"
+#include "nsWeakReference.h"
+
+class nsPIDOMWindowOuter;
+class nsIControllers;
+class nsIController;
+
+namespace mozilla {
+namespace dom {
+class TabParent;
+} // namespace dom
+} // namespace mozilla
+
+#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)
+
+ virtual nsPIDOMWindowOuter* GetWindow()=0;
+
+ // get and set the node that is the context of a popup menu
+ virtual nsIDOMNode* GetPopupNode() = 0;
+ virtual void SetPopupNode(nsIDOMNode* aNode) = 0;
+
+ virtual nsresult GetControllerForCommand(const char *aCommand,
+ nsIController** aResult) = 0;
+ virtual nsresult GetControllers(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(mozilla::dom::TabParent* aBrowser) = 0;
+ virtual void RemoveBrowser(mozilla::dom::TabParent* aBrowser) = 0;
+
+ typedef void (*BrowserEnumerator)(mozilla::dom::TabParent* aTab, void* aArg);
+
+ // Enumerate all stored browsers that for which the weak reference is valid.
+ virtual void EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg) = 0;
+
+ virtual bool ShowAccelerators() = 0;
+ virtual bool ShowFocusRings() = 0;
+ virtual void SetShowAccelerators(bool aEnable) = 0;
+ virtual void SetShowFocusRings(bool aEnable) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPIWindowRoot, NS_IWINDOWROOT_IID)
+
+#endif // nsPIWindowRoot_h__
diff --git a/dom/base/nsPlainTextSerializer.cpp b/dom/base/nsPlainTextSerializer.cpp
new file mode 100644
index 000000000..ef6bdcac7
--- /dev/null
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -0,0 +1,2034 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsIContentSerializer implementation that can be used with an
+ * nsIDocumentEncoder to convert a DOM into plaintext in a nice way
+ * (eg for copy/paste as plaintext).
+ */
+
+#include "nsPlainTextSerializer.h"
+#include "nsLWBrkCIID.h"
+#include "nsIServiceManager.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsTextFragment.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsCRT.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/BinarySearch.h"
+#include "nsComputedDOMStyle.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define PREF_STRUCTS "converter.html2txt.structs"
+#define PREF_HEADER_STRATEGY "converter.html2txt.header_strategy"
+#define PREF_ALWAYS_INCLUDE_RUBY "converter.html2txt.always_include_ruby"
+
+static const int32_t kTabSize=4;
+static const int32_t kIndentSizeHeaders = 2; /* Indention of h1, if
+ mHeaderStrategy = 1 or = 2.
+ Indention of other headers
+ is derived from that.
+ XXX center h1? */
+static const int32_t kIndentIncrementHeaders = 2; /* If mHeaderStrategy = 1,
+ indent h(x+1) this many
+ columns more than h(x) */
+static const int32_t kIndentSizeList = kTabSize;
+ // Indention of non-first lines of ul and ol
+static const int32_t kIndentSizeDD = kTabSize; // Indention of <dd>
+static const char16_t kNBSP = 160;
+static const char16_t kSPACE = ' ';
+
+static int32_t HeaderLevel(nsIAtom* aTag);
+static int32_t GetUnicharWidth(char16_t ucs);
+static int32_t GetUnicharStringWidth(const char16_t* pwcs, int32_t n);
+
+// Someday may want to make this non-const:
+static const uint32_t TagStackSize = 500;
+static const uint32_t OLStackSize = 100;
+
+nsresult
+NS_NewPlainTextSerializer(nsIContentSerializer** aSerializer)
+{
+ RefPtr<nsPlainTextSerializer> it = new nsPlainTextSerializer();
+ it.forget(aSerializer);
+ return NS_OK;
+}
+
+nsPlainTextSerializer::nsPlainTextSerializer()
+ : kSpace(NS_LITERAL_STRING(" ")) // Init of "constant"
+{
+
+ mOutputString = nullptr;
+ mHeadLevel = 0;
+ mAtFirstColumn = true;
+ mIndent = 0;
+ mCiteQuoteLevel = 0;
+ mStructs = true; // will be read from prefs later
+ mHeaderStrategy = 1 /*indent increasingly*/; // ditto
+ mHasWrittenCiteBlockquote = false;
+ mSpanLevel = 0;
+ for (int32_t i = 0; i <= 6; i++) {
+ mHeaderCounter[i] = 0;
+ }
+
+ // Line breaker
+ mWrapColumn = 72; // XXX magic number, we expect someone to reset this
+ mCurrentLineWidth = 0;
+
+ // Flow
+ mEmptyLines = 1; // The start of the document is an "empty line" in itself,
+ mInWhitespace = false;
+ mPreFormattedMail = false;
+ mStartedOutput = false;
+
+ mPreformattedBlockBoundary = false;
+ mWithRubyAnnotation = false; // will be read from pref and flag later
+
+ // initialize the tag stack to zero:
+ // The stack only ever contains pointers to static atoms, so they don't
+ // need refcounting.
+ mTagStack = new nsIAtom*[TagStackSize];
+ mTagStackIndex = 0;
+ mIgnoreAboveIndex = (uint32_t)kNotFound;
+
+ // initialize the OL stack, where numbers for ordered lists are kept
+ mOLStack = new int32_t[OLStackSize];
+ mOLStackIndex = 0;
+
+ mULCount = 0;
+
+ mIgnoredChildNodeLevel = 0;
+}
+
+nsPlainTextSerializer::~nsPlainTextSerializer()
+{
+ delete[] mTagStack;
+ delete[] mOLStack;
+ NS_WARNING_ASSERTION(mHeadLevel == 0, "Wrong head level!");
+}
+
+NS_IMPL_ISUPPORTS(nsPlainTextSerializer,
+ nsIContentSerializer)
+
+
+NS_IMETHODIMP
+nsPlainTextSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
+ const char* aCharSet, bool aIsCopying,
+ bool aIsWholeDocument)
+{
+#ifdef DEBUG
+ // Check if the major control flags are set correctly.
+ if (aFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+ NS_ASSERTION(aFlags & nsIDocumentEncoder::OutputFormatted,
+ "If you want format=flowed, you must combine it with "
+ "nsIDocumentEncoder::OutputFormatted");
+ }
+
+ if (aFlags & nsIDocumentEncoder::OutputFormatted) {
+ NS_ASSERTION(!(aFlags & nsIDocumentEncoder::OutputPreformatted),
+ "Can't do formatted and preformatted output at the same time!");
+ }
+#endif
+
+ mFlags = aFlags;
+ mWrapColumn = aWrapColumn;
+
+ // Only create a linebreaker if we will handle wrapping.
+ if (MayWrap() && MayBreakLines()) {
+ mLineBreaker = nsContentUtils::LineBreaker();
+ }
+
+ // Set the line break character:
+ if ((mFlags & nsIDocumentEncoder::OutputCRLineBreak)
+ && (mFlags & nsIDocumentEncoder::OutputLFLineBreak)) {
+ // Windows
+ mLineBreak.AssignLiteral("\r\n");
+ }
+ else if (mFlags & nsIDocumentEncoder::OutputCRLineBreak) {
+ // Mac
+ mLineBreak.Assign(char16_t('\r'));
+ }
+ else if (mFlags & nsIDocumentEncoder::OutputLFLineBreak) {
+ // Unix/DOM
+ mLineBreak.Assign(char16_t('\n'));
+ }
+ else {
+ // Platform/default
+ mLineBreak.AssignLiteral(NS_LINEBREAK);
+ }
+
+ mLineBreakDue = false;
+ mFloatingLines = -1;
+
+ mPreformattedBlockBoundary = false;
+
+ if (mFlags & nsIDocumentEncoder::OutputFormatted) {
+ // Get some prefs that controls how we do formatted output
+ mStructs = Preferences::GetBool(PREF_STRUCTS, mStructs);
+
+ mHeaderStrategy =
+ Preferences::GetInt(PREF_HEADER_STRATEGY, mHeaderStrategy);
+ }
+
+ // The pref is default inited to false in libpref, but we use true
+ // as fallback value because we don't want to affect behavior in
+ // other places which use this serializer currently.
+ mWithRubyAnnotation =
+ Preferences::GetBool(PREF_ALWAYS_INCLUDE_RUBY, true) ||
+ (mFlags & nsIDocumentEncoder::OutputRubyAnnotation);
+
+ // XXX We should let the caller decide whether to do this or not
+ mFlags &= ~nsIDocumentEncoder::OutputNoFramesContent;
+
+ return NS_OK;
+}
+
+bool
+nsPlainTextSerializer::GetLastBool(const nsTArray<bool>& aStack)
+{
+ uint32_t size = aStack.Length();
+ if (size == 0) {
+ return false;
+ }
+ return aStack.ElementAt(size-1);
+}
+
+void
+nsPlainTextSerializer::SetLastBool(nsTArray<bool>& aStack, bool aValue)
+{
+ uint32_t size = aStack.Length();
+ if (size > 0) {
+ aStack.ElementAt(size-1) = aValue;
+ }
+ else {
+ NS_ERROR("There is no \"Last\" value");
+ }
+}
+
+void
+nsPlainTextSerializer::PushBool(nsTArray<bool>& aStack, bool aValue)
+{
+ aStack.AppendElement(bool(aValue));
+}
+
+bool
+nsPlainTextSerializer::PopBool(nsTArray<bool>& aStack)
+{
+ bool returnValue = false;
+ uint32_t size = aStack.Length();
+ if (size > 0) {
+ returnValue = aStack.ElementAt(size-1);
+ aStack.RemoveElementAt(size-1);
+ }
+ return returnValue;
+}
+
+bool
+nsPlainTextSerializer::ShouldReplaceContainerWithPlaceholder(nsIAtom* aTag)
+{
+ // If nsIDocumentEncoder::OutputNonTextContentAsPlaceholder is set,
+ // non-textual container element should be serialized as placeholder
+ // character and its child nodes should be ignored. See bug 895239.
+ if (!(mFlags & nsIDocumentEncoder::OutputNonTextContentAsPlaceholder)) {
+ return false;
+ }
+
+ return
+ (aTag == nsGkAtoms::audio) ||
+ (aTag == nsGkAtoms::canvas) ||
+ (aTag == nsGkAtoms::iframe) ||
+ (aTag == nsGkAtoms::meter) ||
+ (aTag == nsGkAtoms::progress) ||
+ (aTag == nsGkAtoms::object) ||
+ (aTag == nsGkAtoms::svg) ||
+ (aTag == nsGkAtoms::video);
+}
+
+bool
+nsPlainTextSerializer::IsIgnorableRubyAnnotation(nsIAtom* aTag)
+{
+ if (mWithRubyAnnotation) {
+ return false;
+ }
+
+ return
+ aTag == nsGkAtoms::rp ||
+ aTag == nsGkAtoms::rt ||
+ aTag == nsGkAtoms::rtc;
+}
+
+NS_IMETHODIMP
+nsPlainTextSerializer::AppendText(nsIContent* aText,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr)
+{
+ if (mIgnoreAboveIndex != (uint32_t)kNotFound) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
+ if ( aStartOffset < 0 )
+ return NS_ERROR_INVALID_ARG;
+
+ NS_ENSURE_ARG(aText);
+
+ nsresult rv = NS_OK;
+
+ nsIContent* content = aText;
+ const nsTextFragment* frag;
+ if (!content || !(frag = content->GetText())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t fragLength = frag->GetLength();
+ int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
+ NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!");
+
+ int32_t length = endoffset - aStartOffset;
+ if (length <= 0) {
+ return NS_OK;
+ }
+
+ nsAutoString textstr;
+ if (frag->Is2b()) {
+ textstr.Assign(frag->Get2b() + aStartOffset, length);
+ }
+ else {
+ // AssignASCII is for 7-bit character only, so don't use it
+ const char *data = frag->Get1b();
+ CopyASCIItoUTF16(Substring(data + aStartOffset, data + endoffset), textstr);
+ }
+
+ mOutputString = &aStr;
+
+ // We have to split the string across newlines
+ // to match parser behavior
+ int32_t start = 0;
+ int32_t offset = textstr.FindCharInSet("\n\r");
+ while (offset != kNotFound) {
+
+ if (offset>start) {
+ // Pass in the line
+ DoAddText(false,
+ Substring(textstr, start, offset-start));
+ }
+
+ // Pass in a newline
+ DoAddText(true, mLineBreak);
+
+ start = offset+1;
+ offset = textstr.FindCharInSet("\n\r", start);
+ }
+
+ // Consume the last bit of the string if there's any left
+ if (start < length) {
+ if (start) {
+ DoAddText(false, Substring(textstr, start, length - start));
+ }
+ else {
+ DoAddText(false, textstr);
+ }
+ }
+
+ mOutputString = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPlainTextSerializer::AppendCDATASection(nsIContent* aCDATASection,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr)
+{
+ return AppendText(aCDATASection, aStartOffset, aEndOffset, aStr);
+}
+
+NS_IMETHODIMP
+nsPlainTextSerializer::AppendElementStart(Element* aElement,
+ Element* aOriginalElement,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aElement);
+
+ mElement = aElement;
+
+ nsresult rv;
+ nsIAtom* id = GetIdForContent(mElement);
+
+ bool isContainer = !FragmentOrElement::IsHTMLVoid(id);
+
+ mOutputString = &aStr;
+
+ if (isContainer) {
+ rv = DoOpenContainer(id);
+ mPreformatStack.push(IsElementPreformatted(mElement));
+ }
+ else {
+ rv = DoAddLeaf(id);
+ }
+
+ mElement = nullptr;
+ mOutputString = nullptr;
+
+ if (id == nsGkAtoms::head) {
+ ++mHeadLevel;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPlainTextSerializer::AppendElementEnd(Element* aElement,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aElement);
+
+ mElement = aElement;
+
+ nsresult rv;
+ nsIAtom* id = GetIdForContent(mElement);
+
+ bool isContainer = !FragmentOrElement::IsHTMLVoid(id);
+
+ mOutputString = &aStr;
+
+ rv = NS_OK;
+ if (isContainer) {
+ rv = DoCloseContainer(id);
+ mPreformatStack.pop();
+ }
+
+ mElement = nullptr;
+ mOutputString = nullptr;
+
+ if (id == nsGkAtoms::head) {
+ NS_ASSERTION(mHeadLevel != 0,
+ "mHeadLevel being decremented below 0");
+ --mHeadLevel;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPlainTextSerializer::Flush(nsAString& aStr)
+{
+ mOutputString = &aStr;
+ FlushLine();
+ mOutputString = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPlainTextSerializer::AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr)
+{
+ return NS_OK;
+}
+
+nsresult
+nsPlainTextSerializer::DoOpenContainer(nsIAtom* aTag)
+{
+ // Check if we need output current node as placeholder character and ignore
+ // child nodes.
+ if (ShouldReplaceContainerWithPlaceholder(mElement->NodeInfo()->NameAtom())) {
+ if (mIgnoredChildNodeLevel == 0) {
+ // Serialize current node as placeholder character
+ Write(NS_LITERAL_STRING(u"\xFFFC"));
+ }
+ // Ignore child nodes.
+ mIgnoredChildNodeLevel++;
+ return NS_OK;
+ }
+ if (IsIgnorableRubyAnnotation(aTag)) {
+ // Ignorable ruby annotation shouldn't be replaced by a placeholder
+ // character, neither any of its descendants.
+ mIgnoredChildNodeLevel++;
+ return NS_OK;
+ }
+
+ if (mFlags & nsIDocumentEncoder::OutputForPlainTextClipboardCopy) {
+ if (mPreformattedBlockBoundary && DoOutput()) {
+ // Should always end a line, but get no more whitespace
+ if (mFloatingLines < 0)
+ mFloatingLines = 0;
+ mLineBreakDue = true;
+ }
+ mPreformattedBlockBoundary = false;
+ }
+
+ if (mFlags & nsIDocumentEncoder::OutputRaw) {
+ // Raw means raw. Don't even think about doing anything fancy
+ // here like indenting, adding line breaks or any other
+ // characters such as list item bullets, quote characters
+ // around <q>, etc. I mean it! Don't make me smack you!
+
+ return NS_OK;
+ }
+
+ if (mTagStackIndex < TagStackSize) {
+ mTagStack[mTagStackIndex++] = aTag;
+ }
+
+ if (mIgnoreAboveIndex != (uint32_t)kNotFound) {
+ return NS_OK;
+ }
+
+ // Reset this so that <blockquote type=cite> doesn't affect the whitespace
+ // above random <pre>s below it.
+ mHasWrittenCiteBlockquote = mHasWrittenCiteBlockquote &&
+ aTag == nsGkAtoms::pre;
+
+ bool isInCiteBlockquote = false;
+
+ // XXX special-case <blockquote type=cite> so that we don't add additional
+ // newlines before the text.
+ if (aTag == nsGkAtoms::blockquote) {
+ nsAutoString value;
+ nsresult rv = GetAttributeValue(nsGkAtoms::type, value);
+ isInCiteBlockquote = NS_SUCCEEDED(rv) && value.EqualsIgnoreCase("cite");
+ }
+
+ if (mLineBreakDue && !isInCiteBlockquote)
+ EnsureVerticalSpace(mFloatingLines);
+
+ // Check if this tag's content that should not be output
+ if ((aTag == nsGkAtoms::noscript &&
+ !(mFlags & nsIDocumentEncoder::OutputNoScriptContent)) ||
+ ((aTag == nsGkAtoms::iframe || aTag == nsGkAtoms::noframes) &&
+ !(mFlags & nsIDocumentEncoder::OutputNoFramesContent))) {
+ // Ignore everything that follows the current tag in
+ // question until a matching end tag is encountered.
+ mIgnoreAboveIndex = mTagStackIndex - 1;
+ return NS_OK;
+ }
+
+ if (aTag == nsGkAtoms::body) {
+ // Try to figure out here whether we have a
+ // preformatted style attribute set by Thunderbird.
+ //
+ // Trigger on the presence of a "pre-wrap" in the
+ // style attribute. That's a very simplistic way to do
+ // it, but better than nothing.
+ // Also set mWrapColumn to the value given there
+ // (which arguably we should only do if told to do so).
+ nsAutoString style;
+ int32_t whitespace;
+ if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::style, style)) &&
+ (kNotFound != (whitespace = style.Find("white-space:")))) {
+
+ if (kNotFound != style.Find("pre-wrap", true, whitespace)) {
+#ifdef DEBUG_preformatted
+ printf("Set mPreFormattedMail based on style pre-wrap\n");
+#endif
+ mPreFormattedMail = true;
+ int32_t widthOffset = style.Find("width:");
+ if (widthOffset >= 0) {
+ // We have to search for the ch before the semicolon,
+ // not for the semicolon itself, because nsString::ToInteger()
+ // considers 'c' to be a valid numeric char (even if radix=10)
+ // but then gets confused if it sees it next to the number
+ // when the radix specified was 10, and returns an error code.
+ int32_t semiOffset = style.Find("ch", false, widthOffset+6);
+ int32_t length = (semiOffset > 0 ? semiOffset - widthOffset - 6
+ : style.Length() - widthOffset);
+ nsAutoString widthstr;
+ style.Mid(widthstr, widthOffset+6, length);
+ nsresult err;
+ int32_t col = widthstr.ToInteger(&err);
+
+ if (NS_SUCCEEDED(err)) {
+ mWrapColumn = (uint32_t)col;
+#ifdef DEBUG_preformatted
+ printf("Set wrap column to %d based on style\n", mWrapColumn);
+#endif
+ }
+ }
+ }
+ else if (kNotFound != style.Find("pre", true, whitespace)) {
+#ifdef DEBUG_preformatted
+ printf("Set mPreFormattedMail based on style pre\n");
+#endif
+ mPreFormattedMail = true;
+ mWrapColumn = 0;
+ }
+ }
+ else {
+ /* See comment at end of function. */
+ mInWhitespace = true;
+ mPreFormattedMail = false;
+ }
+
+ return NS_OK;
+ }
+
+ // Keep this in sync with DoCloseContainer!
+ if (!DoOutput()) {
+ return NS_OK;
+ }
+
+ if (aTag == nsGkAtoms::p)
+ EnsureVerticalSpace(1);
+ else if (aTag == nsGkAtoms::pre) {
+ if (GetLastBool(mIsInCiteBlockquote))
+ EnsureVerticalSpace(0);
+ else if (mHasWrittenCiteBlockquote) {
+ EnsureVerticalSpace(0);
+ mHasWrittenCiteBlockquote = false;
+ }
+ else
+ EnsureVerticalSpace(1);
+ }
+ else if (aTag == nsGkAtoms::tr) {
+ PushBool(mHasWrittenCellsForRow, false);
+ }
+ else if (aTag == nsGkAtoms::td || aTag == nsGkAtoms::th) {
+ // We must make sure that the content of two table cells get a
+ // space between them.
+
+ // To make the separation between cells most obvious and
+ // importable, we use a TAB.
+ if (GetLastBool(mHasWrittenCellsForRow)) {
+ // Bypass |Write| so that the TAB isn't compressed away.
+ AddToLine(u"\t", 1);
+ mInWhitespace = true;
+ }
+ else if (mHasWrittenCellsForRow.IsEmpty()) {
+ // We don't always see a <tr> (nor a <table>) before the <td> if we're
+ // copying part of a table
+ PushBool(mHasWrittenCellsForRow, true); // will never be popped
+ }
+ else {
+ SetLastBool(mHasWrittenCellsForRow, true);
+ }
+ }
+ else if (aTag == nsGkAtoms::ul) {
+ // Indent here to support nested lists, which aren't included in li :-(
+ EnsureVerticalSpace(mULCount + mOLStackIndex == 0 ? 1 : 0);
+ // Must end the current line before we change indention
+ mIndent += kIndentSizeList;
+ mULCount++;
+ }
+ else if (aTag == nsGkAtoms::ol) {
+ EnsureVerticalSpace(mULCount + mOLStackIndex == 0 ? 1 : 0);
+ if (mFlags & nsIDocumentEncoder::OutputFormatted) {
+ // Must end the current line before we change indention
+ if (mOLStackIndex < OLStackSize) {
+ nsAutoString startAttr;
+ int32_t startVal = 1;
+ if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::start, startAttr))) {
+ nsresult rv = NS_OK;
+ startVal = startAttr.ToInteger(&rv);
+ if (NS_FAILED(rv))
+ startVal = 1;
+ }
+ mOLStack[mOLStackIndex++] = startVal;
+ }
+ } else {
+ mOLStackIndex++;
+ }
+ mIndent += kIndentSizeList; // see ul
+ }
+ else if (aTag == nsGkAtoms::li &&
+ (mFlags & nsIDocumentEncoder::OutputFormatted)) {
+ if (mTagStackIndex > 1 && IsInOL()) {
+ if (mOLStackIndex > 0) {
+ nsAutoString valueAttr;
+ if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::value, valueAttr))) {
+ nsresult rv = NS_OK;
+ int32_t valueAttrVal = valueAttr.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv))
+ mOLStack[mOLStackIndex-1] = valueAttrVal;
+ }
+ // This is what nsBulletFrame does for OLs:
+ mInIndentString.AppendInt(mOLStack[mOLStackIndex-1]++, 10);
+ }
+ else {
+ mInIndentString.Append(char16_t('#'));
+ }
+
+ mInIndentString.Append(char16_t('.'));
+
+ }
+ else {
+ static char bulletCharArray[] = "*o+#";
+ uint32_t index = mULCount > 0 ? (mULCount - 1) : 3;
+ char bulletChar = bulletCharArray[index % 4];
+ mInIndentString.Append(char16_t(bulletChar));
+ }
+
+ mInIndentString.Append(char16_t(' '));
+ }
+ else if (aTag == nsGkAtoms::dl) {
+ EnsureVerticalSpace(1);
+ }
+ else if (aTag == nsGkAtoms::dt) {
+ EnsureVerticalSpace(0);
+ }
+ else if (aTag == nsGkAtoms::dd) {
+ EnsureVerticalSpace(0);
+ mIndent += kIndentSizeDD;
+ }
+ else if (aTag == nsGkAtoms::span) {
+ ++mSpanLevel;
+ }
+ else if (aTag == nsGkAtoms::blockquote) {
+ // Push
+ PushBool(mIsInCiteBlockquote, isInCiteBlockquote);
+ if (isInCiteBlockquote) {
+ EnsureVerticalSpace(0);
+ mCiteQuoteLevel++;
+ }
+ else {
+ EnsureVerticalSpace(1);
+ mIndent += kTabSize; // Check for some maximum value?
+ }
+ }
+ else if (aTag == nsGkAtoms::q) {
+ Write(NS_LITERAL_STRING("\""));
+ }
+
+ // Else make sure we'll separate block level tags,
+ // even if we're about to leave, before doing any other formatting.
+ else if (IsElementBlock(mElement)) {
+ EnsureVerticalSpace(0);
+ }
+
+ //////////////////////////////////////////////////////////////
+ if (!(mFlags & nsIDocumentEncoder::OutputFormatted)) {
+ return NS_OK;
+ }
+ //////////////////////////////////////////////////////////////
+ // The rest of this routine is formatted output stuff,
+ // which we should skip if we're not formatted:
+ //////////////////////////////////////////////////////////////
+
+ // Push on stack
+ bool currentNodeIsConverted = IsCurrentNodeConverted();
+
+ if (aTag == nsGkAtoms::h1 || aTag == nsGkAtoms::h2 ||
+ aTag == nsGkAtoms::h3 || aTag == nsGkAtoms::h4 ||
+ aTag == nsGkAtoms::h5 || aTag == nsGkAtoms::h6)
+ {
+ EnsureVerticalSpace(2);
+ if (mHeaderStrategy == 2) { // numbered
+ mIndent += kIndentSizeHeaders;
+ // Caching
+ int32_t level = HeaderLevel(aTag);
+ // Increase counter for current level
+ mHeaderCounter[level]++;
+ // Reset all lower levels
+ int32_t i;
+
+ for (i = level + 1; i <= 6; i++) {
+ mHeaderCounter[i] = 0;
+ }
+
+ // Construct numbers
+ nsAutoString leadup;
+ for (i = 1; i <= level; i++) {
+ leadup.AppendInt(mHeaderCounter[i]);
+ leadup.Append(char16_t('.'));
+ }
+ leadup.Append(char16_t(' '));
+ Write(leadup);
+ }
+ else if (mHeaderStrategy == 1) { // indent increasingly
+ mIndent += kIndentSizeHeaders;
+ for (int32_t i = HeaderLevel(aTag); i > 1; i--) {
+ // for h(x), run x-1 times
+ mIndent += kIndentIncrementHeaders;
+ }
+ }
+ }
+ else if (aTag == nsGkAtoms::a && !currentNodeIsConverted) {
+ nsAutoString url;
+ if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::href, url))
+ && !url.IsEmpty()) {
+ mURL = url;
+ }
+ }
+ else if (aTag == nsGkAtoms::sup && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("^"));
+ }
+ else if (aTag == nsGkAtoms::sub && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("_"));
+ }
+ else if (aTag == nsGkAtoms::code && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("|"));
+ }
+ else if ((aTag == nsGkAtoms::strong || aTag == nsGkAtoms::b)
+ && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("*"));
+ }
+ else if ((aTag == nsGkAtoms::em || aTag == nsGkAtoms::i)
+ && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("/"));
+ }
+ else if (aTag == nsGkAtoms::u && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("_"));
+ }
+
+ /* Container elements are always block elements, so we shouldn't
+ output any whitespace immediately after the container tag even if
+ there's extra whitespace there because the HTML is pretty-printed
+ or something. To ensure that happens, tell the serializer we're
+ already in whitespace so it won't output more. */
+ mInWhitespace = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsPlainTextSerializer::DoCloseContainer(nsIAtom* aTag)
+{
+ if (ShouldReplaceContainerWithPlaceholder(mElement->NodeInfo()->NameAtom())) {
+ mIgnoredChildNodeLevel--;
+ return NS_OK;
+ }
+ if (IsIgnorableRubyAnnotation(aTag)) {
+ mIgnoredChildNodeLevel--;
+ return NS_OK;
+ }
+
+ if (mFlags & nsIDocumentEncoder::OutputForPlainTextClipboardCopy) {
+ if (DoOutput() && IsInPre() && IsElementBlock(mElement)) {
+ // If we're closing a preformatted block element, output a line break
+ // when we find a new container.
+ mPreformattedBlockBoundary = true;
+ }
+ }
+
+ if (mFlags & nsIDocumentEncoder::OutputRaw) {
+ // Raw means raw. Don't even think about doing anything fancy
+ // here like indenting, adding line breaks or any other
+ // characters such as list item bullets, quote characters
+ // around <q>, etc. I mean it! Don't make me smack you!
+
+ return NS_OK;
+ }
+
+ if (mTagStackIndex > 0) {
+ --mTagStackIndex;
+ }
+
+ if (mTagStackIndex >= mIgnoreAboveIndex) {
+ if (mTagStackIndex == mIgnoreAboveIndex) {
+ // We're dealing with the close tag whose matching
+ // open tag had set the mIgnoreAboveIndex value.
+ // Reset mIgnoreAboveIndex before discarding this tag.
+ mIgnoreAboveIndex = (uint32_t)kNotFound;
+ }
+ return NS_OK;
+ }
+
+ // End current line if we're ending a block level tag
+ if ((aTag == nsGkAtoms::body) || (aTag == nsGkAtoms::html)) {
+ // We want the output to end with a new line,
+ // but in preformatted areas like text fields,
+ // we can't emit newlines that weren't there.
+ // So add the newline only in the case of formatted output.
+ if (mFlags & nsIDocumentEncoder::OutputFormatted) {
+ EnsureVerticalSpace(0);
+ }
+ else {
+ FlushLine();
+ }
+ // We won't want to do anything with these in formatted mode either,
+ // so just return now:
+ return NS_OK;
+ }
+
+ // Keep this in sync with DoOpenContainer!
+ if (!DoOutput()) {
+ return NS_OK;
+ }
+
+ if (aTag == nsGkAtoms::tr) {
+ PopBool(mHasWrittenCellsForRow);
+ // Should always end a line, but get no more whitespace
+ if (mFloatingLines < 0)
+ mFloatingLines = 0;
+ mLineBreakDue = true;
+ }
+ else if (((aTag == nsGkAtoms::li) ||
+ (aTag == nsGkAtoms::dt)) &&
+ (mFlags & nsIDocumentEncoder::OutputFormatted)) {
+ // Items that should always end a line, but get no more whitespace
+ if (mFloatingLines < 0)
+ mFloatingLines = 0;
+ mLineBreakDue = true;
+ }
+ else if (aTag == nsGkAtoms::pre) {
+ mFloatingLines = GetLastBool(mIsInCiteBlockquote) ? 0 : 1;
+ mLineBreakDue = true;
+ }
+ else if (aTag == nsGkAtoms::ul) {
+ FlushLine();
+ mIndent -= kIndentSizeList;
+ if (--mULCount + mOLStackIndex == 0) {
+ mFloatingLines = 1;
+ mLineBreakDue = true;
+ }
+ }
+ else if (aTag == nsGkAtoms::ol) {
+ FlushLine(); // Doing this after decreasing OLStackIndex would be wrong.
+ mIndent -= kIndentSizeList;
+ NS_ASSERTION(mOLStackIndex, "Wrong OLStack level!");
+ mOLStackIndex--;
+ if (mULCount + mOLStackIndex == 0) {
+ mFloatingLines = 1;
+ mLineBreakDue = true;
+ }
+ }
+ else if (aTag == nsGkAtoms::dl) {
+ mFloatingLines = 1;
+ mLineBreakDue = true;
+ }
+ else if (aTag == nsGkAtoms::dd) {
+ FlushLine();
+ mIndent -= kIndentSizeDD;
+ }
+ else if (aTag == nsGkAtoms::span) {
+ NS_ASSERTION(mSpanLevel, "Span level will be negative!");
+ --mSpanLevel;
+ }
+ else if (aTag == nsGkAtoms::div) {
+ if (mFloatingLines < 0)
+ mFloatingLines = 0;
+ mLineBreakDue = true;
+ }
+ else if (aTag == nsGkAtoms::blockquote) {
+ FlushLine(); // Is this needed?
+
+ // Pop
+ bool isInCiteBlockquote = PopBool(mIsInCiteBlockquote);
+
+ if (isInCiteBlockquote) {
+ NS_ASSERTION(mCiteQuoteLevel, "CiteQuote level will be negative!");
+ mCiteQuoteLevel--;
+ mFloatingLines = 0;
+ mHasWrittenCiteBlockquote = true;
+ }
+ else {
+ mIndent -= kTabSize;
+ mFloatingLines = 1;
+ }
+ mLineBreakDue = true;
+ }
+ else if (aTag == nsGkAtoms::q) {
+ Write(NS_LITERAL_STRING("\""));
+ }
+ else if (IsElementBlock(mElement) && aTag != nsGkAtoms::script) {
+ // All other blocks get 1 vertical space after them
+ // in formatted mode, otherwise 0.
+ // This is hard. Sometimes 0 is a better number, but
+ // how to know?
+ if (mFlags & nsIDocumentEncoder::OutputFormatted)
+ EnsureVerticalSpace(1);
+ else {
+ if (mFloatingLines < 0)
+ mFloatingLines = 0;
+ mLineBreakDue = true;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+ if (!(mFlags & nsIDocumentEncoder::OutputFormatted)) {
+ return NS_OK;
+ }
+ //////////////////////////////////////////////////////////////
+ // The rest of this routine is formatted output stuff,
+ // which we should skip if we're not formatted:
+ //////////////////////////////////////////////////////////////
+
+ // Pop the currentConverted stack
+ bool currentNodeIsConverted = IsCurrentNodeConverted();
+
+ if (aTag == nsGkAtoms::h1 || aTag == nsGkAtoms::h2 ||
+ aTag == nsGkAtoms::h3 || aTag == nsGkAtoms::h4 ||
+ aTag == nsGkAtoms::h5 || aTag == nsGkAtoms::h6) {
+
+ if (mHeaderStrategy) { /*numbered or indent increasingly*/
+ mIndent -= kIndentSizeHeaders;
+ }
+ if (mHeaderStrategy == 1 /*indent increasingly*/ ) {
+ for (int32_t i = HeaderLevel(aTag); i > 1; i--) {
+ // for h(x), run x-1 times
+ mIndent -= kIndentIncrementHeaders;
+ }
+ }
+ EnsureVerticalSpace(1);
+ }
+ else if (aTag == nsGkAtoms::a && !currentNodeIsConverted && !mURL.IsEmpty()) {
+ nsAutoString temp;
+ temp.AssignLiteral(" <");
+ temp += mURL;
+ temp.Append(char16_t('>'));
+ Write(temp);
+ mURL.Truncate();
+ }
+ else if ((aTag == nsGkAtoms::sup || aTag == nsGkAtoms::sub)
+ && mStructs && !currentNodeIsConverted) {
+ Write(kSpace);
+ }
+ else if (aTag == nsGkAtoms::code && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("|"));
+ }
+ else if ((aTag == nsGkAtoms::strong || aTag == nsGkAtoms::b)
+ && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("*"));
+ }
+ else if ((aTag == nsGkAtoms::em || aTag == nsGkAtoms::i)
+ && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("/"));
+ }
+ else if (aTag == nsGkAtoms::u && mStructs && !currentNodeIsConverted) {
+ Write(NS_LITERAL_STRING("_"));
+ }
+
+ return NS_OK;
+}
+
+bool
+nsPlainTextSerializer::MustSuppressLeaf()
+{
+ if (mIgnoredChildNodeLevel > 0) {
+ return true;
+ }
+
+ if ((mTagStackIndex > 1 &&
+ mTagStack[mTagStackIndex-2] == nsGkAtoms::select) ||
+ (mTagStackIndex > 0 &&
+ mTagStack[mTagStackIndex-1] == nsGkAtoms::select)) {
+ // Don't output the contents of SELECT elements;
+ // Might be nice, eventually, to output just the selected element.
+ // Read more in bug 31994.
+ return true;
+ }
+
+ if (mTagStackIndex > 0 &&
+ (mTagStack[mTagStackIndex-1] == nsGkAtoms::script ||
+ mTagStack[mTagStackIndex-1] == nsGkAtoms::style)) {
+ // Don't output the contents of <script> or <style> tags;
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsPlainTextSerializer::DoAddText(bool aIsLineBreak, const nsAString& aText)
+{
+ // If we don't want any output, just return
+ if (!DoOutput()) {
+ return;
+ }
+
+ if (!aIsLineBreak) {
+ // Make sure to reset this, since it's no longer true.
+ mHasWrittenCiteBlockquote = false;
+ }
+
+ if (mLineBreakDue)
+ EnsureVerticalSpace(mFloatingLines);
+
+ if (MustSuppressLeaf()) {
+ return;
+ }
+
+ if (aIsLineBreak) {
+ // The only times we want to pass along whitespace from the original
+ // html source are if we're forced into preformatted mode via flags,
+ // or if we're prettyprinting and we're inside a <pre>.
+ // Otherwise, either we're collapsing to minimal text, or we're
+ // prettyprinting to mimic the html format, and in neither case
+ // does the formatting of the html source help us.
+ if ((mFlags & nsIDocumentEncoder::OutputPreformatted) ||
+ (mPreFormattedMail && !mWrapColumn) ||
+ IsInPre()) {
+ EnsureVerticalSpace(mEmptyLines+1);
+ }
+ else if (!mInWhitespace) {
+ Write(kSpace);
+ mInWhitespace = true;
+ }
+ return;
+ }
+
+ /* Check, if we are in a link (symbolized with mURL containing the URL)
+ and the text is equal to the URL. In that case we don't want to output
+ the URL twice so we scrap the text in mURL. */
+ if (!mURL.IsEmpty() && mURL.Equals(aText)) {
+ mURL.Truncate();
+ }
+ Write(aText);
+}
+
+nsresult
+nsPlainTextSerializer::DoAddLeaf(nsIAtom* aTag)
+{
+ mPreformattedBlockBoundary = false;
+
+ // If we don't want any output, just return
+ if (!DoOutput()) {
+ return NS_OK;
+ }
+
+ if (mLineBreakDue)
+ EnsureVerticalSpace(mFloatingLines);
+
+ if (MustSuppressLeaf()) {
+ return NS_OK;
+ }
+
+ if (aTag == nsGkAtoms::br) {
+ // Another egregious editor workaround, see bug 38194:
+ // ignore the bogus br tags that the editor sticks here and there.
+ nsAutoString tagAttr;
+ if (NS_FAILED(GetAttributeValue(nsGkAtoms::type, tagAttr))
+ || !tagAttr.EqualsLiteral("_moz")) {
+ EnsureVerticalSpace(mEmptyLines+1);
+ }
+ }
+ else if (aTag == nsGkAtoms::hr &&
+ (mFlags & nsIDocumentEncoder::OutputFormatted)) {
+ EnsureVerticalSpace(0);
+
+ // Make a line of dashes as wide as the wrap width
+ // XXX honoring percentage would be nice
+ nsAutoString line;
+ uint32_t width = (mWrapColumn > 0 ? mWrapColumn : 25);
+ while (line.Length() < width) {
+ line.Append(char16_t('-'));
+ }
+ Write(line);
+
+ EnsureVerticalSpace(0);
+ }
+ else if (mFlags & nsIDocumentEncoder::OutputNonTextContentAsPlaceholder) {
+ Write(NS_LITERAL_STRING(u"\xFFFC"));
+ }
+ else if (aTag == nsGkAtoms::img) {
+ /* Output (in decreasing order of preference)
+ alt, title or nothing */
+ // See <http://www.w3.org/TR/REC-html40/struct/objects.html#edef-IMG>
+ nsAutoString imageDescription;
+ if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::alt,
+ imageDescription))) {
+ // If the alt attribute has an empty value (|alt=""|), output nothing
+ }
+ else if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::title,
+ imageDescription))
+ && !imageDescription.IsEmpty()) {
+ imageDescription = NS_LITERAL_STRING(" [") +
+ imageDescription +
+ NS_LITERAL_STRING("] ");
+ }
+
+ Write(imageDescription);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Adds as many newline as necessary to get |noOfRows| empty lines
+ *
+ * noOfRows = -1 : Being in the middle of some line of text
+ * noOfRows = 0 : Being at the start of a line
+ * noOfRows = n>0 : Having n empty lines before the current line.
+ */
+void
+nsPlainTextSerializer::EnsureVerticalSpace(int32_t noOfRows)
+{
+ // If we have something in the indent we probably want to output
+ // it and it's not included in the count for empty lines so we don't
+ // realize that we should start a new line.
+ if (noOfRows >= 0 && !mInIndentString.IsEmpty()) {
+ EndLine(false);
+ mInWhitespace = true;
+ }
+
+ while(mEmptyLines < noOfRows) {
+ EndLine(false);
+ mInWhitespace = true;
+ }
+ mLineBreakDue = false;
+ mFloatingLines = -1;
+}
+
+/**
+ * This empties the current line cache without adding a NEWLINE.
+ * Should not be used if line wrapping is of importance since
+ * this function destroys the cache information.
+ *
+ * It will also write indentation and quotes if we believe us to be
+ * at the start of the line.
+ */
+void
+nsPlainTextSerializer::FlushLine()
+{
+ if (!mCurrentLine.IsEmpty()) {
+ if (mAtFirstColumn) {
+ OutputQuotesAndIndent(); // XXX: Should we always do this? Bug?
+ }
+
+ Output(mCurrentLine);
+ mAtFirstColumn = mAtFirstColumn && mCurrentLine.IsEmpty();
+ mCurrentLine.Truncate();
+ mCurrentLineWidth = 0;
+ }
+}
+
+/**
+ * Prints the text to output to our current output device (the string mOutputString).
+ * The only logic here is to replace non breaking spaces with a normal space since
+ * most (all?) receivers of the result won't understand the nbsp and even be
+ * confused by it.
+ */
+void
+nsPlainTextSerializer::Output(nsString& aString)
+{
+ if (!aString.IsEmpty()) {
+ mStartedOutput = true;
+ }
+
+ if (!(mFlags & nsIDocumentEncoder::OutputPersistNBSP)) {
+ // First, replace all nbsp characters with spaces,
+ // which the unicode encoder won't do for us.
+ aString.ReplaceChar(kNBSP, kSPACE);
+ }
+ mOutputString->Append(aString);
+}
+
+static bool
+IsSpaceStuffable(const char16_t *s)
+{
+ if (s[0] == '>' || s[0] == ' ' || s[0] == kNBSP ||
+ nsCRT::strncmp(s, u"From ", 5) == 0)
+ return true;
+ else
+ return false;
+}
+
+/**
+ * This function adds a piece of text to the current stored line. If we are
+ * wrapping text and the stored line will become too long, a suitable
+ * location to wrap will be found and the line that's complete will be
+ * output.
+ */
+void
+nsPlainTextSerializer::AddToLine(const char16_t * aLineFragment,
+ int32_t aLineFragmentLength)
+{
+ uint32_t prefixwidth = (mCiteQuoteLevel > 0 ? mCiteQuoteLevel + 1:0)+mIndent;
+
+ if (mLineBreakDue)
+ EnsureVerticalSpace(mFloatingLines);
+
+ int32_t linelength = mCurrentLine.Length();
+ if (0 == linelength) {
+ if (0 == aLineFragmentLength) {
+ // Nothing at all. Are you kidding me?
+ return;
+ }
+
+ if (mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+ if (IsSpaceStuffable(aLineFragment)
+ && mCiteQuoteLevel == 0 // We space-stuff quoted lines anyway
+ )
+ {
+ // Space stuffing a la RFC 2646 (format=flowed).
+ mCurrentLine.Append(char16_t(' '));
+
+ if (MayWrap()) {
+ mCurrentLineWidth += GetUnicharWidth(' ');
+#ifdef DEBUG_wrapping
+ NS_ASSERTION(GetUnicharStringWidth(mCurrentLine.get(),
+ mCurrentLine.Length()) ==
+ (int32_t)mCurrentLineWidth,
+ "mCurrentLineWidth and reality out of sync!");
+#endif
+ }
+ }
+ }
+ mEmptyLines=-1;
+ }
+
+ mCurrentLine.Append(aLineFragment, aLineFragmentLength);
+ if (MayWrap()) {
+ mCurrentLineWidth += GetUnicharStringWidth(aLineFragment,
+ aLineFragmentLength);
+#ifdef DEBUG_wrapping
+ NS_ASSERTION(GetUnicharstringWidth(mCurrentLine.get(),
+ mCurrentLine.Length()) ==
+ (int32_t)mCurrentLineWidth,
+ "mCurrentLineWidth and reality out of sync!");
+#endif
+ }
+
+ linelength = mCurrentLine.Length();
+
+ // Wrap?
+ if (MayWrap())
+ {
+#ifdef DEBUG_wrapping
+ NS_ASSERTION(GetUnicharstringWidth(mCurrentLine.get(),
+ mCurrentLine.Length()) ==
+ (int32_t)mCurrentLineWidth,
+ "mCurrentLineWidth and reality out of sync!");
+#endif
+ // Yes, wrap!
+ // The "+4" is to avoid wrap lines that only would be a couple
+ // of letters too long. We give this bonus only if the
+ // wrapcolumn is more than 20.
+ uint32_t bonuswidth = (mWrapColumn > 20) ? 4 : 0;
+
+ // XXX: Should calculate prefixwidth with GetUnicharStringWidth
+ while(mCurrentLineWidth+prefixwidth > mWrapColumn+bonuswidth) {
+ // We go from the end removing one letter at a time until
+ // we have a reasonable width
+ int32_t goodSpace = mCurrentLine.Length();
+ uint32_t width = mCurrentLineWidth;
+ while(goodSpace > 0 && (width+prefixwidth > mWrapColumn)) {
+ goodSpace--;
+ width -= GetUnicharWidth(mCurrentLine[goodSpace]);
+ }
+
+ goodSpace++;
+
+ if (mLineBreaker) {
+ goodSpace = mLineBreaker->Prev(mCurrentLine.get(),
+ mCurrentLine.Length(), goodSpace);
+ if (goodSpace != NS_LINEBREAKER_NEED_MORE_TEXT &&
+ nsCRT::IsAsciiSpace(mCurrentLine.CharAt(goodSpace-1))) {
+ --goodSpace; // adjust the position since line breaker returns a position next to space
+ }
+ }
+ // fallback if the line breaker is unavailable or failed
+ if (!mLineBreaker) {
+ if (mCurrentLine.IsEmpty() || mWrapColumn < prefixwidth) {
+ goodSpace = NS_LINEBREAKER_NEED_MORE_TEXT;
+ } else {
+ goodSpace = std::min(mWrapColumn - prefixwidth, mCurrentLine.Length() - 1);
+ while (goodSpace >= 0 &&
+ !nsCRT::IsAsciiSpace(mCurrentLine.CharAt(goodSpace))) {
+ goodSpace--;
+ }
+ }
+ }
+
+ nsAutoString restOfLine;
+ if (goodSpace == NS_LINEBREAKER_NEED_MORE_TEXT) {
+ // If we didn't find a good place to break, accept long line and
+ // try to find another place to break
+ goodSpace=(prefixwidth>mWrapColumn+1)?1:mWrapColumn-prefixwidth+1;
+ if (mLineBreaker) {
+ if ((uint32_t)goodSpace < mCurrentLine.Length())
+ goodSpace = mLineBreaker->Next(mCurrentLine.get(),
+ mCurrentLine.Length(), goodSpace);
+ if (goodSpace == NS_LINEBREAKER_NEED_MORE_TEXT)
+ goodSpace = mCurrentLine.Length();
+ }
+ // fallback if the line breaker is unavailable or failed
+ if (!mLineBreaker) {
+ goodSpace=(prefixwidth>mWrapColumn)?1:mWrapColumn-prefixwidth;
+ while (goodSpace < linelength &&
+ !nsCRT::IsAsciiSpace(mCurrentLine.CharAt(goodSpace))) {
+ goodSpace++;
+ }
+ }
+ }
+
+ if ((goodSpace < linelength) && (goodSpace > 0)) {
+ // Found a place to break
+
+ // -1 (trim a char at the break position)
+ // only if the line break was a space.
+ if (nsCRT::IsAsciiSpace(mCurrentLine.CharAt(goodSpace))) {
+ mCurrentLine.Right(restOfLine, linelength-goodSpace-1);
+ }
+ else {
+ mCurrentLine.Right(restOfLine, linelength-goodSpace);
+ }
+ // if breaker was U+0020, it has to consider for delsp=yes support
+ bool breakBySpace = mCurrentLine.CharAt(goodSpace) == ' ';
+ mCurrentLine.Truncate(goodSpace);
+ EndLine(true, breakBySpace);
+ mCurrentLine.Truncate();
+ // Space stuff new line?
+ if (mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+ if (!restOfLine.IsEmpty() && IsSpaceStuffable(restOfLine.get())
+ && mCiteQuoteLevel == 0 // We space-stuff quoted lines anyway
+ )
+ {
+ // Space stuffing a la RFC 2646 (format=flowed).
+ mCurrentLine.Append(char16_t(' '));
+ //XXX doesn't seem to work correctly for ' '
+ }
+ }
+ mCurrentLine.Append(restOfLine);
+ mCurrentLineWidth = GetUnicharStringWidth(mCurrentLine.get(),
+ mCurrentLine.Length());
+ linelength = mCurrentLine.Length();
+ mEmptyLines = -1;
+ }
+ else {
+ // Nothing to do. Hopefully we get more data later
+ // to use for a place to break line
+ break;
+ }
+ }
+ }
+ else {
+ // No wrapping.
+ }
+}
+
+/**
+ * Outputs the contents of mCurrentLine, and resets line specific
+ * variables. Also adds an indentation and prefix if there is
+ * one specified. Strips ending spaces from the line if it isn't
+ * preformatted.
+ */
+void
+nsPlainTextSerializer::EndLine(bool aSoftlinebreak, bool aBreakBySpace)
+{
+ uint32_t currentlinelength = mCurrentLine.Length();
+
+ if (aSoftlinebreak && 0 == currentlinelength) {
+ // No meaning
+ return;
+ }
+
+ /* In non-preformatted mode, remove spaces from the end of the line for
+ * format=flowed compatibility. Don't do this for these special cases:
+ * "-- ", the signature separator (RFC 2646) shouldn't be touched and
+ * "- -- ", the OpenPGP dash-escaped signature separator in inline
+ * signed messages according to the OpenPGP standard (RFC 2440).
+ */
+ if (!(mFlags & nsIDocumentEncoder::OutputPreformatted) &&
+ !(mFlags & nsIDocumentEncoder::OutputDontRemoveLineEndingSpaces) &&
+ (aSoftlinebreak ||
+ !(mCurrentLine.EqualsLiteral("-- ") || mCurrentLine.EqualsLiteral("- -- ")))) {
+ // Remove spaces from the end of the line.
+ while(currentlinelength > 0 &&
+ mCurrentLine[currentlinelength-1] == ' ') {
+ --currentlinelength;
+ }
+ mCurrentLine.SetLength(currentlinelength);
+ }
+
+ if (aSoftlinebreak &&
+ (mFlags & nsIDocumentEncoder::OutputFormatFlowed) &&
+ (mIndent == 0)) {
+ // Add the soft part of the soft linebreak (RFC 2646 4.1)
+ // We only do this when there is no indentation since format=flowed
+ // lines and indentation doesn't work well together.
+
+ // If breaker character is ASCII space with RFC 3676 support (delsp=yes),
+ // add twice space.
+ if ((mFlags & nsIDocumentEncoder::OutputFormatDelSp) && aBreakBySpace)
+ mCurrentLine.AppendLiteral(" ");
+ else
+ mCurrentLine.Append(char16_t(' '));
+ }
+
+ if (aSoftlinebreak) {
+ mEmptyLines=0;
+ }
+ else {
+ // Hard break
+ if (!mCurrentLine.IsEmpty() || !mInIndentString.IsEmpty()) {
+ mEmptyLines=-1;
+ }
+
+ mEmptyLines++;
+ }
+
+ if (mAtFirstColumn) {
+ // If we don't have anything "real" to output we have to
+ // make sure the indent doesn't end in a space since that
+ // would trick a format=flowed-aware receiver.
+ bool stripTrailingSpaces = mCurrentLine.IsEmpty();
+ OutputQuotesAndIndent(stripTrailingSpaces);
+ }
+
+ mCurrentLine.Append(mLineBreak);
+ Output(mCurrentLine);
+ mCurrentLine.Truncate();
+ mCurrentLineWidth = 0;
+ mAtFirstColumn=true;
+ mInWhitespace=true;
+ mLineBreakDue = false;
+ mFloatingLines = -1;
+}
+
+
+/**
+ * Outputs the calculated and stored indent and text in the indentation. That is
+ * quote chars and numbers for numbered lists and such. It will also reset any
+ * stored text to put in the indentation after using it.
+ */
+void
+nsPlainTextSerializer::OutputQuotesAndIndent(bool stripTrailingSpaces /* = false */)
+{
+ nsAutoString stringToOutput;
+
+ // Put the mail quote "> " chars in, if appropriate:
+ if (mCiteQuoteLevel > 0) {
+ nsAutoString quotes;
+ for(int i=0; i < mCiteQuoteLevel; i++) {
+ quotes.Append(char16_t('>'));
+ }
+ if (!mCurrentLine.IsEmpty()) {
+ /* Better don't output a space here, if the line is empty,
+ in case a receiving f=f-aware UA thinks, this were a flowed line,
+ which it isn't - it's just empty.
+ (Flowed lines may be joined with the following one,
+ so the empty line may be lost completely.) */
+ quotes.Append(char16_t(' '));
+ }
+ stringToOutput = quotes;
+ mAtFirstColumn = false;
+ }
+
+ // Indent if necessary
+ int32_t indentwidth = mIndent - mInIndentString.Length();
+ if (indentwidth > 0
+ && (!mCurrentLine.IsEmpty() || !mInIndentString.IsEmpty())
+ // Don't make empty lines look flowed
+ ) {
+ nsAutoString spaces;
+ for (int i=0; i < indentwidth; ++i)
+ spaces.Append(char16_t(' '));
+ stringToOutput += spaces;
+ mAtFirstColumn = false;
+ }
+
+ if (!mInIndentString.IsEmpty()) {
+ stringToOutput += mInIndentString;
+ mAtFirstColumn = false;
+ mInIndentString.Truncate();
+ }
+
+ if (stripTrailingSpaces) {
+ int32_t lineLength = stringToOutput.Length();
+ while(lineLength > 0 &&
+ ' ' == stringToOutput[lineLength-1]) {
+ --lineLength;
+ }
+ stringToOutput.SetLength(lineLength);
+ }
+
+ if (!stringToOutput.IsEmpty()) {
+ Output(stringToOutput);
+ }
+
+}
+
+/**
+ * Write a string. This is the highlevel function to use to get text output.
+ * By using AddToLine, Output, EndLine and other functions it handles quotation,
+ * line wrapping, indentation, whitespace compression and other things.
+ */
+void
+nsPlainTextSerializer::Write(const nsAString& aStr)
+{
+ // XXX Copy necessary to use nsString methods and gain
+ // access to underlying buffer
+ nsAutoString str(aStr);
+
+#ifdef DEBUG_wrapping
+ printf("Write(%s): wrap col = %d\n",
+ NS_ConvertUTF16toUTF8(str).get(), mWrapColumn);
+#endif
+
+ int32_t bol = 0;
+ int32_t newline;
+
+ int32_t totLen = str.Length();
+
+ // If the string is empty, do nothing:
+ if (totLen <= 0) return;
+
+ // For Flowed text change nbsp-ses to spaces at end of lines to allow them
+ // to be cut off along with usual spaces if required. (bug #125928)
+ if (mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+ for (int32_t i = totLen-1; i >= 0; i--) {
+ char16_t c = str[i];
+ if ('\n' == c || '\r' == c || ' ' == c || '\t' == c)
+ continue;
+ if (kNBSP == c)
+ str.Replace(i, 1, ' ');
+ else
+ break;
+ }
+ }
+
+ // We have two major codepaths here. One that does preformatted text and one
+ // that does normal formatted text. The one for preformatted text calls
+ // Output directly while the other code path goes through AddToLine.
+ if ((mPreFormattedMail && !mWrapColumn) || (IsInPre() && !mPreFormattedMail)
+ || (mSpanLevel > 0 && mEmptyLines >= 0 && IsQuotedLine(str))) {
+ // No intelligent wrapping.
+
+ // This mustn't be mixed with intelligent wrapping without clearing
+ // the mCurrentLine buffer before!!!
+ NS_ASSERTION(mCurrentLine.IsEmpty() || (IsInPre() && !mPreFormattedMail),
+ "Mixed wrapping data and nonwrapping data on the same line");
+ if (!mCurrentLine.IsEmpty()) {
+ FlushLine();
+ }
+
+ // Put the mail quote "> " chars in, if appropriate.
+ // Have to put it in before every line.
+ while(bol<totLen) {
+ bool outputQuotes = mAtFirstColumn;
+ bool atFirstColumn = mAtFirstColumn;
+ bool outputLineBreak = false;
+ bool spacesOnly = true;
+
+ // Find one of '\n' or '\r' using iterators since nsAString
+ // doesn't have the old FindCharInSet function.
+ nsAString::const_iterator iter; str.BeginReading(iter);
+ nsAString::const_iterator done_searching; str.EndReading(done_searching);
+ iter.advance(bol);
+ int32_t new_newline = bol;
+ newline = kNotFound;
+ while(iter != done_searching) {
+ if ('\n' == *iter || '\r' == *iter) {
+ newline = new_newline;
+ break;
+ }
+ if (' ' != *iter)
+ spacesOnly = false;
+ ++new_newline;
+ ++iter;
+ }
+
+ // Done searching
+ nsAutoString stringpart;
+ if (newline == kNotFound) {
+ // No new lines.
+ stringpart.Assign(Substring(str, bol, totLen - bol));
+ if (!stringpart.IsEmpty()) {
+ char16_t lastchar = stringpart[stringpart.Length()-1];
+ if ((lastchar == '\t') || (lastchar == ' ') ||
+ (lastchar == '\r') ||(lastchar == '\n')) {
+ mInWhitespace = true;
+ }
+ else {
+ mInWhitespace = false;
+ }
+ }
+ mEmptyLines=-1;
+ atFirstColumn = mAtFirstColumn && (totLen-bol)==0;
+ bol = totLen;
+ }
+ else {
+ // There is a newline
+ stringpart.Assign(Substring(str, bol, newline-bol));
+ mInWhitespace = true;
+ outputLineBreak = true;
+ mEmptyLines=0;
+ atFirstColumn = true;
+ bol = newline+1;
+ if ('\r' == *iter && bol < totLen && '\n' == *++iter) {
+ // There was a CRLF in the input. This used to be illegal and
+ // stripped by the parser. Apparently not anymore. Let's skip
+ // over the LF.
+ bol++;
+ }
+ }
+
+ mCurrentLine.Truncate();
+ if (mFlags & nsIDocumentEncoder::OutputFormatFlowed) {
+ if ((outputLineBreak || !spacesOnly) && // bugs 261467,125928
+ !IsQuotedLine(stringpart) &&
+ !stringpart.EqualsLiteral("-- ") &&
+ !stringpart.EqualsLiteral("- -- "))
+ stringpart.Trim(" ", false, true, true);
+ if (IsSpaceStuffable(stringpart.get()) && !IsQuotedLine(stringpart))
+ mCurrentLine.Append(char16_t(' '));
+ }
+ mCurrentLine.Append(stringpart);
+
+ if (outputQuotes) {
+ // Note: this call messes with mAtFirstColumn
+ OutputQuotesAndIndent();
+ }
+
+ Output(mCurrentLine);
+ if (outputLineBreak) {
+ Output(mLineBreak);
+ }
+ mAtFirstColumn = atFirstColumn;
+ }
+
+ // Reset mCurrentLine.
+ mCurrentLine.Truncate();
+
+#ifdef DEBUG_wrapping
+ printf("No wrapping: newline is %d, totLen is %d\n",
+ newline, totLen);
+#endif
+ return;
+ }
+
+ // Intelligent handling of text
+ // If needed, strip out all "end of lines"
+ // and multiple whitespace between words
+ int32_t nextpos;
+ const char16_t * offsetIntoBuffer = nullptr;
+
+ while (bol < totLen) { // Loop over lines
+ // Find a place where we may have to do whitespace compression
+ nextpos = str.FindCharInSet(" \t\n\r", bol);
+#ifdef DEBUG_wrapping
+ nsAutoString remaining;
+ str.Right(remaining, totLen - bol);
+ foo = ToNewCString(remaining);
+ // printf("Next line: bol = %d, newlinepos = %d, totLen = %d, string = '%s'\n",
+ // bol, nextpos, totLen, foo);
+ free(foo);
+#endif
+
+ if (nextpos == kNotFound) {
+ // The rest of the string
+ offsetIntoBuffer = str.get() + bol;
+ AddToLine(offsetIntoBuffer, totLen-bol);
+ bol=totLen;
+ mInWhitespace=false;
+ }
+ else {
+ // There's still whitespace left in the string
+ if (nextpos != 0 && (nextpos + 1) < totLen) {
+ offsetIntoBuffer = str.get() + nextpos;
+ // skip '\n' if it is between CJ chars
+ if (offsetIntoBuffer[0] == '\n' && IS_CJ_CHAR(offsetIntoBuffer[-1]) && IS_CJ_CHAR(offsetIntoBuffer[1])) {
+ offsetIntoBuffer = str.get() + bol;
+ AddToLine(offsetIntoBuffer, nextpos-bol);
+ bol = nextpos + 1;
+ continue;
+ }
+ }
+ // If we're already in whitespace and not preformatted, just skip it:
+ if (mInWhitespace && (nextpos == bol) && !mPreFormattedMail &&
+ !(mFlags & nsIDocumentEncoder::OutputPreformatted)) {
+ // Skip whitespace
+ bol++;
+ continue;
+ }
+
+ if (nextpos == bol) {
+ // Note that we are in whitespace.
+ mInWhitespace = true;
+ offsetIntoBuffer = str.get() + nextpos;
+ AddToLine(offsetIntoBuffer, 1);
+ bol++;
+ continue;
+ }
+
+ mInWhitespace = true;
+
+ offsetIntoBuffer = str.get() + bol;
+ if (mPreFormattedMail || (mFlags & nsIDocumentEncoder::OutputPreformatted)) {
+ // Preserve the real whitespace character
+ nextpos++;
+ AddToLine(offsetIntoBuffer, nextpos-bol);
+ bol = nextpos;
+ }
+ else {
+ // Replace the whitespace with a space
+ AddToLine(offsetIntoBuffer, nextpos-bol);
+ AddToLine(kSpace.get(),1);
+ bol = nextpos + 1; // Let's eat the whitespace
+ }
+ }
+ } // Continue looping over the string
+}
+
+
+/**
+ * Gets the value of an attribute in a string. If the function returns
+ * NS_ERROR_NOT_AVAILABLE, there was none such attribute specified.
+ */
+nsresult
+nsPlainTextSerializer::GetAttributeValue(nsIAtom* aName,
+ nsString& aValueRet)
+{
+ if (mElement) {
+ if (mElement->GetAttr(kNameSpaceID_None, aName, aValueRet)) {
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+/**
+ * Returns true, if the element was inserted by Moz' TXT->HTML converter.
+ * In this case, we should ignore it.
+ */
+bool
+nsPlainTextSerializer::IsCurrentNodeConverted()
+{
+ nsAutoString value;
+ nsresult rv = GetAttributeValue(nsGkAtoms::_class, value);
+ return (NS_SUCCEEDED(rv) &&
+ (value.EqualsIgnoreCase("moz-txt", 7) ||
+ value.EqualsIgnoreCase("\"moz-txt", 8)));
+}
+
+
+// static
+nsIAtom*
+nsPlainTextSerializer::GetIdForContent(nsIContent* aContent)
+{
+ if (!aContent->IsHTMLElement()) {
+ return nullptr;
+ }
+
+ nsIAtom* localName = aContent->NodeInfo()->NameAtom();
+ return localName->IsStaticAtom() ? localName : nullptr;
+}
+
+bool
+nsPlainTextSerializer::IsInPre()
+{
+ return !mPreformatStack.empty() && mPreformatStack.top();
+}
+
+bool
+nsPlainTextSerializer::IsElementPreformatted(Element* aElement)
+{
+ RefPtr<nsStyleContext> styleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, nullptr,
+ nullptr);
+ if (styleContext) {
+ const nsStyleText* textStyle = styleContext->StyleText();
+ return textStyle->WhiteSpaceOrNewlineIsSignificant();
+ }
+ // Fall back to looking at the tag, in case there is no style information.
+ return GetIdForContent(aElement) == nsGkAtoms::pre;
+}
+
+bool
+nsPlainTextSerializer::IsElementBlock(Element* aElement)
+{
+ RefPtr<nsStyleContext> styleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, nullptr,
+ nullptr);
+ if (styleContext) {
+ const nsStyleDisplay* displayStyle = styleContext->StyleDisplay();
+ return displayStyle->IsBlockOutsideStyle();
+ }
+ // Fall back to looking at the tag, in case there is no style information.
+ return nsContentUtils::IsHTMLBlock(aElement);
+}
+
+/**
+ * This method is required only to identify LI's inside OL.
+ * Returns TRUE if we are inside an OL tag and FALSE otherwise.
+ */
+bool
+nsPlainTextSerializer::IsInOL()
+{
+ int32_t i = mTagStackIndex;
+ while(--i >= 0) {
+ if (mTagStack[i] == nsGkAtoms::ol)
+ return true;
+ if (mTagStack[i] == nsGkAtoms::ul) {
+ // If a UL is reached first, LI belongs the UL nested in OL.
+ return false;
+ }
+ }
+ // We may reach here for orphan LI's.
+ return false;
+}
+
+/*
+ @return 0 = no header, 1 = h1, ..., 6 = h6
+*/
+int32_t HeaderLevel(nsIAtom* aTag)
+{
+ if (aTag == nsGkAtoms::h1) {
+ return 1;
+ }
+ if (aTag == nsGkAtoms::h2) {
+ return 2;
+ }
+ if (aTag == nsGkAtoms::h3) {
+ return 3;
+ }
+ if (aTag == nsGkAtoms::h4) {
+ return 4;
+ }
+ if (aTag == nsGkAtoms::h5) {
+ return 5;
+ }
+ if (aTag == nsGkAtoms::h6) {
+ return 6;
+ }
+ return 0;
+}
+
+
+/*
+ * This is an implementation of GetUnicharWidth() and
+ * GetUnicharStringWidth() as defined in
+ * "The Single UNIX Specification, Version 2, The Open Group, 1997"
+ * <http://www.UNIX-systems.org/online.html>
+ *
+ * Markus Kuhn -- 2000-02-08 -- public domain
+ *
+ * Minor alterations to fit Mozilla's data types by Daniel Bratell
+ */
+
+/* These functions define the column width of an ISO 10646 character
+ * as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * FullWidth (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+namespace {
+
+struct interval
+{
+ uint16_t first;
+ uint16_t last;
+};
+
+struct CombiningComparator
+{
+ const char16_t mUcs;
+ explicit CombiningComparator(char16_t aUcs) : mUcs(aUcs) {}
+ int operator()(const interval& combining) const {
+ if (mUcs > combining.last)
+ return 1;
+ if (mUcs < combining.first)
+ return -1;
+
+ MOZ_ASSERT(combining.first <= mUcs);
+ MOZ_ASSERT(mUcs <= combining.last);
+ return 0;
+ }
+};
+
+} // namespace
+
+int32_t GetUnicharWidth(char16_t ucs)
+{
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ static const interval combining[] = {
+ { 0x0300, 0x034E }, { 0x0360, 0x0362 }, { 0x0483, 0x0486 },
+ { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 },
+ { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C4 }, { 0x064B, 0x0655 }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+ { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
+ { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
+ { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 },
+ { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 },
+ { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A02, 0x0A02 },
+ { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 },
+ { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 },
+ { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 },
+ { 0x0ACD, 0x0ACD }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
+ { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBF, 0x0CBF },
+ { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 },
+ { 0x0D4D, 0x0D4D }, { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 },
+ { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A },
+ { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 },
+ { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 },
+ { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 },
+ { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 },
+ { 0x0F90, 0x0F97 }, { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 },
+ { 0x102D, 0x1030 }, { 0x1032, 0x1032 }, { 0x1036, 0x1037 },
+ { 0x1039, 0x1039 }, { 0x1058, 0x1059 }, { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x18A9, 0x18A9 },
+ { 0x20D0, 0x20E3 }, { 0x302A, 0x302F }, { 0x3099, 0x309A },
+ { 0xFB1E, 0xFB1E }, { 0xFE20, 0xFE23 }
+ };
+
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* first quick check for Latin-1 etc. characters */
+ if (ucs < combining[0].first)
+ return 1;
+
+ /* binary search in table of non-spacing characters */
+ size_t idx;
+ if (BinarySearchIf(combining, 0, ArrayLength(combining),
+ CombiningComparator(ucs), &idx)) {
+ return 0;
+ }
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ /* fast test for majority of non-wide scripts */
+ if (ucs < 0x1100)
+ return 1;
+
+ return 1 +
+ ((ucs >= 0x1100 && ucs <= 0x115f) || /* Hangul Jamo */
+ (ucs >= 0x2e80 && ucs <= 0xa4cf && (ucs & ~0x0011) != 0x300a &&
+ ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff5f) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6));
+}
+
+
+int32_t GetUnicharStringWidth(const char16_t* pwcs, int32_t n)
+{
+ int32_t w, width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++)
+ if ((w = GetUnicharWidth(*pwcs)) < 0)
+ ++width; // Taking 1 as the width of non-printable character, for bug# 94475.
+ else
+ width += w;
+
+ return width;
+}
diff --git a/dom/base/nsPlainTextSerializer.h b/dom/base/nsPlainTextSerializer.h
new file mode 100644
index 000000000..95cf5590c
--- /dev/null
+++ b/dom/base/nsPlainTextSerializer.h
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsIContentSerializer implementation that can be used with an
+ * nsIDocumentEncoder to convert a DOM into plaintext in a nice way
+ * (eg for copy/paste as plaintext).
+ */
+
+#ifndef nsPlainTextSerializer_h__
+#define nsPlainTextSerializer_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsIContentSerializer.h"
+#include "nsIDocumentEncoder.h"
+#include "nsILineBreaker.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include <stack>
+
+class nsIContent;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+class nsPlainTextSerializer final : public nsIContentSerializer
+{
+public:
+ nsPlainTextSerializer();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIContentSerializer
+ NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
+ const char* aCharSet, bool aIsCopying,
+ bool aIsWholeDocument) override;
+
+ NS_IMETHOD AppendText(nsIContent* aText, int32_t aStartOffset,
+ int32_t aEndOffset, nsAString& aStr) override;
+ NS_IMETHOD AppendCDATASection(nsIContent* aCDATASection,
+ int32_t aStartOffset, int32_t aEndOffset,
+ nsAString& aStr) override;
+ NS_IMETHOD AppendProcessingInstruction(nsIContent* aPI,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr) override { return NS_OK; }
+ NS_IMETHOD AppendComment(nsIContent* aComment, int32_t aStartOffset,
+ int32_t aEndOffset, nsAString& aStr) override { return NS_OK; }
+ NS_IMETHOD AppendDoctype(nsIContent *aDoctype,
+ nsAString& aStr) override { return NS_OK; }
+ NS_IMETHOD AppendElementStart(mozilla::dom::Element* aElement,
+ mozilla::dom::Element* aOriginalElement,
+ nsAString& aStr) override;
+ NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement,
+ nsAString& aStr) override;
+ NS_IMETHOD Flush(nsAString& aStr) override;
+
+ NS_IMETHOD AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr) override;
+
+private:
+ ~nsPlainTextSerializer();
+
+ nsresult GetAttributeValue(nsIAtom* aName, nsString& aValueRet);
+ void AddToLine(const char16_t* aStringToAdd, int32_t aLength);
+ void EndLine(bool softlinebreak, bool aBreakBySpace = false);
+ void EnsureVerticalSpace(int32_t noOfRows);
+ void FlushLine();
+ void OutputQuotesAndIndent(bool stripTrailingSpaces=false);
+ void Output(nsString& aString);
+ void Write(const nsAString& aString);
+ bool IsInPre();
+ bool IsInOL();
+ bool IsCurrentNodeConverted();
+ bool MustSuppressLeaf();
+
+ /**
+ * Returns the local name of the element as an atom if the element is an
+ * HTML element and the atom is a static atom. Otherwise, nullptr is returned.
+ */
+ static nsIAtom* GetIdForContent(nsIContent* aContent);
+ nsresult DoOpenContainer(nsIAtom* aTag);
+ nsresult DoCloseContainer(nsIAtom* aTag);
+ nsresult DoAddLeaf(nsIAtom* aTag);
+ void DoAddText(bool aIsWhitespace, const nsAString& aText);
+
+ // Inlined functions
+ inline bool MayWrap()
+ {
+ return mWrapColumn &&
+ ((mFlags & nsIDocumentEncoder::OutputFormatted) ||
+ (mFlags & nsIDocumentEncoder::OutputWrap));
+ }
+ inline bool MayBreakLines()
+ {
+ return !(mFlags & nsIDocumentEncoder::OutputDisallowLineBreaking);
+ }
+
+ inline bool DoOutput()
+ {
+ return mHeadLevel == 0;
+ }
+
+ inline bool IsQuotedLine(const nsAString& aLine)
+ {
+ return !aLine.IsEmpty() && aLine.First() == char16_t('>');
+ }
+
+ // Stack handling functions
+ bool GetLastBool(const nsTArray<bool>& aStack);
+ void SetLastBool(nsTArray<bool>& aStack, bool aValue);
+ void PushBool(nsTArray<bool>& aStack, bool aValue);
+ bool PopBool(nsTArray<bool>& aStack);
+
+ bool ShouldReplaceContainerWithPlaceholder(nsIAtom* aTag);
+ bool IsIgnorableRubyAnnotation(nsIAtom* aTag);
+
+ bool IsElementPreformatted(mozilla::dom::Element* aElement);
+ bool IsElementBlock(mozilla::dom::Element* aElement);
+
+private:
+ nsString mCurrentLine;
+ uint32_t mHeadLevel;
+ bool mAtFirstColumn;
+
+ bool mStructs; // Output structs (pref)
+
+ // If we've just written out a cite blockquote, we need to remember it
+ // so we don't duplicate spaces before a <pre wrap> (which mail uses to quote
+ // old messages).
+ bool mHasWrittenCiteBlockquote;
+
+ int32_t mIndent;
+ // mInIndentString keeps a header that has to be written in the indent.
+ // That could be, for instance, the bullet in a bulleted list.
+ nsString mInIndentString;
+ int32_t mCiteQuoteLevel;
+ int32_t mFlags;
+ int32_t mFloatingLines; // To store the number of lazy line breaks
+
+ // The wrap column is how many standard sized chars (western languages)
+ // should be allowed on a line. There could be less chars if the chars
+ // are wider than latin chars of more if the chars are more narrow.
+ uint32_t mWrapColumn;
+
+ // The width of the line as it will appear on the screen (approx.)
+ uint32_t mCurrentLineWidth;
+
+ // Treat quoted text as though it's preformatted -- don't wrap it.
+ // Having it on a pref is a temporary measure, See bug 69638.
+ int32_t mSpanLevel;
+
+
+ int32_t mEmptyLines; // Will be the number of empty lines before
+ // the current. 0 if we are starting a new
+ // line and -1 if we are in a line.
+
+ bool mInWhitespace;
+ bool mPreFormattedMail; // we're dealing with special DOM
+ // used by Thunderbird code.
+ bool mStartedOutput; // we've produced at least a character
+
+ // While handling a new tag, this variable should remind if any line break
+ // is due because of a closing tag. Setting it to "TRUE" while closing the tags.
+ // Hence opening tags are guaranteed to start with appropriate line breaks.
+ bool mLineBreakDue;
+
+ bool mPreformattedBlockBoundary;
+
+ // Whether the output should include ruby annotations.
+ bool mWithRubyAnnotation;
+
+ nsString mURL;
+ int32_t mHeaderStrategy; /* Header strategy (pref)
+ 0 = no indention
+ 1 = indention, increased with
+ header level (default)
+ 2 = numbering and slight indention */
+ int32_t mHeaderCounter[7]; /* For header-numbering:
+ Number of previous headers of
+ the same depth and in the same
+ section.
+ mHeaderCounter[1] for <h1> etc. */
+
+ RefPtr<mozilla::dom::Element> mElement;
+
+ // For handling table rows
+ AutoTArray<bool, 8> mHasWrittenCellsForRow;
+
+ // Values gotten in OpenContainer that is (also) needed in CloseContainer
+ AutoTArray<bool, 8> mIsInCiteBlockquote;
+
+ // The output data
+ nsAString* mOutputString;
+
+ // The tag stack: the stack of tags we're operating on, so we can nest.
+ // The stack only ever points to static atoms, so they don't need to be
+ // refcounted.
+ nsIAtom** mTagStack;
+ uint32_t mTagStackIndex;
+
+ // The stack indicating whether the elements we've been operating on are
+ // CSS preformatted elements, so that we can tell if the text inside them
+ // should be formatted.
+ std::stack<bool> mPreformatStack;
+
+ // Content in the stack above this index should be ignored:
+ uint32_t mIgnoreAboveIndex;
+
+ // The stack for ordered lists
+ int32_t *mOLStack;
+ uint32_t mOLStackIndex;
+
+ uint32_t mULCount;
+
+ nsString mLineBreak;
+ nsCOMPtr<nsILineBreaker> mLineBreaker;
+
+ // Conveniance constant. It would be nice to have it as a const static
+ // variable, but that causes issues with OpenBSD and module unloading.
+ const nsString kSpace;
+
+ // If nsIDocumentEncoder::OutputNonTextContentAsPlaceholder is set, the child
+ // nodes of specific nodes - <iframe>, <canvas>, etc. should be ignored.
+ // mIgnoredChildNodeLevel is used to tell if current node is an ignorable
+ // child node. The initial value of mIgnoredChildNodeLevel is 0. When
+ // serializer enters those specific nodes, mIgnoredChildNodeLevel increases
+ // and is greater than 0. Otherwise when serializer leaves those nodes,
+ // mIgnoredChildNodeLevel decreases.
+ uint32_t mIgnoredChildNodeLevel;
+};
+
+nsresult
+NS_NewPlainTextSerializer(nsIContentSerializer** aSerializer);
+
+#endif
diff --git a/dom/base/nsPluginArray.cpp b/dom/base/nsPluginArray.cpp
new file mode 100644
index 000000000..b9c946ca3
--- /dev/null
+++ b/dom/base/nsPluginArray.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 "nsPluginArray.h"
+
+#include "mozilla/dom/PluginArrayBinding.h"
+#include "mozilla/dom/PluginBinding.h"
+#include "mozilla/dom/HiddenPluginEvent.h"
+
+#include "nsMimeTypeArray.h"
+#include "Navigator.h"
+#include "nsIDocShell.h"
+#include "nsIWebNavigation.h"
+#include "nsPluginHost.h"
+#include "nsPluginTags.h"
+#include "nsIObserverService.h"
+#include "nsIWeakReference.h"
+#include "mozilla/Services.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsContentUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIDocument.h"
+#include "nsIBlocklistService.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+{
+}
+
+void
+nsPluginArray::Init()
+{
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->AddObserver(this, "plugin-info-updated", true);
+ }
+}
+
+nsPluginArray::~nsPluginArray()
+{
+}
+
+static bool
+ResistFingerprinting() {
+ return !nsContentUtils::ThreadsafeIsCallerChrome() &&
+ nsContentUtils::ResistFingerprinting();
+}
+
+nsPIDOMWindowInner*
+nsPluginArray::GetParentObject() const
+{
+ MOZ_ASSERT(mWindow);
+ return mWindow;
+}
+
+JSObject*
+nsPluginArray::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PluginArrayBinding::Wrap(aCx, this, aGivenProto);
+}
+
+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_AMBIGUOUS(nsISupports, nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
+ mWindow,
+ mPlugins,
+ mCTPPlugins)
+
+static void
+GetPluginMimeTypes(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
+ nsTArray<RefPtr<nsMimeType> >& aMimeTypes)
+{
+ for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
+ nsPluginElement *plugin = aPlugins[i];
+ aMimeTypes.AppendElements(plugin->MimeTypes());
+ }
+}
+
+static bool
+operator<(const RefPtr<nsMimeType>& lhs, const RefPtr<nsMimeType>& rhs)
+{
+ // Sort MIME types alphabetically by type name.
+ return lhs->Type() < rhs->Type();
+}
+
+void
+nsPluginArray::GetMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes)
+{
+ aMimeTypes.Clear();
+
+ if (!AllowPlugins()) {
+ return;
+ }
+
+ EnsurePlugins();
+
+ GetPluginMimeTypes(mPlugins, aMimeTypes);
+
+ // Alphabetize the enumeration order of non-hidden MIME types to reduce
+ // fingerprintable entropy based on plugins' installation file times.
+ aMimeTypes.Sort();
+}
+
+void
+nsPluginArray::GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes)
+{
+ aMimeTypes.Clear();
+
+ if (!AllowPlugins()) {
+ return;
+ }
+
+ EnsurePlugins();
+
+ GetPluginMimeTypes(mCTPPlugins, aMimeTypes);
+
+ // Alphabetize the enumeration order of non-hidden MIME types to reduce
+ // fingerprintable entropy based on plugins' installation file times.
+ aMimeTypes.Sort();
+}
+
+nsPluginElement*
+nsPluginArray::Item(uint32_t aIndex)
+{
+ bool unused;
+ return IndexedGetter(aIndex, unused);
+}
+
+nsPluginElement*
+nsPluginArray::NamedItem(const nsAString& aName)
+{
+ bool unused;
+ return NamedGetter(aName, unused);
+}
+
+void
+nsPluginArray::Refresh(bool aReloadDocuments)
+{
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+
+ if(!AllowPlugins() || !pluginHost) {
+ return;
+ }
+
+ // NS_ERROR_PLUGINS_PLUGINSNOTCHANGED on reloading plugins indicates
+ // that plugins did not change and was not reloaded
+ if (pluginHost->ReloadPlugins() ==
+ NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) {
+ nsTArray<nsCOMPtr<nsIInternalPluginTag> > newPluginTags;
+ pluginHost->GetPlugins(newPluginTags);
+
+ // Check if the number of plugins we know about are different from
+ // the number of plugin tags the plugin host knows about. If the
+ // lengths are different, we refresh. This is safe because we're
+ // notified for every plugin enabling/disabling event that
+ // happens, and therefore the lengths will be in sync only when
+ // the both arrays contain the same plugin tags (though as
+ // different types).
+ if (newPluginTags.Length() == mPlugins.Length()) {
+ return;
+ }
+ }
+
+ mPlugins.Clear();
+ mCTPPlugins.Clear();
+
+ nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
+
+ if (!navigator) {
+ return;
+ }
+
+ static_cast<mozilla::dom::Navigator*>(navigator.get())->RefreshMIMEArray();
+
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
+ if (aReloadDocuments && webNav) {
+ webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE);
+ }
+}
+
+nsPluginElement*
+nsPluginArray::IndexedGetter(uint32_t aIndex, bool &aFound)
+{
+ aFound = false;
+
+ if (!AllowPlugins() || ResistFingerprinting()) {
+ return nullptr;
+ }
+
+ EnsurePlugins();
+
+ aFound = aIndex < mPlugins.Length();
+
+ if (!aFound) {
+ return nullptr;
+ }
+
+ return mPlugins[aIndex];
+}
+
+void
+nsPluginArray::Invalidate()
+{
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->RemoveObserver(this, "plugin-info-updated");
+ }
+}
+
+static nsPluginElement*
+FindPlugin(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
+ const nsAString& aName)
+{
+ for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
+ nsAutoString pluginName;
+ nsPluginElement* plugin = aPlugins[i];
+ plugin->GetName(pluginName);
+
+ if (pluginName.Equals(aName)) {
+ return plugin;
+ }
+ }
+
+ return nullptr;
+}
+
+nsPluginElement*
+nsPluginArray::NamedGetter(const nsAString& aName, bool &aFound)
+{
+ aFound = false;
+
+ if (!AllowPlugins() || ResistFingerprinting()) {
+ return nullptr;
+ }
+
+ EnsurePlugins();
+
+ nsPluginElement* plugin = FindPlugin(mPlugins, aName);
+ aFound = (plugin != nullptr);
+ if (!aFound) {
+ nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
+ if (hiddenPlugin) {
+ NotifyHiddenPluginTouched(hiddenPlugin);
+ }
+ }
+ return plugin;
+}
+
+void nsPluginArray::NotifyHiddenPluginTouched(nsPluginElement* aHiddenElement)
+{
+ HiddenPluginEventInit init;
+ init.mTag = aHiddenElement->PluginTag();
+ nsCOMPtr<nsIDocument> doc = aHiddenElement->GetParentObject()->GetDoc();
+ RefPtr<HiddenPluginEvent> event =
+ HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
+ event->SetTarget(doc);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ bool dummy;
+ doc->DispatchEvent(event, &dummy);
+}
+
+uint32_t
+nsPluginArray::Length()
+{
+ if (!AllowPlugins() || ResistFingerprinting()) {
+ return 0;
+ }
+
+ EnsurePlugins();
+
+ return mPlugins.Length();
+}
+
+void
+nsPluginArray::GetSupportedNames(nsTArray<nsString>& aRetval)
+{
+ aRetval.Clear();
+
+ if (!AllowPlugins()) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < mPlugins.Length(); ++i) {
+ nsAutoString pluginName;
+ mPlugins[i]->GetName(pluginName);
+
+ aRetval.AppendElement(pluginName);
+ }
+}
+
+NS_IMETHODIMP
+nsPluginArray::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData) {
+ if (!nsCRT::strcmp(aTopic, "plugin-info-updated")) {
+ Refresh(false);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsPluginArray::AllowPlugins() const
+{
+ nsCOMPtr<nsIDocShell> docShell = mWindow ? mWindow->GetDocShell() : nullptr;
+
+ return docShell && docShell->PluginsAllowedInCurrentDoc();
+}
+
+static bool
+operator<(const RefPtr<nsPluginElement>& lhs,
+ const RefPtr<nsPluginElement>& rhs)
+{
+ // Sort plugins alphabetically by name.
+ return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
+}
+
+static bool
+PluginShouldBeHidden(nsCString aName) {
+ // This only supports one hidden plugin
+ return Preferences::GetCString("plugins.navigator.hidden_ctp_plugin").Equals(aName);
+}
+
+void
+nsPluginArray::EnsurePlugins()
+{
+ if (!mPlugins.IsEmpty() || !mCTPPlugins.IsEmpty()) {
+ // We already have an array of plugin elements.
+ return;
+ }
+
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ if (!pluginHost) {
+ // We have no plugin host.
+ return;
+ }
+
+ nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
+ pluginHost->GetPlugins(pluginTags);
+
+ // need to wrap each of these with a nsPluginElement, which is
+ // scriptable.
+ for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
+ nsCOMPtr<nsPluginTag> pluginTag = do_QueryInterface(pluginTags[i]);
+ if (!pluginTag) {
+ mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+ } else if (pluginTag->IsActive()) {
+ uint32_t permission = nsIPermissionManager::ALLOW_ACTION;
+ uint32_t blocklistState;
+ if (pluginTag->IsClicktoplay() &&
+ NS_SUCCEEDED(pluginTag->GetBlocklistState(&blocklistState)) &&
+ blocklistState == nsIBlocklistService::STATE_NOT_BLOCKED) {
+ nsCString name;
+ pluginTag->GetName(name);
+ if (PluginShouldBeHidden(name)) {
+ RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+ nsCString permString;
+ nsresult rv = pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
+ if (rv == NS_OK) {
+ nsIPrincipal* principal = mWindow->GetExtantDoc()->NodePrincipal();
+ nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+ permMgr->TestPermissionFromPrincipal(principal, permString.get(), &permission);
+ }
+ }
+ }
+ if (permission == nsIPermissionManager::ALLOW_ACTION) {
+ mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+ } else {
+ mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+ }
+ }
+ }
+
+ if (mPlugins.Length() == 0 && mCTPPlugins.Length() != 0) {
+ nsCOMPtr<nsPluginTag> hiddenTag = new nsPluginTag("Hidden Plugin", NULL, "dummy.plugin", NULL, NULL,
+ NULL, NULL, NULL, 0, 0, false);
+ mPlugins.AppendElement(new nsPluginElement(mWindow, hiddenTag));
+ }
+
+ // Alphabetize the enumeration order of non-hidden plugins to reduce
+ // fingerprintable entropy based on plugins' installation file times.
+ mPlugins.Sort();
+}
+
+// nsPluginElement implementation.
+
+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, mMimeTypes)
+
+nsPluginElement::nsPluginElement(nsPIDOMWindowInner* aWindow,
+ nsIInternalPluginTag* aPluginTag)
+ : mWindow(aWindow),
+ mPluginTag(aPluginTag)
+{
+}
+
+nsPluginElement::~nsPluginElement()
+{
+}
+
+nsPIDOMWindowInner*
+nsPluginElement::GetParentObject() const
+{
+ MOZ_ASSERT(mWindow);
+ return mWindow;
+}
+
+JSObject*
+nsPluginElement::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PluginBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+nsPluginElement::GetDescription(nsString& retval) const
+{
+ CopyUTF8toUTF16(mPluginTag->Description(), retval);
+}
+
+void
+nsPluginElement::GetFilename(nsString& retval) const
+{
+ CopyUTF8toUTF16(mPluginTag->FileName(), retval);
+}
+
+void
+nsPluginElement::GetVersion(nsString& retval) const
+{
+ CopyUTF8toUTF16(mPluginTag->Version(), retval);
+}
+
+void
+nsPluginElement::GetName(nsString& retval) const
+{
+ CopyUTF8toUTF16(mPluginTag->Name(), retval);
+}
+
+nsMimeType*
+nsPluginElement::Item(uint32_t aIndex)
+{
+ EnsurePluginMimeTypes();
+
+ return mMimeTypes.SafeElementAt(aIndex);
+}
+
+nsMimeType*
+nsPluginElement::NamedItem(const nsAString& aName)
+{
+ bool unused;
+ return NamedGetter(aName, unused);
+}
+
+nsMimeType*
+nsPluginElement::IndexedGetter(uint32_t aIndex, bool &aFound)
+{
+ EnsurePluginMimeTypes();
+
+ aFound = aIndex < mMimeTypes.Length();
+
+ if (!aFound) {
+ return nullptr;
+ }
+
+ return mMimeTypes[aIndex];
+}
+
+nsMimeType*
+nsPluginElement::NamedGetter(const nsAString& aName, bool &aFound)
+{
+ EnsurePluginMimeTypes();
+
+ aFound = false;
+
+ for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
+ if (mMimeTypes[i]->Type().Equals(aName)) {
+ aFound = true;
+
+ return mMimeTypes[i];
+ }
+ }
+
+ return nullptr;
+}
+
+uint32_t
+nsPluginElement::Length()
+{
+ EnsurePluginMimeTypes();
+
+ return mMimeTypes.Length();
+}
+
+void
+nsPluginElement::GetSupportedNames(nsTArray<nsString>& retval)
+{
+ EnsurePluginMimeTypes();
+
+ for (uint32_t i = 0; i < mMimeTypes.Length(); ++i) {
+ retval.AppendElement(mMimeTypes[i]->Type());
+ }
+}
+
+nsTArray<RefPtr<nsMimeType> >&
+nsPluginElement::MimeTypes()
+{
+ EnsurePluginMimeTypes();
+
+ return mMimeTypes;
+}
+
+void
+nsPluginElement::EnsurePluginMimeTypes()
+{
+ if (!mMimeTypes.IsEmpty()) {
+ return;
+ }
+
+ if (mPluginTag->MimeTypes().Length() != mPluginTag->MimeDescriptions().Length() ||
+ mPluginTag->MimeTypes().Length() != mPluginTag->Extensions().Length()) {
+ MOZ_ASSERT(false, "mime type arrays expected to be the same length");
+ return;
+ }
+
+ for (uint32_t i = 0; i < mPluginTag->MimeTypes().Length(); ++i) {
+ NS_ConvertUTF8toUTF16 type(mPluginTag->MimeTypes()[i]);
+ NS_ConvertUTF8toUTF16 description(mPluginTag->MimeDescriptions()[i]);
+ NS_ConvertUTF8toUTF16 extension(mPluginTag->Extensions()[i]);
+
+ mMimeTypes.AppendElement(new nsMimeType(mWindow, this, type, description,
+ extension));
+ }
+}
diff --git a/dom/base/nsPluginArray.h b/dom/base/nsPluginArray.h
new file mode 100644
index 000000000..6f9ce4651
--- /dev/null
+++ b/dom/base/nsPluginArray.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/. */
+
+#ifndef nsPluginArray_h___
+#define nsPluginArray_h___
+
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+#include "nsWrapperCache.h"
+#include "nsPIDOMWindow.h"
+
+class nsPluginElement;
+class nsMimeType;
+class nsIInternalPluginTag;
+
+class nsPluginArray final : public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsPluginArray,
+ nsIObserver)
+
+ // nsIObserver
+ NS_DECL_NSIOBSERVER
+
+ explicit nsPluginArray(nsPIDOMWindowInner* aWindow);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // nsPluginArray registers itself as an observer with a weak reference.
+ // This can't be done in the constructor, because at that point its
+ // refcount is 0 (and it gets destroyed upon registration). So, Init()
+ // must be called after construction.
+ void Init();
+ void Invalidate();
+
+ void GetMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes);
+ void GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes);
+
+ static void NotifyHiddenPluginTouched(nsPluginElement* aElement);
+
+ // PluginArray WebIDL methods
+
+ nsPluginElement* Item(uint32_t aIndex);
+ nsPluginElement* NamedItem(const nsAString& aName);
+ void Refresh(bool aReloadDocuments);
+ nsPluginElement* IndexedGetter(uint32_t aIndex, bool &aFound);
+ nsPluginElement* NamedGetter(const nsAString& aName, bool &aFound);
+ uint32_t Length();
+ void GetSupportedNames(nsTArray<nsString>& aRetval);
+
+private:
+ virtual ~nsPluginArray();
+
+ bool AllowPlugins() const;
+ void EnsurePlugins();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsTArray<RefPtr<nsPluginElement> > mPlugins;
+ /* A separate list of click-to-play plugins that we don't tell content
+ * about but keep track of so we can still prompt the user to click to play.
+ */
+ nsTArray<RefPtr<nsPluginElement> > mCTPPlugins;
+};
+
+class nsPluginElement final : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
+
+ nsPluginElement(nsPIDOMWindowInner* aWindow,
+ nsIInternalPluginTag* aPluginTag);
+
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsIInternalPluginTag* PluginTag() const
+ {
+ return mPluginTag;
+ }
+
+ // Plugin WebIDL methods
+
+ void GetDescription(nsString& retval) const;
+ void GetFilename(nsString& retval) const;
+ void GetVersion(nsString& retval) const;
+ void GetName(nsString& retval) const;
+ nsMimeType* Item(uint32_t index);
+ nsMimeType* NamedItem(const nsAString& name);
+ nsMimeType* IndexedGetter(uint32_t index, bool &found);
+ nsMimeType* NamedGetter(const nsAString& name, bool &found);
+ uint32_t Length();
+ void GetSupportedNames(nsTArray<nsString>& retval);
+
+ nsTArray<RefPtr<nsMimeType> >& MimeTypes();
+
+protected:
+ ~nsPluginElement();
+
+ void EnsurePluginMimeTypes();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsCOMPtr<nsIInternalPluginTag> mPluginTag;
+ nsTArray<RefPtr<nsMimeType> > mMimeTypes;
+};
+
+#endif /* nsPluginArray_h___ */
diff --git a/dom/base/nsPropertyTable.cpp b/dom/base/nsPropertyTable.cpp
new file mode 100644
index 000000000..f0f81ba3f
--- /dev/null
+++ b/dom/base/nsPropertyTable.cpp
@@ -0,0 +1,342 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/**
+ * 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
+ * nsIAtom pointers, and the values are void pointers.
+ */
+
+#include "nsPropertyTable.h"
+
+#include "mozilla/MemoryReporting.h"
+
+#include "PLDHashTable.h"
+#include "nsError.h"
+#include "nsIAtom.h"
+
+struct PropertyListMapEntry : public PLDHashEntryHdr {
+ const void *key;
+ void *value;
+};
+
+//----------------------------------------------------------------------
+
+class nsPropertyTable::PropertyList {
+public:
+ PropertyList(nsIAtom* aName,
+ NSPropertyDtorFunc aDtorFunc,
+ void* aDtorData,
+ bool aTransfer);
+ ~PropertyList();
+
+ // Removes the property associated with the given object, and destroys
+ // the property value
+ bool DeletePropertyFor(nsPropertyOwner aObject);
+
+ // Destroy all remaining properties (without removing them)
+ void Destroy();
+
+ bool Equals(nsIAtom *aPropertyName)
+ {
+ return mName == aPropertyName;
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ nsCOMPtr<nsIAtom> 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
+ // TransferOrDeleteAllPropertiesFor
+
+ PropertyList* mNext;
+};
+
+void
+nsPropertyTable::DeleteAllProperties()
+{
+ while (mPropertyList) {
+ PropertyList* tmp = mPropertyList;
+
+ mPropertyList = mPropertyList->mNext;
+ tmp->Destroy();
+ delete tmp;
+ }
+}
+
+void
+nsPropertyTable::DeleteAllPropertiesFor(nsPropertyOwner aObject)
+{
+ for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
+ prop->DeletePropertyFor(aObject);
+ }
+}
+
+nsresult
+nsPropertyTable::TransferOrDeleteAllPropertiesFor(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)) {
+ DeleteAllPropertiesFor(aObject);
+ aOtherTable->DeleteAllPropertiesFor(aObject);
+
+ break;
+ }
+
+ prop->mObjectValueMap.RemoveEntry(entry);
+ }
+ }
+ else {
+ prop->DeletePropertyFor(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.Iter(); !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,
+ nsIAtom *aPropertyName,
+ bool aRemove,
+ nsresult *aResult)
+{
+ NS_PRECONDITION(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,
+ nsIAtom *aPropertyName,
+ void *aPropertyValue,
+ NSPropertyDtorFunc aPropDtorFunc,
+ void *aPropDtorData,
+ bool aTransfer,
+ void **aOldValue)
+{
+ NS_PRECONDITION(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 (aOldValue)
+ *aOldValue = entry->value;
+ else if (propertyList->mDtorFunc)
+ propertyList->mDtorFunc(const_cast<void*>(entry->key), aPropertyName,
+ entry->value, propertyList->mDtorData);
+ result = NS_PROPTABLE_PROP_OVERWRITTEN;
+ }
+ else if (aOldValue) {
+ *aOldValue = nullptr;
+ }
+ entry->key = aObject;
+ entry->value = aPropertyValue;
+
+ return result;
+}
+
+nsresult
+nsPropertyTable::DeleteProperty(nsPropertyOwner aObject,
+ nsIAtom *aPropertyName)
+{
+ NS_PRECONDITION(aPropertyName && aObject, "unexpected null param");
+
+ PropertyList* propertyList = GetPropertyListFor(aPropertyName);
+ if (propertyList) {
+ if (propertyList->DeletePropertyFor(aObject))
+ return NS_OK;
+ }
+
+ return NS_PROPTABLE_PROP_NOT_THERE;
+}
+
+nsPropertyTable::PropertyList*
+nsPropertyTable::GetPropertyListFor(nsIAtom* aPropertyName) const
+{
+ PropertyList* result;
+
+ for (result = mPropertyList; result; result = result->mNext) {
+ if (result->Equals(aPropertyName)) {
+ break;
+ }
+ }
+
+ return result;
+}
+
+//----------------------------------------------------------------------
+
+nsPropertyTable::PropertyList::PropertyList(nsIAtom *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()
+{
+}
+
+void
+nsPropertyTable::PropertyList::Destroy()
+{
+ // Enumerate any remaining object/value pairs and destroy the value object.
+ if (mDtorFunc) {
+ for (auto iter = mObjectValueMap.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PropertyListMapEntry*>(iter.Get());
+ mDtorFunc(const_cast<void*>(entry->key), mName, entry->value, mDtorData);
+ }
+ }
+}
+
+bool
+nsPropertyTable::PropertyList::DeletePropertyFor(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, nsIAtom *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 000000000..9a82d4163
--- /dev/null
+++ b/dom/base/nsPropertyTable.h
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+/**
+ * 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
+ * nsIAtom pointers, and the values are void pointers.
+ */
+
+#ifndef nsPropertyTable_h_
+#define nsPropertyTable_h_
+
+#include "mozilla/MemoryReporting.h"
+#include "nscore.h"
+
+class nsIAtom;
+
+typedef void
+(*NSPropertyFunc)(void *aObject,
+ nsIAtom *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().
+ **/
+typedef NSPropertyFunc NSPropertyDtorFunc;
+class nsINode;
+class nsIFrame;
+
+class nsPropertyOwner
+{
+public:
+ nsPropertyOwner(const nsPropertyOwner& aOther) : mObject(aOther.mObject) {}
+
+ // 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(nsPropertyOwner aObject,
+ nsIAtom *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 aOldValue is non-null
+ * it will contain the old value after the function returns (the destructor
+ * for the old value will not be run in that case). 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(nsPropertyOwner aObject,
+ nsIAtom *aPropertyName,
+ void *aPropertyValue,
+ NSPropertyDtorFunc aDtor,
+ void *aDtorData,
+ bool aTransfer = false,
+ void **aOldValue = nullptr)
+ {
+ return SetPropertyInternal(aObject, aPropertyName, aPropertyValue,
+ aDtor, aDtorData, aTransfer, aOldValue);
+ }
+
+ /**
+ * Delete the property |aPropertyName| in the global category for object
+ * |aObject|. The property's destructor function will be called.
+ */
+ nsresult DeleteProperty(nsPropertyOwner aObject,
+ nsIAtom *aPropertyName);
+
+ /**
+ * Unset 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* UnsetProperty(nsPropertyOwner aObject,
+ nsIAtom *aPropertyName,
+ nsresult *aStatus = nullptr)
+ {
+ return GetPropertyInternal(aObject, aPropertyName, true, aStatus);
+ }
+
+ /**
+ * Deletes all of the properties for object |aObject|, calling the
+ * destructor function for each property.
+ */
+ void DeleteAllPropertiesFor(nsPropertyOwner aObject);
+
+ /**
+ * Transfers all properties for object |aObject| that were set with the
+ * |aTransfer| argument as true to |aTable|. Deletes 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
+ TransferOrDeleteAllPropertiesFor(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);
+
+ /**
+ * Deletes all of the properties for all objects in the property
+ * table, calling the destructor function for each property.
+ */
+ void DeleteAllProperties();
+
+ nsPropertyTable() : mPropertyList(nullptr) {}
+ ~nsPropertyTable() {
+ DeleteAllProperties();
+ }
+
+ /**
+ * 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, nsIAtom *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(nsIAtom *aPropertyName) const;
+ void* GetPropertyInternal(nsPropertyOwner aObject,
+ nsIAtom *aPropertyName,
+ bool aRemove,
+ nsresult *aStatus);
+ nsresult SetPropertyInternal(nsPropertyOwner aObject,
+ nsIAtom *aPropertyName,
+ void *aPropertyValue,
+ NSPropertyDtorFunc aDtor,
+ void *aDtorData,
+ bool aTransfer,
+ void **aOldValue);
+
+ PropertyList *mPropertyList;
+};
+#endif
diff --git a/dom/base/nsQueryContentEventResult.cpp b/dom/base/nsQueryContentEventResult.cpp
new file mode 100644
index 000000000..462cfe86a
--- /dev/null
+++ b/dom/base/nsQueryContentEventResult.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 "nsIWidget.h"
+#include "nsPoint.h"
+#include "nsQueryContentEventResult.h"
+#include "mozilla/Move.h"
+#include "mozilla/TextEvents.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()
+ : mEventMessage(eVoidEvent)
+ , mSucceeded(false)
+{
+}
+
+nsQueryContentEventResult::~nsQueryContentEventResult()
+{
+}
+
+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 = mOffset;
+ 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;
+ 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,
+ NS_ERROR_NOT_AVAILABLE);
+ aText = mString;
+ 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 = (mOffset == WidgetQueryContentEvent::NOT_FOUND);
+ 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 == WidgetQueryContentEvent::NOT_FOUND);
+ 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<uint32_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,
+ WidgetQueryContentEvent &aEvent)
+{
+ mEventMessage = aEvent.mMessage;
+ mSucceeded = aEvent.mSucceeded;
+ mReversed = aEvent.mReply.mReversed;
+ mRect = aEvent.mReply.mRect;
+ mOffset = aEvent.mReply.mOffset;
+ mTentativeCaretOffset = aEvent.mReply.mTentativeCaretOffset;
+ mString = aEvent.mReply.mString;
+ mRectArray = mozilla::Move(aEvent.mReply.mRectArray);
+ // Mark as result that is longer used.
+ aEvent.mSucceeded = false;
+
+ 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 000000000..5ef6223a7
--- /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 "Units.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+
+class nsIWidget;
+
+class nsQueryContentEventResult final : public nsIQueryContentEventResult
+{
+public:
+ nsQueryContentEventResult();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIQUERYCONTENTEVENTRESULT
+
+ void SetEventResult(nsIWidget* aWidget,
+ mozilla::WidgetQueryContentEvent& aEvent);
+
+protected:
+ ~nsQueryContentEventResult();
+
+ mozilla::EventMessage mEventMessage;
+
+ uint32_t mOffset;
+ uint32_t mTentativeCaretOffset;
+ nsString mString;
+ mozilla::LayoutDeviceIntRect mRect;
+ nsTArray<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 000000000..37ba147af
--- /dev/null
+++ b/dom/base/nsRange.cpp
@@ -0,0 +1,3579 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMRange object.
+ */
+
+#include "nscore.h"
+#include "nsRange.h"
+
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMDocumentFragment.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMText.h"
+#include "nsError.h"
+#include "nsIContentIterator.h"
+#include "nsIDOMNodeList.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsGenericDOMDataNode.h"
+#include "nsTextFrame.h"
+#include "nsFontFaceList.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/RangeBinding.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Likely.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsStyleStruct.h"
+#include "nsStyleStructInlines.h"
+#include "nsComputedDOMStyle.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+JSObject*
+nsRange::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return RangeBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/******************************************************
+ * stack based utilty class for managing monitor
+ ******************************************************/
+
+static void InvalidateAllFrames(nsINode* aNode)
+{
+ NS_PRECONDITION(aNode, "bad arg");
+
+ nsIFrame* frame = nullptr;
+ switch (aNode->NodeType()) {
+ case nsIDOMNode::TEXT_NODE:
+ case nsIDOMNode::ELEMENT_NODE:
+ {
+ nsIContent* content = static_cast<nsIContent*>(aNode);
+ frame = content->GetPrimaryFrame();
+ break;
+ }
+ case nsIDOMNode::DOCUMENT_NODE:
+ {
+ nsIDocument* doc = static_cast<nsIDocument*>(aNode);
+ nsIPresShell* shell = doc ? doc->GetShell() : nullptr;
+ frame = shell ? shell->GetRootFrame() : nullptr;
+ break;
+ }
+ }
+ for (nsIFrame* f = frame; f; f = f->GetNextContinuation()) {
+ f->InvalidateFrameSubtree();
+ }
+}
+
+// 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
+nsRange::CompareNodeToRange(nsINode* aNode, nsRange* aRange,
+ bool *outNodeBefore, bool *outNodeAfter)
+{
+ NS_ENSURE_STATE(aNode);
+ // 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.
+
+ if (!aRange || !aRange->IsPositioned())
+ return NS_ERROR_UNEXPECTED;
+
+ // gather up the dom point info
+ int32_t nodeStart, 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->IndexOf(aNode);
+ nodeEnd = nodeStart + 1;
+ }
+
+ nsINode* rangeStartParent = aRange->GetStartParent();
+ nsINode* rangeEndParent = aRange->GetEndParent();
+ int32_t rangeStartOffset = aRange->StartOffset();
+ int32_t rangeEndOffset = aRange->EndOffset();
+
+ // is RANGE(start) <= NODE(start) ?
+ bool disconnected = false;
+ *outNodeBefore = nsContentUtils::ComparePoints(rangeStartParent,
+ rangeStartOffset,
+ parent, nodeStart,
+ &disconnected) > 0;
+ NS_ENSURE_TRUE(!disconnected, NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+
+ // is RANGE(end) >= NODE(end) ?
+ *outNodeAfter = nsContentUtils::ComparePoints(rangeEndParent,
+ rangeEndOffset,
+ parent, nodeEnd,
+ &disconnected) < 0;
+ NS_ENSURE_TRUE(!disconnected, NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+ return NS_OK;
+}
+
+static nsINode*
+GetNextRangeCommonAncestor(nsINode* aNode)
+{
+ while (aNode && !aNode->IsCommonAncestorForRangeInSelection()) {
+ if (!aNode->IsDescendantOfCommonAncestorForRangeInSelection()) {
+ 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).
+ */
+struct IsItemInRangeComparator
+{
+ nsINode* mNode;
+ uint32_t mStartOffset;
+ uint32_t mEndOffset;
+
+ int operator()(const nsRange* const aRange) const
+ {
+ int32_t cmp = nsContentUtils::ComparePoints(mNode, mEndOffset,
+ aRange->GetStartParent(),
+ aRange->StartOffset());
+ if (cmp == 1) {
+ cmp = nsContentUtils::ComparePoints(mNode, mStartOffset,
+ aRange->GetEndParent(),
+ aRange->EndOffset());
+ if (cmp == -1) {
+ return 0;
+ }
+ return 1;
+ }
+ return -1;
+ }
+};
+
+/* static */ bool
+nsRange::IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
+ uint32_t aEndOffset)
+{
+ NS_PRECONDITION(aNode, "bad arg");
+
+ nsINode* n = GetNextRangeCommonAncestor(aNode);
+ NS_ASSERTION(n || !aNode->IsSelectionDescendant(),
+ "orphan selection descendant");
+
+ // Collect the potential ranges and their selection objects.
+ RangeHashTable ancestorSelectionRanges;
+ nsTHashtable<nsPtrHashKey<Selection>> ancestorSelections;
+ uint32_t maxRangeCount = 0;
+ for (; n; n = GetNextRangeCommonAncestor(n->GetParentNode())) {
+ RangeHashTable* ranges =
+ static_cast<RangeHashTable*>(n->GetProperty(nsGkAtoms::range));
+ for (auto iter = ranges->ConstIter(); !iter.Done(); iter.Next()) {
+ nsRange* range = iter.Get()->GetKey();
+ if (range->IsInSelection() && !range->Collapsed()) {
+ ancestorSelectionRanges.PutEntry(range);
+ Selection* selection = range->mSelection;
+ ancestorSelections.PutEntry(selection);
+ maxRangeCount = std::max(maxRangeCount, selection->RangeCount());
+ }
+ }
+ }
+
+ if (!ancestorSelectionRanges.IsEmpty()) {
+ nsTArray<const nsRange*> sortedRanges(maxRangeCount);
+ for (auto iter = ancestorSelections.ConstIter(); !iter.Done(); iter.Next()) {
+ Selection* selection = iter.Get()->GetKey();
+ // Sort the found ranges for |selection| in document order
+ // (Selection::GetRangeAt returns its ranges ordered).
+ for (uint32_t i = 0, len = selection->RangeCount(); i < len; ++i) {
+ nsRange* range = selection->GetRangeAt(i);
+ if (ancestorSelectionRanges.Contains(range)) {
+ sortedRanges.AppendElement(range);
+ }
+ }
+ MOZ_ASSERT(!sortedRanges.IsEmpty());
+ // Binary search the now sorted ranges.
+ IsItemInRangeComparator comparator = { aNode, aStartOffset, aEndOffset };
+ size_t unused;
+ if (mozilla::BinarySearchIf(sortedRanges, 0, sortedRanges.Length(), comparator, &unused)) {
+ return true;
+ }
+ sortedRanges.ClearAndRetainStorage();
+ }
+ }
+ return false;
+}
+
+/******************************************************
+ * constructor/destructor
+ ******************************************************/
+
+nsRange::~nsRange()
+{
+ NS_ASSERTION(!IsInSelection(), "deleting nsRange that is in use");
+
+ // we want the side effects (releases and list removals)
+ DoSetRange(nullptr, 0, nullptr, 0, nullptr);
+}
+
+nsRange::nsRange(nsINode* aNode)
+ : mRoot(nullptr)
+ , mStartOffset(0)
+ , mEndOffset(0)
+ , mIsPositioned(false)
+ , mMaySpanAnonymousSubtrees(false)
+ , mIsGenerated(false)
+ , mStartOffsetWasIncremented(false)
+ , mEndOffsetWasIncremented(false)
+ , mEnableGravitationOnElementRemoval(true)
+#ifdef DEBUG
+ , mAssertNextInsertOrAppendIndex(-1)
+ , mAssertNextInsertOrAppendNode(nullptr)
+#endif
+{
+ MOZ_ASSERT(aNode, "range isn't in a document!");
+ mOwner = aNode->OwnerDoc();
+}
+
+/* static */
+nsresult
+nsRange::CreateRange(nsINode* aStartParent, int32_t aStartOffset,
+ nsINode* aEndParent, int32_t aEndOffset,
+ nsRange** aRange)
+{
+ nsCOMPtr<nsIDOMNode> startDomNode = do_QueryInterface(aStartParent);
+ nsCOMPtr<nsIDOMNode> endDomNode = do_QueryInterface(aEndParent);
+
+ nsresult rv = CreateRange(startDomNode, aStartOffset, endDomNode, aEndOffset,
+ aRange);
+
+ return rv;
+
+}
+
+/* static */
+nsresult
+nsRange::CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset,
+ nsIDOMNode* aEndParent, int32_t aEndOffset,
+ nsRange** aRange)
+{
+ MOZ_ASSERT(aRange);
+ *aRange = nullptr;
+
+ nsCOMPtr<nsINode> startParent = do_QueryInterface(aStartParent);
+ NS_ENSURE_ARG_POINTER(startParent);
+
+ RefPtr<nsRange> range = new nsRange(startParent);
+
+ nsresult rv = range->SetStart(startParent, aStartOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = range->SetEnd(aEndParent, aEndOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ range.forget(aRange);
+ return NS_OK;
+}
+
+/* static */
+nsresult
+nsRange::CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset,
+ nsIDOMNode* aEndParent, int32_t aEndOffset,
+ nsIDOMRange** aRange)
+{
+ RefPtr<nsRange> range;
+ nsresult rv = nsRange::CreateRange(aStartParent, aStartOffset, aEndParent,
+ aEndOffset, getter_AddRefs(range));
+ range.forget(aRange);
+ return rv;
+}
+
+/******************************************************
+ * nsISupports
+ ******************************************************/
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsRange)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsRange,
+ DoSetRange(nullptr, 0, nullptr, 0, nullptr))
+
+// QueryInterface implementation for nsRange
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsRange)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIDOMRange)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMRange)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsRange)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner);
+ tmp->Reset();
+
+ // This needs to be unlinked after Reset() is called, as it controls
+ // the result of IsInSelection() which is used by tmp->Reset().
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelection);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsRange)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStartParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelection)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsRange)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+static void MarkDescendants(nsINode* aNode)
+{
+ // Set NodeIsDescendantOfCommonAncestorForRangeInSelection 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->IsSelectionDescendant()) {
+ // don't set the Descendant bit on |aNode| itself
+ nsINode* node = aNode->GetNextNode(aNode);
+ while (node) {
+ node->SetDescendantOfCommonAncestorForRangeInSelection();
+ if (!node->IsCommonAncestorForRangeInSelection()) {
+ node = node->GetNextNode(aNode);
+ } else {
+ // optimize: skip this sub-tree since it's marked already.
+ node = node->GetNextNonChildNode(aNode);
+ }
+ }
+ }
+}
+
+static void UnmarkDescendants(nsINode* aNode)
+{
+ // Unset NodeIsDescendantOfCommonAncestorForRangeInSelection 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->IsDescendantOfCommonAncestorForRangeInSelection()) {
+ // we know |aNode| doesn't have any bit set
+ nsINode* node = aNode->GetNextNode(aNode);
+ while (node) {
+ node->ClearDescendantOfCommonAncestorForRangeInSelection();
+ if (!node->IsCommonAncestorForRangeInSelection()) {
+ node = node->GetNextNode(aNode);
+ } else {
+ // We found an ancestor of an overlapping range, skip its descendants.
+ node = node->GetNextNonChildNode(aNode);
+ }
+ }
+ }
+}
+
+void
+nsRange::RegisterCommonAncestor(nsINode* aNode)
+{
+ NS_PRECONDITION(aNode, "bad arg");
+ NS_ASSERTION(IsInSelection(), "registering range not in selection");
+
+ MarkDescendants(aNode);
+
+ RangeHashTable* ranges =
+ static_cast<RangeHashTable*>(aNode->GetProperty(nsGkAtoms::range));
+ if (!ranges) {
+ ranges = new RangeHashTable;
+ aNode->SetProperty(nsGkAtoms::range, ranges,
+ nsINode::DeleteProperty<nsRange::RangeHashTable>, true);
+ }
+ ranges->PutEntry(this);
+ aNode->SetCommonAncestorForRangeInSelection();
+}
+
+void
+nsRange::UnregisterCommonAncestor(nsINode* aNode)
+{
+ NS_PRECONDITION(aNode, "bad arg");
+ NS_ASSERTION(aNode->IsCommonAncestorForRangeInSelection(), "wrong node");
+ RangeHashTable* ranges =
+ static_cast<RangeHashTable*>(aNode->GetProperty(nsGkAtoms::range));
+ NS_ASSERTION(ranges->GetEntry(this), "unknown range");
+
+ if (ranges->Count() == 1) {
+ aNode->ClearCommonAncestorForRangeInSelection();
+ aNode->DeleteProperty(nsGkAtoms::range);
+ UnmarkDescendants(aNode);
+ } else {
+ ranges->RemoveEntry(this);
+ }
+}
+
+/******************************************************
+ * nsIMutationObserver implementation
+ ******************************************************/
+
+void
+nsRange::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ MOZ_ASSERT(mAssertNextInsertOrAppendIndex == -1,
+ "splitText failed to notify insert/append?");
+ NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned");
+
+ nsINode* newRoot = nullptr;
+ nsINode* newStartNode = nullptr;
+ nsINode* newEndNode = nullptr;
+ uint32_t newStartOffset = 0;
+ uint32_t newEndOffset = 0;
+
+ if (aInfo->mDetails &&
+ aInfo->mDetails->mType == CharacterDataChangeInfo::Details::eSplit) {
+ // 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 increment the corresponding offset to account for the new
+ // text node that will be inserted. If so, we need to prevent the next
+ // ContentInserted or ContentAppended for this range from incrementing it
+ // again (when the new text node is notified).
+ nsINode* parentNode = aContent->GetParentNode();
+ int32_t index = -1;
+ if (parentNode == mEndParent && mEndOffset > 0 &&
+ (index = parentNode->IndexOf(aContent)) + 1 == mEndOffset) {
+ ++mEndOffset;
+ mEndOffsetWasIncremented = true;
+ }
+ if (parentNode == mStartParent && mStartOffset > 0 &&
+ (index != -1 ? index : parentNode->IndexOf(aContent)) + 1 == mStartOffset) {
+ ++mStartOffset;
+ mStartOffsetWasIncremented = true;
+ }
+#ifdef DEBUG
+ if (mStartOffsetWasIncremented || mEndOffsetWasIncremented) {
+ mAssertNextInsertOrAppendIndex =
+ (mStartOffsetWasIncremented ? mStartOffset : mEndOffset) - 1;
+ mAssertNextInsertOrAppendNode = aInfo->mDetails->mNextSibling;
+ }
+#endif
+ }
+
+ // If the changed node contains our start boundary and the change starts
+ // before the boundary we'll need to adjust the offset.
+ if (aContent == mStartParent &&
+ aInfo->mChangeStart < static_cast<uint32_t>(mStartOffset)) {
+ 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(static_cast<uint32_t>(mStartOffset) <= aInfo->mChangeEnd + 1,
+ "mStartOffset is beyond the end of this node");
+ newStartOffset = static_cast<uint32_t>(mStartOffset) - aInfo->mChangeStart;
+ newStartNode = aInfo->mDetails->mNextSibling;
+ if (MOZ_UNLIKELY(aContent == mRoot)) {
+ newRoot = IsValidBoundary(newStartNode);
+ }
+
+ bool isCommonAncestor = IsInSelection() && mStartParent == mEndParent;
+ if (isCommonAncestor) {
+ UnregisterCommonAncestor(mStartParent);
+ RegisterCommonAncestor(newStartNode);
+ }
+ if (mStartParent->IsDescendantOfCommonAncestorForRangeInSelection()) {
+ newStartNode->SetDescendantOfCommonAncestorForRangeInSelection();
+ }
+ } else {
+ // If boundary is inside changed text, position it before change
+ // else adjust start offset for the change in length.
+ mStartOffset = static_cast<uint32_t>(mStartOffset) <= aInfo->mChangeEnd ?
+ aInfo->mChangeStart :
+ mStartOffset + aInfo->mChangeStart - aInfo->mChangeEnd +
+ aInfo->mReplaceLength;
+ }
+ }
+
+ // 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 == mEndParent &&
+ aInfo->mChangeStart < static_cast<uint32_t>(mEndOffset)) {
+ if (aInfo->mDetails && (aContent->GetParentNode() || newStartNode)) {
+ // 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(static_cast<uint32_t>(mEndOffset) <= aInfo->mChangeEnd + 1,
+ "mEndOffset is beyond the end of this node");
+ newEndOffset = static_cast<uint32_t>(mEndOffset) - aInfo->mChangeStart;
+ newEndNode = aInfo->mDetails->mNextSibling;
+
+ bool isCommonAncestor = IsInSelection() && mStartParent == mEndParent;
+ if (isCommonAncestor && !newStartNode) {
+ // The split occurs inside the range.
+ UnregisterCommonAncestor(mStartParent);
+ RegisterCommonAncestor(mStartParent->GetParentNode());
+ newEndNode->SetDescendantOfCommonAncestorForRangeInSelection();
+ } else if (mEndParent->IsDescendantOfCommonAncestorForRangeInSelection()) {
+ newEndNode->SetDescendantOfCommonAncestorForRangeInSelection();
+ }
+ } else {
+ mEndOffset = static_cast<uint32_t>(mEndOffset) <= aInfo->mChangeEnd ?
+ aInfo->mChangeStart :
+ mEndOffset + aInfo->mChangeStart - aInfo->mChangeEnd +
+ aInfo->mReplaceLength;
+ }
+ }
+
+ if (aInfo->mDetails &&
+ aInfo->mDetails->mType == CharacterDataChangeInfo::Details::eMerge) {
+ // normalize(), aInfo->mDetails->mNextSibling is the merged text node
+ // that will be removed
+ nsIContent* removed = aInfo->mDetails->mNextSibling;
+ if (removed == mStartParent) {
+ newStartOffset = static_cast<uint32_t>(mStartOffset) + aInfo->mChangeStart;
+ newStartNode = aContent;
+ if (MOZ_UNLIKELY(removed == mRoot)) {
+ newRoot = IsValidBoundary(newStartNode);
+ }
+ }
+ if (removed == mEndParent) {
+ newEndOffset = static_cast<uint32_t>(mEndOffset) + aInfo->mChangeStart;
+ newEndNode = aContent;
+ if (MOZ_UNLIKELY(removed == mRoot)) {
+ newRoot = IsValidBoundary(newEndNode);
+ }
+ }
+ // 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 == mStartParent && mStartOffset > 0 &&
+ uint32_t(mStartOffset) < parentNode->GetChildCount() &&
+ removed == parentNode->GetChildAt(mStartOffset)) {
+ newStartNode = aContent;
+ newStartOffset = aInfo->mChangeStart;
+ }
+ if (parentNode == mEndParent && mEndOffset > 0 &&
+ uint32_t(mEndOffset) < parentNode->GetChildCount() &&
+ removed == parentNode->GetChildAt(mEndOffset)) {
+ newEndNode = aContent;
+ newEndOffset = aInfo->mChangeEnd;
+ }
+ }
+
+ if (newStartNode || newEndNode) {
+ if (!newStartNode) {
+ newStartNode = mStartParent;
+ newStartOffset = mStartOffset;
+ }
+ if (!newEndNode) {
+ newEndNode = mEndParent;
+ newEndOffset = mEndOffset;
+ }
+ DoSetRange(newStartNode, newStartOffset, newEndNode, newEndOffset,
+ newRoot ? newRoot : mRoot.get(),
+ !newEndNode->GetParentNode() || !newStartNode->GetParentNode());
+ }
+}
+
+void
+nsRange::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned");
+
+ nsINode* container = NODE_FROM(aContainer, aDocument);
+ if (container->IsSelectionDescendant() && IsInSelection()) {
+ nsINode* child = aFirstNewContent;
+ while (child) {
+ if (!child->IsDescendantOfCommonAncestorForRangeInSelection()) {
+ MarkDescendants(child);
+ child->SetDescendantOfCommonAncestorForRangeInSelection();
+ }
+ child = child->GetNextSibling();
+ }
+ }
+
+ if (mStartOffsetWasIncremented || mEndOffsetWasIncremented) {
+ MOZ_ASSERT(mAssertNextInsertOrAppendIndex == aNewIndexInContainer);
+ MOZ_ASSERT(mAssertNextInsertOrAppendNode == aFirstNewContent);
+ MOZ_ASSERT(aFirstNewContent->IsNodeOfType(nsINode::eDATA_NODE));
+ mStartOffsetWasIncremented = mEndOffsetWasIncremented = false;
+#ifdef DEBUG
+ mAssertNextInsertOrAppendIndex = -1;
+ mAssertNextInsertOrAppendNode = nullptr;
+#endif
+ }
+}
+
+void
+nsRange::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned");
+
+ nsINode* container = NODE_FROM(aContainer, aDocument);
+
+ // Adjust position if a sibling was inserted.
+ if (container == mStartParent && aIndexInContainer < mStartOffset &&
+ !mStartOffsetWasIncremented) {
+ ++mStartOffset;
+ }
+ if (container == mEndParent && aIndexInContainer < mEndOffset &&
+ !mEndOffsetWasIncremented) {
+ ++mEndOffset;
+ }
+ if (container->IsSelectionDescendant() &&
+ !aChild->IsDescendantOfCommonAncestorForRangeInSelection()) {
+ MarkDescendants(aChild);
+ aChild->SetDescendantOfCommonAncestorForRangeInSelection();
+ }
+
+ if (mStartOffsetWasIncremented || mEndOffsetWasIncremented) {
+ MOZ_ASSERT(mAssertNextInsertOrAppendIndex == aIndexInContainer);
+ MOZ_ASSERT(mAssertNextInsertOrAppendNode == aChild);
+ MOZ_ASSERT(aChild->IsNodeOfType(nsINode::eDATA_NODE));
+ mStartOffsetWasIncremented = mEndOffsetWasIncremented = false;
+#ifdef DEBUG
+ mAssertNextInsertOrAppendIndex = -1;
+ mAssertNextInsertOrAppendNode = nullptr;
+#endif
+ }
+}
+
+void
+nsRange::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ NS_ASSERTION(mIsPositioned, "shouldn't be notified if not positioned");
+ MOZ_ASSERT(!mStartOffsetWasIncremented && !mEndOffsetWasIncremented &&
+ mAssertNextInsertOrAppendIndex == -1,
+ "splitText failed to notify insert/append?");
+
+ nsINode* container = NODE_FROM(aContainer, aDocument);
+ bool gravitateStart = false;
+ bool gravitateEnd = false;
+ bool didCheckStartParentDescendant = false;
+
+ // Adjust position if a sibling was removed...
+ if (container == mStartParent) {
+ if (aIndexInContainer < mStartOffset) {
+ --mStartOffset;
+ }
+ } else { // ...or gravitate if an ancestor was removed.
+ didCheckStartParentDescendant = true;
+ gravitateStart = nsContentUtils::ContentIsDescendantOf(mStartParent, aChild);
+ }
+
+ // Do same thing for end boundry.
+ if (container == mEndParent) {
+ if (aIndexInContainer < mEndOffset) {
+ --mEndOffset;
+ }
+ } else if (didCheckStartParentDescendant && mStartParent == mEndParent) {
+ gravitateEnd = gravitateStart;
+ } else {
+ gravitateEnd = nsContentUtils::ContentIsDescendantOf(mEndParent, aChild);
+ }
+
+ if (!mEnableGravitationOnElementRemoval) {
+ // Do not gravitate.
+ return;
+ }
+
+ if (gravitateStart || gravitateEnd) {
+ DoSetRange(gravitateStart ? container : mStartParent.get(),
+ gravitateStart ? aIndexInContainer : mStartOffset,
+ gravitateEnd ? container : mEndParent.get(),
+ gravitateEnd ? aIndexInContainer : mEndOffset,
+ mRoot);
+ }
+ if (container->IsSelectionDescendant() &&
+ aChild->IsDescendantOfCommonAncestorForRangeInSelection()) {
+ aChild->ClearDescendantOfCommonAncestorForRangeInSelection();
+ UnmarkDescendants(aChild);
+ }
+}
+
+void
+nsRange::ParentChainChanged(nsIContent *aContent)
+{
+ MOZ_ASSERT(!mStartOffsetWasIncremented && !mEndOffsetWasIncremented &&
+ mAssertNextInsertOrAppendIndex == -1,
+ "splitText failed to notify insert/append?");
+ NS_ASSERTION(mRoot == aContent, "Wrong ParentChainChanged notification?");
+ nsINode* newRoot = IsValidBoundary(mStartParent);
+ NS_ASSERTION(newRoot, "No valid boundary or root found!");
+ if (newRoot != IsValidBoundary(mEndParent)) {
+ // 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(mEndParent->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(mStartParent, mStartOffset, mEndParent, mEndOffset, newRoot);
+}
+
+/******************************************************
+ * Utilities for comparing points: API from nsIDOMRange
+ ******************************************************/
+NS_IMETHODIMP
+nsRange::IsPointInRange(nsIDOMNode* aParent, int32_t aOffset, bool* aResult)
+{
+ nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
+ if (!parent) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+
+ ErrorResult rv;
+ *aResult = IsPointInRange(*parent, aOffset, rv);
+ return rv.StealNSResult();
+}
+
+bool
+nsRange::IsPointInRange(nsINode& aParent, uint32_t aOffset, ErrorResult& aRv)
+{
+ uint16_t compareResult = ComparePoint(aParent, 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;
+}
+
+// returns -1 if point is before range, 0 if point is in range,
+// 1 if point is after range.
+NS_IMETHODIMP
+nsRange::ComparePoint(nsIDOMNode* aParent, int32_t aOffset, int16_t* aResult)
+{
+ nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
+ NS_ENSURE_TRUE(parent, NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+
+ ErrorResult rv;
+ *aResult = ComparePoint(*parent, aOffset, rv);
+ return rv.StealNSResult();
+}
+
+int16_t
+nsRange::ComparePoint(nsINode& aParent, uint32_t aOffset, ErrorResult& aRv)
+{
+ // our range is in a good state?
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return 0;
+ }
+
+ if (!nsContentUtils::ContentIsDescendantOf(&aParent, mRoot)) {
+ aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+ return 0;
+ }
+
+ if (aParent.NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return 0;
+ }
+
+ if (aOffset > aParent.Length()) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return 0;
+ }
+
+ int32_t cmp;
+ if ((cmp = nsContentUtils::ComparePoints(&aParent, aOffset,
+ mStartParent, mStartOffset)) <= 0) {
+
+ return cmp;
+ }
+ if (nsContentUtils::ComparePoints(mEndParent, mEndOffset,
+ &aParent, aOffset) == -1) {
+ return 1;
+ }
+
+ return 0;
+}
+
+NS_IMETHODIMP
+nsRange::IntersectsNode(nsIDOMNode* aNode, bool* aResult)
+{
+ *aResult = false;
+
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ // TODO: This should throw a TypeError.
+ NS_ENSURE_ARG(node);
+
+ ErrorResult rv;
+ *aResult = IntersectsNode(*node, rv);
+ return rv.StealNSResult();
+}
+
+bool
+nsRange::IntersectsNode(nsINode& aNode, ErrorResult& aRv)
+{
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return false;
+ }
+
+ // Step 3.
+ nsINode* parent = aNode.GetParentNode();
+ if (!parent) {
+ // Steps 2 and 4.
+ // |parent| is null, so |node|'s root is |node| itself.
+ return GetRoot() == &aNode;
+ }
+
+ // Step 5.
+ int32_t nodeIndex = parent->IndexOf(&aNode);
+
+ // Steps 6-7.
+ // Note: if disconnected is true, ComparePoints returns 1.
+ bool disconnected = false;
+ bool result = nsContentUtils::ComparePoints(mStartParent, mStartOffset,
+ parent, nodeIndex + 1,
+ &disconnected) < 0 &&
+ nsContentUtils::ComparePoints(parent, nodeIndex,
+ mEndParent, mEndOffset,
+ &disconnected) < 0;
+
+ // Step 2.
+ if (disconnected) {
+ result = false;
+ }
+ return result;
+}
+
+/******************************************************
+ * Private helper routines
+ ******************************************************/
+
+// 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
+void
+nsRange::DoSetRange(nsINode* aStartN, int32_t aStartOffset,
+ nsINode* aEndN, int32_t aEndOffset,
+ nsINode* aRoot, bool aNotInsertedYet)
+{
+ NS_PRECONDITION((aStartN && aEndN && aRoot) ||
+ (!aStartN && !aEndN && !aRoot),
+ "Set all or none");
+ NS_PRECONDITION(!aRoot || aNotInsertedYet ||
+ (nsContentUtils::ContentIsDescendantOf(aStartN, aRoot) &&
+ nsContentUtils::ContentIsDescendantOf(aEndN, aRoot) &&
+ aRoot == IsValidBoundary(aStartN) &&
+ aRoot == IsValidBoundary(aEndN)),
+ "Wrong root");
+ NS_PRECONDITION(!aRoot ||
+ (aStartN->IsNodeOfType(nsINode::eCONTENT) &&
+ aEndN->IsNodeOfType(nsINode::eCONTENT) &&
+ aRoot ==
+ static_cast<nsIContent*>(aStartN)->GetBindingParent() &&
+ aRoot ==
+ static_cast<nsIContent*>(aEndN)->GetBindingParent()) ||
+ (!aRoot->GetParentNode() &&
+ (aRoot->IsNodeOfType(nsINode::eDOCUMENT) ||
+ aRoot->IsNodeOfType(nsINode::eATTRIBUTE) ||
+ aRoot->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT) ||
+ /*For backward compatibility*/
+ aRoot->IsNodeOfType(nsINode::eCONTENT))),
+ "Bad root");
+
+ if (mRoot != aRoot) {
+ if (mRoot) {
+ mRoot->RemoveMutationObserver(this);
+ }
+ if (aRoot) {
+ aRoot->AddMutationObserver(this);
+ }
+ }
+ bool checkCommonAncestor = (mStartParent != aStartN || mEndParent != aEndN) &&
+ IsInSelection() && !aNotInsertedYet;
+ nsINode* oldCommonAncestor = checkCommonAncestor ? GetCommonAncestor() : nullptr;
+ mStartParent = aStartN;
+ mStartOffset = aStartOffset;
+ mEndParent = aEndN;
+ mEndOffset = aEndOffset;
+ mIsPositioned = !!mStartParent;
+ if (checkCommonAncestor) {
+ nsINode* newCommonAncestor = GetCommonAncestor();
+ if (newCommonAncestor != oldCommonAncestor) {
+ if (oldCommonAncestor) {
+ UnregisterCommonAncestor(oldCommonAncestor);
+ }
+ if (newCommonAncestor) {
+ RegisterCommonAncestor(newCommonAncestor);
+ } else {
+ NS_ASSERTION(!mIsPositioned, "unexpected disconnected nodes");
+ mSelection = nullptr;
+ }
+ }
+ }
+
+ // This needs to be the last thing this function does, other than notifying
+ // selection listeners. See comment in ParentChainChanged.
+ mRoot = aRoot;
+
+ // 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.
+ if (mSelection) {
+ mSelection->NotifySelectionListeners();
+ }
+}
+
+static int32_t
+IndexOf(nsINode* aChild)
+{
+ nsINode* parent = aChild->GetParentNode();
+
+ return parent ? parent->IndexOf(aChild) : -1;
+}
+
+void
+nsRange::SetSelection(mozilla::dom::Selection* aSelection)
+{
+ if (mSelection == aSelection) {
+ return;
+ }
+ // At least one of aSelection and mSelection must be null
+ // aSelection will be null when we are removing from a selection
+ // and a range can't be in more than one selection at a time,
+ // thus mSelection must be null too.
+ MOZ_ASSERT(!aSelection || !mSelection);
+
+ mSelection = aSelection;
+ nsINode* commonAncestor = GetCommonAncestor();
+ NS_ASSERTION(commonAncestor, "unexpected disconnected nodes");
+ if (mSelection) {
+ RegisterCommonAncestor(commonAncestor);
+ } else {
+ UnregisterCommonAncestor(commonAncestor);
+ }
+}
+
+nsINode*
+nsRange::GetCommonAncestor() const
+{
+ return mIsPositioned ?
+ nsContentUtils::GetCommonAncestor(mStartParent, mEndParent) :
+ nullptr;
+}
+
+void
+nsRange::Reset()
+{
+ DoSetRange(nullptr, 0, nullptr, 0, nullptr);
+}
+
+/******************************************************
+ * public functionality
+ ******************************************************/
+
+NS_IMETHODIMP
+nsRange::GetStartContainer(nsIDOMNode** aStartParent)
+{
+ if (!mIsPositioned)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return CallQueryInterface(mStartParent, aStartParent);
+}
+
+nsINode*
+nsRange::GetStartContainer(ErrorResult& aRv) const
+{
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+
+ return mStartParent;
+}
+
+NS_IMETHODIMP
+nsRange::GetStartOffset(int32_t* aStartOffset)
+{
+ if (!mIsPositioned)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ *aStartOffset = mStartOffset;
+
+ return NS_OK;
+}
+
+uint32_t
+nsRange::GetStartOffset(ErrorResult& aRv) const
+{
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return 0;
+ }
+
+ return mStartOffset;
+}
+
+NS_IMETHODIMP
+nsRange::GetEndContainer(nsIDOMNode** aEndParent)
+{
+ if (!mIsPositioned)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return CallQueryInterface(mEndParent, aEndParent);
+}
+
+nsINode*
+nsRange::GetEndContainer(ErrorResult& aRv) const
+{
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+
+ return mEndParent;
+}
+
+NS_IMETHODIMP
+nsRange::GetEndOffset(int32_t* aEndOffset)
+{
+ if (!mIsPositioned)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ *aEndOffset = mEndOffset;
+
+ return NS_OK;
+}
+
+uint32_t
+nsRange::GetEndOffset(ErrorResult& aRv) const
+{
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return 0;
+ }
+
+ return mEndOffset;
+}
+
+NS_IMETHODIMP
+nsRange::GetCollapsed(bool* aIsCollapsed)
+{
+ if (!mIsPositioned)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ *aIsCollapsed = Collapsed();
+
+ return NS_OK;
+}
+
+nsINode*
+nsRange::GetCommonAncestorContainer(ErrorResult& aRv) const
+{
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+
+ return nsContentUtils::GetCommonAncestor(mStartParent, mEndParent);
+}
+
+NS_IMETHODIMP
+nsRange::GetCommonAncestorContainer(nsIDOMNode** aCommonParent)
+{
+ ErrorResult rv;
+ nsINode* commonAncestor = GetCommonAncestorContainer(rv);
+ if (commonAncestor) {
+ NS_ADDREF(*aCommonParent = commonAncestor->AsDOMNode());
+ } else {
+ *aCommonParent = nullptr;
+ }
+
+ return rv.StealNSResult();
+}
+
+nsINode*
+nsRange::IsValidBoundary(nsINode* aNode)
+{
+ if (!aNode) {
+ return nullptr;
+ }
+
+ if (aNode->IsNodeOfType(nsINode::eCONTENT)) {
+ if (aNode->NodeInfo()->NameAtom() == nsGkAtoms::documentTypeNodeName) {
+ return nullptr;
+ }
+
+ nsIContent* content = static_cast<nsIContent*>(aNode);
+
+ if (!mMaySpanAnonymousSubtrees) {
+ // If the node is in a shadow tree then the ShadowRoot is the root.
+ ShadowRoot* containingShadow = content->GetContainingShadow();
+ if (containingShadow) {
+ return containingShadow;
+ }
+
+ // If the node has a binding parent, that should be the root.
+ // XXXbz maybe only for native anonymous content?
+ nsINode* root = content->GetBindingParent();
+ if (root) {
+ return root;
+ }
+ }
+ }
+
+ // Elements etc. must be in document or in document fragment,
+ // text nodes in document, in document fragment or in attribute.
+ nsINode* root = aNode->GetUncomposedDoc();
+ if (root) {
+ return root;
+ }
+
+ root = aNode->SubtreeRoot();
+
+ NS_ASSERTION(!root->IsNodeOfType(nsINode::eDOCUMENT),
+ "GetUncomposedDoc should have returned a doc");
+
+ // We allow this because of backward compatibility.
+ return root;
+}
+
+void
+nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ aRv = SetStart(&aNode, aOffset);
+}
+
+NS_IMETHODIMP
+nsRange::SetStart(nsIDOMNode* aParent, int32_t aOffset)
+{
+ nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
+ if (!parent) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+
+ ErrorResult rv;
+ SetStart(*parent, aOffset, rv);
+ return rv.StealNSResult();
+}
+
+/* virtual */ nsresult
+nsRange::SetStart(nsINode* aParent, int32_t aOffset)
+{
+ nsINode* newRoot = IsValidBoundary(aParent);
+ if (!newRoot) {
+ return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
+ }
+
+ if (aOffset < 0 || uint32_t(aOffset) > aParent->Length()) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ // Collapse if not positioned yet, if positioned in another doc or
+ // if the new start is after end.
+ if (!mIsPositioned || newRoot != mRoot ||
+ nsContentUtils::ComparePoints(aParent, aOffset,
+ mEndParent, mEndOffset) == 1) {
+ DoSetRange(aParent, aOffset, aParent, aOffset, newRoot);
+
+ return NS_OK;
+ }
+
+ DoSetRange(aParent, aOffset, mEndParent, mEndOffset, mRoot);
+
+ return NS_OK;
+}
+
+void
+nsRange::SetStartBefore(nsINode& aNode, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ aRv = SetStart(aNode.GetParentNode(), IndexOf(&aNode));
+}
+
+NS_IMETHODIMP
+nsRange::SetStartBefore(nsIDOMNode* aSibling)
+{
+ nsCOMPtr<nsINode> sibling = do_QueryInterface(aSibling);
+ if (!sibling) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+
+ ErrorResult rv;
+ SetStartBefore(*sibling, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsRange::SetStartAfter(nsINode& aNode, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ aRv = SetStart(aNode.GetParentNode(), IndexOf(&aNode) + 1);
+}
+
+NS_IMETHODIMP
+nsRange::SetStartAfter(nsIDOMNode* aSibling)
+{
+ nsCOMPtr<nsINode> sibling = do_QueryInterface(aSibling);
+ if (!sibling) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+
+ ErrorResult rv;
+ SetStartAfter(*sibling, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsRange::SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ AutoInvalidateSelection atEndOfBlock(this);
+ aRv = SetEnd(&aNode, aOffset);
+}
+
+NS_IMETHODIMP
+nsRange::SetEnd(nsIDOMNode* aParent, int32_t aOffset)
+{
+ nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
+ if (!parent) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+
+ ErrorResult rv;
+ SetEnd(*parent, aOffset, rv);
+ return rv.StealNSResult();
+}
+
+/* virtual */ nsresult
+nsRange::SetEnd(nsINode* aParent, int32_t aOffset)
+{
+ nsINode* newRoot = IsValidBoundary(aParent);
+ if (!newRoot) {
+ return NS_ERROR_DOM_INVALID_NODE_TYPE_ERR;
+ }
+
+ if (aOffset < 0 || uint32_t(aOffset) > aParent->Length()) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ // Collapse if not positioned yet, if positioned in another doc or
+ // if the new end is before start.
+ if (!mIsPositioned || newRoot != mRoot ||
+ nsContentUtils::ComparePoints(mStartParent, mStartOffset,
+ aParent, aOffset) == 1) {
+ DoSetRange(aParent, aOffset, aParent, aOffset, newRoot);
+
+ return NS_OK;
+ }
+
+ DoSetRange(mStartParent, mStartOffset, aParent, aOffset, mRoot);
+
+ return NS_OK;
+}
+
+void
+nsRange::SetEndBefore(nsINode& aNode, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ aRv = SetEnd(aNode.GetParentNode(), IndexOf(&aNode));
+}
+
+NS_IMETHODIMP
+nsRange::SetEndBefore(nsIDOMNode* aSibling)
+{
+ nsCOMPtr<nsINode> sibling = do_QueryInterface(aSibling);
+ if (!sibling) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+
+ ErrorResult rv;
+ SetEndBefore(*sibling, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsRange::SetEndAfter(nsINode& aNode, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ aRv = SetEnd(aNode.GetParentNode(), IndexOf(&aNode) + 1);
+}
+
+NS_IMETHODIMP
+nsRange::SetEndAfter(nsIDOMNode* aSibling)
+{
+ nsCOMPtr<nsINode> sibling = do_QueryInterface(aSibling);
+ if (!sibling) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+
+ ErrorResult rv;
+ SetEndAfter(*sibling, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsRange::Collapse(bool aToStart)
+{
+ if (!mIsPositioned)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ if (aToStart)
+ DoSetRange(mStartParent, mStartOffset, mStartParent, mStartOffset, mRoot);
+ else
+ DoSetRange(mEndParent, mEndOffset, mEndParent, mEndOffset, mRoot);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRange::SelectNode(nsIDOMNode* aN)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aN);
+ NS_ENSURE_TRUE(node, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+
+ ErrorResult rv;
+ SelectNode(*node, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsRange::SelectNode(nsINode& aNode, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsINode* parent = aNode.GetParentNode();
+ nsINode* newRoot = IsValidBoundary(parent);
+ if (!newRoot) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ int32_t index = parent->IndexOf(&aNode);
+ if (index < 0) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ DoSetRange(parent, index, parent, index + 1, newRoot);
+}
+
+NS_IMETHODIMP
+nsRange::SelectNodeContents(nsIDOMNode* aN)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aN);
+ NS_ENSURE_TRUE(node, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+
+ ErrorResult rv;
+ SelectNodeContents(*node, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsRange::SelectNodeContents(nsINode& aNode, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsINode* newRoot = IsValidBoundary(&aNode);
+ if (!newRoot) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+ return;
+ }
+
+ AutoInvalidateSelection atEndOfBlock(this);
+ DoSetRange(&aNode, 0, &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 nsIContentIterator
+// 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 };
+
+ nsCOMPtr<nsIContentIterator> mIter;
+ RangeSubtreeIterState mIterState;
+
+ nsCOMPtr<nsINode> mStart;
+ nsCOMPtr<nsINode> mEnd;
+
+public:
+
+ RangeSubtreeIterator()
+ : mIterState(eDone)
+ {
+ }
+ ~RangeSubtreeIterator()
+ {
+ }
+
+ 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.
+
+ ErrorResult rv;
+ nsCOMPtr<nsINode> node = aRange->GetStartContainer(rv);
+ if (!node) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDOMCharacterData> startData = do_QueryInterface(node);
+ if (startData || (node->IsElement() &&
+ node->AsElement()->GetChildCount() == aRange->GetStartOffset(rv))) {
+ 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(rv);
+ if (!node) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDOMCharacterData> endData = do_QueryInterface(node);
+ if (endData || (node->IsElement() && aRange->GetEndOffset(rv) == 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!
+
+ mIter = NS_NewContentSubtreeIterator();
+
+ nsresult res = mIter->Init(aRange);
+ if (NS_FAILED(res)) return res;
+
+ if (mIter->IsDone())
+ {
+ // The subtree iterator thinks there's nothing
+ // to iterate over, so just free it up so we
+ // don't accidentally call into it.
+
+ mIter = nullptr;
+ }
+ }
+
+ // 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 && mIter) {
+ node = mIter->GetCurrentNode();
+ }
+
+ return node.forget();
+}
+
+void
+RangeSubtreeIterator::First()
+{
+ if (mStart)
+ mIterState = eUseStart;
+ else if (mIter)
+ {
+ mIter->First();
+
+ mIterState = eUseIterator;
+ }
+ else if (mEnd)
+ mIterState = eUseEnd;
+ else
+ mIterState = eDone;
+}
+
+void
+RangeSubtreeIterator::Last()
+{
+ if (mEnd)
+ mIterState = eUseEnd;
+ else if (mIter)
+ {
+ mIter->Last();
+
+ mIterState = eUseIterator;
+ }
+ else if (mStart)
+ mIterState = eUseStart;
+ else
+ mIterState = eDone;
+}
+
+void
+RangeSubtreeIterator::Next()
+{
+ if (mIterState == eUseStart)
+ {
+ if (mIter)
+ {
+ mIter->First();
+
+ mIterState = eUseIterator;
+ }
+ else if (mEnd)
+ mIterState = eUseEnd;
+ else
+ mIterState = eDone;
+ }
+ else if (mIterState == eUseIterator)
+ {
+ mIter->Next();
+
+ if (mIter->IsDone())
+ {
+ if (mEnd)
+ mIterState = eUseEnd;
+ else
+ mIterState = eDone;
+ }
+ }
+ else
+ mIterState = eDone;
+}
+
+void
+RangeSubtreeIterator::Prev()
+{
+ if (mIterState == eUseEnd)
+ {
+ if (mIter)
+ {
+ mIter->Last();
+
+ mIterState = eUseIterator;
+ }
+ else if (mStart)
+ mIterState = eUseStart;
+ else
+ mIterState = eDone;
+ }
+ else if (mIterState == eUseIterator)
+ {
+ mIter->Prev();
+
+ if (mIter->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.
+
+ ErrorResult rv;
+ nsCOMPtr<nsINode> commonAncestor = aRange->GetCommonAncestorContainer(rv);
+ if (rv.Failed()) return rv.StealNSResult();
+
+ nsCOMPtr<nsINode> startContainer = aRange->GetStartContainer(rv);
+ if (rv.Failed()) return rv.StealNSResult();
+ nsCOMPtr<nsINode> endContainer = aRange->GetEndContainer(rv);
+ if (rv.Failed()) return rv.StealNSResult();
+
+ // 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)
+ return aRange->Collapse(true);
+ if (endContainer == commonAncestor)
+ return aRange->Collapse(false);
+
+ // 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!
+
+ aRange->SelectNode(*nodeToSelect, rv);
+ if (rv.Failed()) return rv.StealNSResult();
+
+ return aRange->Collapse(false);
+}
+
+/**
+ * Split a data node into two parts.
+ *
+ * @param aStartNode The original node we are trying to split.
+ * @param aStartIndex The index at which to split.
+ * @param aEndNode The second node.
+ * @param aCloneAfterOriginal Set false if the original node should be the
+ * latter one after split.
+ */
+static nsresult SplitDataNode(nsIDOMCharacterData* aStartNode,
+ uint32_t aStartIndex,
+ nsIDOMCharacterData** aEndNode,
+ bool aCloneAfterOriginal = true)
+{
+ nsresult rv;
+ nsCOMPtr<nsINode> node = do_QueryInterface(aStartNode);
+ NS_ENSURE_STATE(node && node->IsNodeOfType(nsINode::eDATA_NODE));
+ nsGenericDOMDataNode* dataNode = static_cast<nsGenericDOMDataNode*>(node.get());
+
+ nsCOMPtr<nsIContent> newData;
+ rv = dataNode->SplitData(aStartIndex, getter_AddRefs(newData),
+ aCloneAfterOriginal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return CallQueryInterface(newData, aEndNode);
+}
+
+NS_IMETHODIMP
+PrependChild(nsINode* aParent, nsINode* aChild)
+{
+ nsCOMPtr<nsINode> first = aParent->GetFirstChild();
+ ErrorResult rv;
+ aParent->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 res = nsRange::CompareNodeToRange(node, aRange, &before, &after);
+ NS_ENSURE_SUCCESS(res, false);
+
+ if (before || after) {
+ nsCOMPtr<nsIDOMCharacterData> charData = do_QueryInterface(node);
+ if (charData) {
+ // 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->GetStartParent()) {
+ before = false;
+ }
+ if (after && node == aRange->GetEndParent()) {
+ after = false;
+ }
+ }
+ }
+
+ return !before && !after;
+}
+
+nsresult
+nsRange::CutContents(DocumentFragment** aFragment)
+{
+ if (aFragment) {
+ *aFragment = nullptr;
+ }
+
+ nsCOMPtr<nsIDocument> doc = mStartParent->OwnerDoc();
+
+ ErrorResult res;
+ nsCOMPtr<nsINode> commonAncestor = GetCommonAncestorContainer(res);
+ NS_ENSURE_TRUE(!res.Failed(), res.StealNSResult());
+
+ // If aFragment isn't null, create a temporary fragment to hold our return.
+ RefPtr<DocumentFragment> retval;
+ if (aFragment) {
+ retval = new 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 = mStartParent;
+ int32_t startOffset = mStartOffset;
+ nsCOMPtr<nsINode> endContainer = mEndParent;
+ int32_t endOffset = mEndOffset;
+
+ 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<nsIDocument> commonAncestorDocument = do_QueryInterface(commonAncestor);
+ if (commonAncestorDocument) {
+ RefPtr<DocumentType> doctype = commonAncestorDocument->GetDoctype();
+
+ if (doctype &&
+ nsContentUtils::ComparePoints(startContainer, startOffset,
+ doctype, 0) < 0 &&
+ nsContentUtils::ComparePoints(doctype, 0,
+ endContainer, endOffset) < 0) {
+ return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
+ }
+ }
+ }
+
+ // Create and initialize a subtree iterator that will give
+ // us all the subtrees within the range.
+
+ RangeSubtreeIterator iter;
+
+ nsresult rv = iter.Init(this);
+ if (NS_FAILED(rv)) return rv;
+
+ if (iter.IsDone())
+ {
+ // There's nothing for us to delete.
+ rv = CollapseRangeAfterDelete(this);
+ if (NS_SUCCEEDED(rv) && aFragment) {
+ retval.forget(aFragment);
+ }
+ return rv;
+ }
+
+ // We delete backwards to avoid iterator problems!
+
+ iter.Last();
+
+ 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 subtree.
+
+ iter.Prev();
+
+ 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.
+
+ nsCOMPtr<nsIDOMCharacterData> charData(do_QueryInterface(node));
+
+ if (charData)
+ {
+ 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;
+ rv = charData->SubstringData(startOffset, endOffset - startOffset,
+ cutValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDOMNode> clone;
+ rv = charData->CloneNode(false, 1, getter_AddRefs(clone));
+ NS_ENSURE_SUCCESS(rv, rv);
+ clone->SetNodeValue(cutValue);
+ nodeToResult = do_QueryInterface(clone);
+ }
+
+ nsMutationGuard guard;
+ rv = charData->DeleteData(startOffset, endOffset - startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(!guard.Mutated(0) ||
+ ValidateCurrentNode(this, iter));
+ }
+
+ handled = true;
+ }
+ else
+ {
+ // Delete or extract everything after startOffset.
+
+ rv = charData->GetLength(&dataLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dataLength >= (uint32_t)startOffset)
+ {
+ nsMutationGuard guard;
+ nsCOMPtr<nsIDOMCharacterData> cutNode;
+ rv = SplitDataNode(charData, startOffset, getter_AddRefs(cutNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(!guard.Mutated(1) ||
+ ValidateCurrentNode(this, iter));
+ nodeToResult = do_QueryInterface(cutNode);
+ }
+
+ handled = true;
+ }
+ }
+ else if (node == endContainer)
+ {
+ // Delete or extract everything before endOffset.
+
+ if (endOffset >= 0)
+ {
+ nsMutationGuard guard;
+ nsCOMPtr<nsIDOMCharacterData> cutNode;
+ /* The Range spec clearly states clones get cut and original nodes
+ remain behind, so use false as the last parameter.
+ */
+ rv = SplitDataNode(charData, endOffset, getter_AddRefs(cutNode),
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(!guard.Mutated(1) ||
+ ValidateCurrentNode(this, iter));
+ nodeToResult = do_QueryInterface(cutNode);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (!handled && (node == endContainer || node == startContainer))
+ {
+ if (node && node->IsElement() &&
+ ((node == endContainer && endOffset == 0) ||
+ (node == startContainer &&
+ int32_t(node->AsElement()->GetChildCount()) == startOffset)))
+ {
+ if (retval) {
+ ErrorResult rv;
+ nodeToResult = node->CloneNode(false, rv);
+ NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
+ }
+ 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.
+ nsCOMPtr<nsINode> prevNode = iter.GetCurrentNode();
+ NS_ENSURE_STATE(prevNode);
+
+ // Get node's and prevNode's common parent. Do this before moving
+ // nodes from original DOM to result fragment.
+ commonAncestor = nsContentUtils::GetCommonAncestor(node, prevNode);
+ NS_ENSURE_STATE(commonAncestor);
+
+ nsCOMPtr<nsINode> parentCounterNode = node;
+ while (parentCounterNode && parentCounterNode != commonAncestor)
+ {
+ ++parentCount;
+ parentCounterNode = parentCounterNode->GetParentNode();
+ NS_ENSURE_STATE(parentCounterNode);
+ }
+ }
+
+ // Clone the parent hierarchy between commonAncestor and node.
+ nsCOMPtr<nsINode> closestAncestor, farthestAncestor;
+ rv = CloneParentsBetween(oldCommonAncestor, node,
+ getter_AddRefs(closestAncestor),
+ getter_AddRefs(farthestAncestor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (farthestAncestor)
+ {
+ nsCOMPtr<nsINode> n = do_QueryInterface(commonCloneAncestor);
+ rv = PrependChild(n, farthestAncestor);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsMutationGuard guard;
+ nsCOMPtr<nsINode> parent = nodeToResult->GetParentNode();
+ rv = closestAncestor ? PrependChild(closestAncestor, nodeToResult)
+ : PrependChild(commonCloneAncestor, nodeToResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(!guard.Mutated(parent ? 2 : 1) ||
+ ValidateCurrentNode(this, iter));
+ } else if (nodeToResult) {
+ nsMutationGuard guard;
+ nsCOMPtr<nsINode> node = nodeToResult;
+ nsCOMPtr<nsINode> parent = node->GetParentNode();
+ if (parent) {
+ mozilla::ErrorResult error;
+ parent->RemoveChild(*node, error);
+ NS_ENSURE_FALSE(error.Failed(), error.StealNSResult());
+ }
+ NS_ENSURE_STATE(!guard.Mutated(1) ||
+ ValidateCurrentNode(this, iter));
+ }
+
+ 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();
+ NS_ENSURE_STATE(newCloneAncestor);
+ }
+ commonCloneAncestor = newCloneAncestor;
+ }
+ }
+
+ rv = CollapseRangeAfterDelete(this);
+ if (NS_SUCCEEDED(rv) && aFragment) {
+ retval.forget(aFragment);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsRange::DeleteContents()
+{
+ return CutContents(nullptr);
+}
+
+void
+nsRange::DeleteContents(ErrorResult& aRv)
+{
+ aRv = CutContents(nullptr);
+}
+
+NS_IMETHODIMP
+nsRange::ExtractContents(nsIDOMDocumentFragment** aReturn)
+{
+ NS_ENSURE_ARG_POINTER(aReturn);
+ RefPtr<DocumentFragment> fragment;
+ nsresult rv = CutContents(getter_AddRefs(fragment));
+ fragment.forget(aReturn);
+ return rv;
+}
+
+already_AddRefed<DocumentFragment>
+nsRange::ExtractContents(ErrorResult& rv)
+{
+ RefPtr<DocumentFragment> fragment;
+ rv = CutContents(getter_AddRefs(fragment));
+ return fragment.forget();
+}
+
+NS_IMETHODIMP
+nsRange::CompareBoundaryPoints(uint16_t aHow, nsIDOMRange* aOtherRange,
+ int16_t* aCmpRet)
+{
+ nsRange* otherRange = static_cast<nsRange*>(aOtherRange);
+ NS_ENSURE_TRUE(otherRange, NS_ERROR_NULL_POINTER);
+
+ ErrorResult rv;
+ *aCmpRet = CompareBoundaryPoints(aHow, *otherRange, rv);
+ return rv.StealNSResult();
+}
+
+int16_t
+nsRange::CompareBoundaryPoints(uint16_t aHow, nsRange& aOtherRange,
+ ErrorResult& rv)
+{
+ if (!mIsPositioned || !aOtherRange.IsPositioned()) {
+ rv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return 0;
+ }
+
+ nsINode *ourNode, *otherNode;
+ int32_t ourOffset, otherOffset;
+
+ switch (aHow) {
+ case nsIDOMRange::START_TO_START:
+ ourNode = mStartParent;
+ ourOffset = mStartOffset;
+ otherNode = aOtherRange.GetStartParent();
+ otherOffset = aOtherRange.StartOffset();
+ break;
+ case nsIDOMRange::START_TO_END:
+ ourNode = mEndParent;
+ ourOffset = mEndOffset;
+ otherNode = aOtherRange.GetStartParent();
+ otherOffset = aOtherRange.StartOffset();
+ break;
+ case nsIDOMRange::END_TO_START:
+ ourNode = mStartParent;
+ ourOffset = mStartOffset;
+ otherNode = aOtherRange.GetEndParent();
+ otherOffset = aOtherRange.EndOffset();
+ break;
+ case nsIDOMRange::END_TO_END:
+ ourNode = mEndParent;
+ ourOffset = mEndOffset;
+ otherNode = aOtherRange.GetEndParent();
+ otherOffset = aOtherRange.EndOffset();
+ break;
+ default:
+ // We were passed an illegal value
+ rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return 0;
+ }
+
+ if (mRoot != aOtherRange.GetRoot()) {
+ rv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+ return 0;
+ }
+
+ return nsContentUtils::ComparePoints(ourNode, ourOffset,
+ otherNode, otherOffset);
+}
+
+/* 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;
+
+ nsCOMPtr<nsINode> firstParent, lastParent;
+ nsCOMPtr<nsINode> parent = aNode->GetParentNode();
+
+ while(parent && parent != aAncestor)
+ {
+ ErrorResult rv;
+ nsCOMPtr<nsINode> clone = parent->CloneNode(false, rv);
+
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ if (!clone) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (! firstParent) {
+ firstParent = lastParent = clone;
+ } else {
+ clone->AppendChild(*lastParent, rv);
+ if (rv.Failed()) return rv.StealNSResult();
+
+ lastParent = clone;
+ }
+
+ parent = parent->GetParentNode();
+ }
+
+ *aClosestAncestor = firstParent;
+ NS_IF_ADDREF(*aClosestAncestor);
+
+ *aFarthestAncestor = lastParent;
+ NS_IF_ADDREF(*aFarthestAncestor);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRange::CloneContents(nsIDOMDocumentFragment** aReturn)
+{
+ ErrorResult rv;
+ *aReturn = CloneContents(rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<DocumentFragment>
+nsRange::CloneContents(ErrorResult& aRv)
+{
+ nsCOMPtr<nsINode> commonAncestor = GetCommonAncestorContainer(aRv);
+ MOZ_ASSERT(!aRv.Failed(), "GetCommonAncestorContainer() shouldn't fail!");
+
+ nsCOMPtr<nsIDocument> doc = mStartParent->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 DocumentFragment(doc->NodeInfoManager());
+
+ 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 == mEndParent && mEndOffset == 0) &&
+ !(node == mStartParent &&
+ mStartOffset ==
+ int32_t(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.
+
+ nsCOMPtr<nsIDOMCharacterData> charData(do_QueryInterface(clone));
+
+ if (charData)
+ {
+ if (node == mEndParent)
+ {
+ // We only need the data before mEndOffset, so get rid of any
+ // data after it.
+
+ uint32_t dataLength = 0;
+ aRv = charData->GetLength(&dataLength);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (dataLength > (uint32_t)mEndOffset)
+ {
+ aRv = charData->DeleteData(mEndOffset, dataLength - mEndOffset);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (node == mStartParent)
+ {
+ // We don't need any data before mStartOffset, so just
+ // delete it!
+
+ if (mStartOffset > 0)
+ {
+ aRv = charData->DeleteData(0, mStartOffset);
+ 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 = do_QueryInterface(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::GetCommonAncestor(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 = new nsRange(mOwner);
+
+ range->SetMaySpanAnonymousSubtrees(mMaySpanAnonymousSubtrees);
+
+ range->DoSetRange(mStartParent, mStartOffset, mEndParent, mEndOffset, mRoot);
+
+ return range.forget();
+}
+
+NS_IMETHODIMP
+nsRange::CloneRange(nsIDOMRange** aReturn)
+{
+ *aReturn = CloneRange().take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRange::InsertNode(nsIDOMNode* aNode)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ if (!node) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+
+ ErrorResult rv;
+ InsertNode(*node, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsRange::InsertNode(nsINode& aNode, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ int32_t tStartOffset = StartOffset();
+
+ nsCOMPtr<nsINode> tStartContainer = GetStartContainer(aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (&aNode == tStartContainer) {
+ aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return;
+ }
+
+ // This is the node we'll be inserting before, and its parent
+ nsCOMPtr<nsINode> referenceNode;
+ nsCOMPtr<nsINode> referenceParentNode = tStartContainer;
+
+ nsCOMPtr<nsIDOMText> startTextNode(do_QueryInterface(tStartContainer));
+ nsCOMPtr<nsIDOMNodeList> tChildList;
+ if (startTextNode) {
+ referenceParentNode = tStartContainer->GetParentNode();
+ if (!referenceParentNode) {
+ aRv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
+ return;
+ }
+
+ referenceParentNode->EnsurePreInsertionValidity(aNode, tStartContainer,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMText> secondPart;
+ aRv = startTextNode->SplitText(tStartOffset, getter_AddRefs(secondPart));
+ if (aRv.Failed()) {
+ return;
+ }
+
+ referenceNode = do_QueryInterface(secondPart);
+ } else {
+ aRv = tStartContainer->AsDOMNode()->GetChildNodes(getter_AddRefs(tChildList));
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // find the insertion point in the DOM and insert the Node
+ nsCOMPtr<nsIDOMNode> q;
+ aRv = tChildList->Item(tStartOffset, getter_AddRefs(q));
+ referenceNode = do_QueryInterface(q);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ 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).
+ int32_t newOffset;
+
+ if (referenceNode) {
+ newOffset = IndexOf(referenceNode);
+ } else {
+ uint32_t length;
+ aRv = tChildList->GetLength(&length);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ newOffset = length;
+ }
+
+ if (aNode.NodeType() == nsIDOMNode::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);
+ }
+}
+
+NS_IMETHODIMP
+nsRange::SurroundContents(nsIDOMNode* aNewParent)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNewParent);
+ if (!node) {
+ return NS_ERROR_DOM_NOT_OBJECT_ERR;
+ }
+ ErrorResult rv;
+ SurroundContents(*node, rv);
+ return rv.StealNSResult();
+}
+
+void
+nsRange::SurroundContents(nsINode& aNewParent, ErrorResult& aRv)
+{
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&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 (mStartParent != mEndParent) {
+ bool startIsText = mStartParent->IsNodeOfType(nsINode::eTEXT);
+ bool endIsText = mEndParent->IsNodeOfType(nsINode::eTEXT);
+ nsINode* startGrandParent = mStartParent->GetParentNode();
+ nsINode* endGrandParent = mEndParent->GetParentNode();
+ if (!((startIsText && endIsText &&
+ startGrandParent &&
+ startGrandParent == endGrandParent) ||
+ (startIsText &&
+ startGrandParent &&
+ startGrandParent == mEndParent) ||
+ (endIsText &&
+ endGrandParent &&
+ endGrandParent == mStartParent))) {
+ 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 == nsIDOMNode::DOCUMENT_NODE ||
+ nodeType == nsIDOMNode::DOCUMENT_TYPE_NODE ||
+ nodeType == nsIDOMNode::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);
+}
+
+NS_IMETHODIMP
+nsRange::ToString(nsAString& aReturn)
+{
+ // clear the string
+ aReturn.Truncate();
+
+ // If we're unpositioned, return the empty string
+ if (!mIsPositioned) {
+ return NS_OK;
+ }
+
+#ifdef DEBUG_range
+ printf("Range dump: -----------------------\n");
+#endif /* DEBUG */
+
+ // effeciency hack for simple case
+ if (mStartParent == mEndParent)
+ {
+ nsCOMPtr<nsIDOMText> textNode( do_QueryInterface(mStartParent) );
+
+ if (textNode)
+ {
+#ifdef DEBUG_range
+ // If debug, dump it:
+ nsCOMPtr<nsIContent> cN (do_QueryInterface(mStartParent));
+ if (cN) cN->List(stdout);
+ printf("End Range dump: -----------------------\n");
+#endif /* DEBUG */
+
+ // grab the text
+ if (NS_FAILED(textNode->SubstringData(mStartOffset,mEndOffset-mStartOffset,aReturn)))
+ return NS_ERROR_UNEXPECTED;
+ return NS_OK;
+ }
+ }
+
+ /* complex case: mStartParent != mEndParent, or mStartParent not a text node
+ revisit - there are potential optimizations here and also tradeoffs.
+ */
+
+ nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
+ nsresult rv = iter->Init(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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
+ while (!iter->IsDone())
+ {
+ nsINode *n = iter->GetCurrentNode();
+
+#ifdef DEBUG_range
+ // If debug, dump it:
+ n->List(stdout);
+#endif /* DEBUG */
+ nsCOMPtr<nsIDOMText> textNode(do_QueryInterface(n));
+ if (textNode) // if it's a text node, get the text
+ {
+ if (n == mStartParent) // only include text past start offset
+ {
+ uint32_t strLength;
+ textNode->GetLength(&strLength);
+ textNode->SubstringData(mStartOffset,strLength-mStartOffset,tempString);
+ aReturn += tempString;
+ }
+ else if (n == mEndParent) // only include text before end offset
+ {
+ textNode->SubstringData(0,mEndOffset,tempString);
+ aReturn += tempString;
+ }
+ else // grab the whole kit-n-kaboodle
+ {
+ textNode->GetData(tempString);
+ aReturn += tempString;
+ }
+ }
+
+ iter->Next();
+ }
+
+#ifdef DEBUG_range
+ printf("End Range dump: -----------------------\n");
+#endif /* DEBUG */
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsRange::Detach()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRange::CreateContextualFragment(const nsAString& aFragment,
+ nsIDOMDocumentFragment** aReturn)
+{
+ if (mIsPositioned) {
+ return nsContentUtils::CreateContextualFragment(mStartParent, aFragment,
+ false, aReturn);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<DocumentFragment>
+nsRange::CreateContextualFragment(const nsAString& aFragment, ErrorResult& aRv)
+{
+ if (!mIsPositioned) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return nsContentUtils::CreateContextualFragment(mStartParent, aFragment,
+ false, aRv);
+}
+
+static void ExtractRectFromOffset(nsIFrame* aFrame,
+ const int32_t aOffset, nsRect* aR, bool aKeepLeft,
+ bool aClampToEdge)
+{
+ nsPoint point;
+ aFrame->GetPointFromOffset(aOffset, &point);
+
+ if (!aClampToEdge && !aR->Contains(point)) {
+ aR->width = 0;
+ aR->x = point.x;
+ return;
+ }
+
+ if (aClampToEdge) {
+ point = aR->ClampPoint(point);
+ }
+
+ if (aKeepLeft) {
+ aR->width = point.x - aR->x;
+ } else {
+ aR->width = aR->XMost() - point.x;
+ aR->x = point.x;
+ }
+}
+
+static nsTextFrame*
+GetTextFrameForContent(nsIContent* aContent, bool aFlushLayout)
+{
+ nsIPresShell* presShell = aContent->OwnerDoc()->GetShell();
+ if (presShell) {
+ presShell->FrameConstructor()->EnsureFrameForTextNode(
+ static_cast<nsGenericDOMDataNode*>(aContent));
+
+ if (aFlushLayout) {
+ aContent->OwnerDoc()->FlushPendingNotifications(Flush_Layout);
+ }
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame && frame->GetType() == nsGkAtoms::textFrame) {
+ return static_cast<nsTextFrame*>(frame);
+ }
+ }
+ return nullptr;
+}
+
+static nsresult GetPartialTextRect(nsLayoutUtils::RectCallback* aCallback,
+ mozilla::dom::DOMStringList* aTextList,
+ nsIContent* aContent, int32_t aStartOffset,
+ int32_t aEndOffset, bool aClampToEdge,
+ bool aFlushLayout)
+{
+ nsTextFrame* textFrame = GetTextFrameForContent(aContent, aFlushLayout);
+ if (textFrame) {
+ // If we'll need it later, collect the full content text now.
+ nsAutoString textContent;
+ if (aTextList) {
+ mozilla::ErrorResult err; // ignored
+ aContent->GetTextContent(textContent, err);
+ }
+
+ nsIFrame* relativeTo = nsLayoutUtils::GetContainingBlockForClientRect(textFrame);
+ for (nsTextFrame* f = textFrame; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
+ int32_t fstart = f->GetContentOffset(), fend = f->GetContentEnd();
+ if (fend <= aStartOffset || fstart >= aEndOffset)
+ continue;
+
+ // 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 rtl = f->GetTextRun(nsTextFrame::eInflated)->IsRightToLeft();
+ nsRect r = f->GetRectRelativeToSelf();
+ if (fstart < aStartOffset) {
+ // aStartOffset is within this frame
+ ExtractRectFromOffset(f, aStartOffset, &r, rtl, aClampToEdge);
+ textContentStart = aStartOffset;
+ }
+ if (fend > aEndOffset) {
+ // aEndOffset is in the middle of this frame
+ ExtractRectFromOffset(f, aEndOffset, &r, !rtl, aClampToEdge);
+ textContentEnd = aEndOffset;
+ }
+ r = nsLayoutUtils::TransformFrameRectToAncestor(f, r, relativeTo);
+ aCallback->AddRect(r);
+
+ // Finally capture the text, if requested.
+ if (aTextList) {
+ const nsAString& textSubstring =
+ Substring(textContent,
+ textContentStart,
+ (textContentEnd - textContentStart));
+ aTextList->Add(textSubstring);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+/* static */ void
+nsRange::CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
+ mozilla::dom::DOMStringList* aTextList,
+ nsRange* aRange,
+ nsINode* aStartParent, int32_t aStartOffset,
+ nsINode* aEndParent, int32_t aEndOffset,
+ bool aClampToEdge, bool aFlushLayout)
+{
+ // Hold strong pointers across the flush
+ nsCOMPtr<nsINode> startContainer = aStartParent;
+ nsCOMPtr<nsINode> endContainer = aEndParent;
+
+ // Flush out layout so our frames are up to date.
+ if (!aStartParent->IsInUncomposedDoc()) {
+ return;
+ }
+
+ if (aFlushLayout) {
+ aStartParent->OwnerDoc()->FlushPendingNotifications(Flush_Layout);
+ // Recheck whether we're still in the document
+ if (!aStartParent->IsInUncomposedDoc()) {
+ 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
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aStartParent);
+ if (content && content->IsNodeOfType(nsINode::eTEXT)) {
+ nsTextFrame* textFrame = GetTextFrameForContent(content, aFlushLayout);
+ if (textFrame) {
+ int32_t outOffset;
+ nsIFrame* outFrame;
+ textFrame->GetChildFrameContainingOffset(aStartOffset, false,
+ &outOffset, &outFrame);
+ if (outFrame) {
+ nsIFrame* relativeTo =
+ nsLayoutUtils::GetContainingBlockForClientRect(outFrame);
+ nsRect r = outFrame->GetRectRelativeToSelf();
+ ExtractRectFromOffset(outFrame, aStartOffset, &r, false, aClampToEdge);
+ r.width = 0;
+ r = nsLayoutUtils::TransformFrameRectToAncestor(outFrame, r, relativeTo);
+ aCollector->AddRect(r);
+ }
+ }
+ }
+ return;
+ }
+
+ do {
+ nsCOMPtr<nsINode> node = iter.GetCurrentNode();
+ iter.Next();
+ nsCOMPtr<nsIContent> content = do_QueryInterface(node);
+ if (!content)
+ continue;
+ if (content->IsNodeOfType(nsINode::eTEXT)) {
+ if (node == startContainer) {
+ int32_t offset = startContainer == endContainer ?
+ aEndOffset : content->GetText()->GetLength();
+ GetPartialTextRect(aCollector, aTextList, content, aStartOffset, offset,
+ aClampToEdge, aFlushLayout);
+ continue;
+ } else if (node == endContainer) {
+ GetPartialTextRect(aCollector, aTextList, content, 0, aEndOffset,
+ aClampToEdge, aFlushLayout);
+ continue;
+ }
+ }
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (frame) {
+ nsLayoutUtils::GetAllInFlowRectsAndTexts(frame,
+ nsLayoutUtils::GetContainingBlockForClientRect(frame), aCollector,
+ aTextList,
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ }
+ } while (!iter.IsDone());
+}
+
+NS_IMETHODIMP
+nsRange::GetBoundingClientRect(nsIDOMClientRect** aResult)
+{
+ *aResult = GetBoundingClientRect(true).take();
+ return NS_OK;
+}
+
+already_AddRefed<DOMRect>
+nsRange::GetBoundingClientRect(bool aClampToEdge, bool aFlushLayout)
+{
+ RefPtr<DOMRect> rect = new DOMRect(ToSupports(this));
+ if (!mStartParent) {
+ return rect.forget();
+ }
+
+ nsLayoutUtils::RectAccumulator accumulator;
+ CollectClientRectsAndText(&accumulator, nullptr, this, mStartParent,
+ mStartOffset, mEndParent, mEndOffset, aClampToEdge, aFlushLayout);
+
+ nsRect r = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect :
+ accumulator.mResultRect;
+ rect->SetLayoutRect(r);
+ return rect.forget();
+}
+
+NS_IMETHODIMP
+nsRange::GetClientRects(nsIDOMClientRectList** aResult)
+{
+ *aResult = GetClientRects(true).take();
+ return NS_OK;
+}
+
+already_AddRefed<DOMRectList>
+nsRange::GetClientRects(bool aClampToEdge, bool aFlushLayout)
+{
+ if (!mStartParent) {
+ return nullptr;
+ }
+
+ RefPtr<DOMRectList> rectList =
+ new DOMRectList(static_cast<nsIDOMRange*>(this));
+
+ nsLayoutUtils::RectListBuilder builder(rectList);
+
+ CollectClientRectsAndText(&builder, nullptr, this, mStartParent,
+ mStartOffset, mEndParent, mEndOffset, aClampToEdge, aFlushLayout);
+ return rectList.forget();
+}
+
+void
+nsRange::GetClientRectsAndTexts(
+ mozilla::dom::ClientRectsAndTexts& aResult,
+ ErrorResult& aErr)
+{
+ if (!mStartParent) {
+ return;
+ }
+
+ aResult.mRectList = new DOMRectList(static_cast<nsIDOMRange*>(this));
+ aResult.mTextList = new DOMStringList();
+
+ nsLayoutUtils::RectListBuilder builder(aResult.mRectList);
+
+ CollectClientRectsAndText(&builder, aResult.mTextList, this,
+ mStartParent, mStartOffset, mEndParent, mEndOffset, true, true);
+}
+
+NS_IMETHODIMP
+nsRange::GetUsedFontFaces(nsIDOMFontFaceList** aResult)
+{
+ *aResult = nullptr;
+
+ NS_ENSURE_TRUE(mStartParent, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsINode> startContainer = do_QueryInterface(mStartParent);
+ nsCOMPtr<nsINode> endContainer = do_QueryInterface(mEndParent);
+
+ // Flush out layout so our frames are up to date.
+ nsIDocument* doc = mStartParent->OwnerDoc();
+ NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
+ doc->FlushPendingNotifications(Flush_Frames);
+
+ // Recheck whether we're still in the document
+ NS_ENSURE_TRUE(mStartParent->IsInUncomposedDoc(), NS_ERROR_UNEXPECTED);
+
+ RefPtr<nsFontFaceList> fontFaceList = new nsFontFaceList();
+
+ 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->IsNodeOfType(nsINode::eTEXT)) {
+ if (node == startContainer) {
+ int32_t offset = startContainer == endContainer ?
+ mEndOffset : content->GetText()->GetLength();
+ nsLayoutUtils::GetFontFacesForText(frame, mStartOffset, offset,
+ true, fontFaceList);
+ continue;
+ }
+ if (node == endContainer) {
+ nsLayoutUtils::GetFontFacesForText(frame, 0, mEndOffset,
+ true, fontFaceList);
+ continue;
+ }
+ }
+
+ nsLayoutUtils::GetFontFacesForFrames(frame, fontFaceList);
+ }
+
+ fontFaceList.forget(aResult);
+ return NS_OK;
+}
+
+nsINode*
+nsRange::GetRegisteredCommonAncestor()
+{
+ NS_ASSERTION(IsInSelection(),
+ "GetRegisteredCommonAncestor only valid for range in selection");
+ nsINode* ancestor = GetNextRangeCommonAncestor(mStartParent);
+ while (ancestor) {
+ RangeHashTable* ranges =
+ static_cast<RangeHashTable*>(ancestor->GetProperty(nsGkAtoms::range));
+ if (ranges->GetEntry(this)) {
+ break;
+ }
+ ancestor = GetNextRangeCommonAncestor(ancestor->GetParentNode());
+ }
+ NS_ASSERTION(ancestor, "can't find common ancestor for selected range");
+ return ancestor;
+}
+
+/* static */ bool nsRange::AutoInvalidateSelection::mIsNested;
+
+nsRange::AutoInvalidateSelection::~AutoInvalidateSelection()
+{
+ NS_ASSERTION(mWasInSelection == mRange->IsInSelection(),
+ "Range got unselected in AutoInvalidateSelection block");
+ if (!mCommonAncestor) {
+ return;
+ }
+ mIsNested = false;
+ ::InvalidateAllFrames(mCommonAncestor);
+ nsINode* commonAncestor = mRange->GetRegisteredCommonAncestor();
+ if (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->IsNodeOfType(nsINode::eTEXT) &&
+ aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE);
+}
+
+void
+nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges)
+{
+ MOZ_ASSERT(mIsPositioned);
+ MOZ_ASSERT(mEndParent);
+ MOZ_ASSERT(mStartParent);
+
+ nsRange* range = this;
+ RefPtr<nsRange> newRange;
+ while (range) {
+ nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
+ nsresult rv = iter->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) {
+ ErrorResult err;
+ nsINode* node = iter->GetCurrentNode();
+ iter->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) {
+ frame->IsSelectable(&selectable, nullptr);
+ }
+ }
+ }
+
+ if (!selectable) {
+ if (!firstNonSelectableContent) {
+ firstNonSelectableContent = content;
+ }
+ if (iter->IsDone() && seenSelectable) {
+ // The tail end of the initial range is non-selectable - truncate the
+ // current range before the first non-selectable node.
+ range->SetEndBefore(*firstNonSelectableContent, err);
+ }
+ } else 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.
+ range->SetStartBefore(*node, err);
+ if (err.Failed()) {
+ return;
+ }
+ break; // restart the same range with a new iterator
+ } else {
+ // Save the end point before truncating the range.
+ nsINode* endParent = range->mEndParent;
+ int32_t endOffset = range->mEndOffset;
+
+ // Truncate the current range before the first non-selectable node.
+ 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* startParent = node;
+ int32_t startOffset = 0;
+ // Don't start *inside* a node with independent selection though
+ // (e.g. <input>).
+ if (content && content->HasIndependentSelection()) {
+ nsINode* parent = startParent->GetParent();
+ if (parent) {
+ startOffset = parent->IndexOf(startParent);
+ startParent = parent;
+ }
+ }
+ rv = CreateRange(startParent, startOffset, endParent, endOffset,
+ getter_AddRefs(newRange));
+ if (NS_FAILED(rv) || newRange->Collapsed()) {
+ newRange = nullptr;
+ }
+ range = newRange;
+ break; // create a new iterator for the new range, if any
+ }
+ } else {
+ seenSelectable = true;
+ if (!added) {
+ added = true;
+ aOutRanges->AppendElement(range);
+ }
+ }
+ if (iter->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()) {
+ return false;
+ }
+ for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
+ if (f->IsFrameOfType(nsIFrame::eReplaced) &&
+ !f->GetContent()->IsHTMLElement(nsGkAtoms::button) &&
+ !f->GetContent()->IsHTMLElement(nsGkAtoms::select)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+ElementIsVisibleNoFlush(Element* aElement)
+{
+ if (!aElement) {
+ return false;
+ }
+ RefPtr<nsStyleContext> sc =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, nullptr,
+ nullptr);
+ return sc && sc->StyleVisibility()->IsVisible();
+}
+
+static void
+AppendTransformedText(InnerTextAccumulator& aResult,
+ nsGenericDOMDataNode* aTextNode,
+ int32_t aStart, int32_t aEnd)
+{
+ nsIFrame* frame = aTextNode->GetPrimaryFrame();
+ if (!IsVisibleAndNotInReplacedElement(frame)) {
+ return;
+ }
+ nsIFrame::RenderedText text = frame->GetRenderedText(aStart, aEnd);
+ 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)
+{
+ nsIAtom* type = aFrame->GetType();
+ if (type != nsGkAtoms::tableCellFrame &&
+ type != nsGkAtoms::bcTableCellFrame) {
+ return true;
+ }
+ for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) {
+ if (c->GetNextSibling()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+IsLastRowOfRowGroup(nsIFrame* aFrame)
+{
+ if (aFrame->GetType() != nsGkAtoms::tableRowFrame) {
+ return true;
+ }
+ for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) {
+ if (c->GetNextSibling()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+IsLastNonemptyRowGroupOfTable(nsIFrame* aFrame)
+{
+ if (aFrame->GetType() != nsGkAtoms::tableRowGroupFrame) {
+ 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* aStartParent, uint32_t aStartOffset,
+ nsIContent* aEndParent, uint32_t aEndOffset)
+{
+ InnerTextAccumulator result(aValue);
+ nsIContent* currentNode = aStartParent;
+ TreeTraversalState currentState = AFTER_NODE;
+ if (aStartParent->IsNodeOfType(nsINode::eTEXT)) {
+ auto t = static_cast<nsGenericDOMDataNode*>(aStartParent);
+ if (aStartParent == aEndParent) {
+ AppendTransformedText(result, t, aStartOffset, aEndOffset);
+ return;
+ }
+ AppendTransformedText(result, t, aStartOffset, t->TextLength());
+ } else {
+ if (uint32_t(aStartOffset) < aStartParent->GetChildCount()) {
+ currentNode = aStartParent->GetChildAt(aStartOffset);
+ currentState = AT_NODE;
+ }
+ }
+
+ nsIContent* endNode = aEndParent;
+ TreeTraversalState endState = AFTER_NODE;
+ if (aEndParent->IsNodeOfType(nsINode::eTEXT)) {
+ endState = AT_NODE;
+ } else {
+ if (uint32_t(aEndOffset) < aEndParent->GetChildCount()) {
+ endNode = aEndParent->GetChildAt(aEndOffset);
+ endState = AT_NODE;
+ }
+ }
+
+ while (currentNode != endNode || currentState != endState) {
+ nsIFrame* f = currentNode->GetPrimaryFrame();
+ bool isVisibleAndNotReplaced = IsVisibleAndNotInReplacedElement(f);
+ if (currentState == AT_NODE) {
+ bool isText = currentNode->IsNodeOfType(nsINode::eTEXT);
+ if (isText && currentNode->GetParent()->IsHTMLElement(nsGkAtoms::rp) &&
+ ElementIsVisibleNoFlush(currentNode->GetParent()->AsElement())) {
+ nsAutoString str;
+ currentNode->GetTextContent(str, aError);
+ result.Append(str);
+ } else 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();
+ }
+ }
+
+ if (aEndParent->IsNodeOfType(nsINode::eTEXT)) {
+ nsGenericDOMDataNode* t = static_cast<nsGenericDOMDataNode*>(aEndParent);
+ AppendTransformedText(result, t, 0, aEndOffset);
+ }
+ // 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 000000000..4b35c749a
--- /dev/null
+++ b/dom/base/nsRange.h
@@ -0,0 +1,380 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMRange object.
+ */
+
+#ifndef nsRange_h___
+#define nsRange_h___
+
+#include "nsIDOMRange.h"
+#include "nsCOMPtr.h"
+#include "nsINode.h"
+#include "nsIDocument.h"
+#include "nsIDOMNode.h"
+#include "nsLayoutUtils.h"
+#include "prmon.h"
+#include "nsStubMutationObserver.h"
+#include "nsWrapperCache.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+struct ClientRectsAndTexts;
+class DocumentFragment;
+class DOMRect;
+class DOMRectList;
+class Selection;
+} // namespace dom
+} // namespace mozilla
+
+class nsRange final : public nsIDOMRange,
+ public nsStubMutationObserver,
+ public nsWrapperCache
+{
+ typedef mozilla::ErrorResult ErrorResult;
+ typedef mozilla::dom::DOMRect DOMRect;
+ typedef mozilla::dom::DOMRectList DOMRectList;
+
+ virtual ~nsRange();
+
+public:
+ explicit nsRange(nsINode* aNode);
+
+ static nsresult CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset,
+ nsIDOMNode* aEndParent, int32_t aEndOffset,
+ nsRange** aRange);
+ static nsresult CreateRange(nsIDOMNode* aStartParent, int32_t aStartOffset,
+ nsIDOMNode* aEndParent, int32_t aEndOffset,
+ nsIDOMRange** aRange);
+ static nsresult CreateRange(nsINode* aStartParent, int32_t aStartOffset,
+ nsINode* aEndParent, int32_t aEndOffset,
+ nsRange** aRange);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsRange, nsIDOMRange)
+
+ /**
+ * The DOM Range spec requires that when a node is removed from its parent,
+ * and the node's subtree contains the start or end point of a range, that
+ * start or end point is moved up to where the node was removed from its
+ * parent.
+ * For some internal uses of Ranges it's useful to disable that behavior,
+ * so that a range of children within a single parent is preserved even if
+ * that parent is removed from the document tree.
+ */
+ void SetEnableGravitationOnElementRemoval(bool aEnable)
+ {
+ mEnableGravitationOnElementRemoval = aEnable;
+ }
+
+ // nsIDOMRange interface
+ NS_DECL_NSIDOMRANGE
+
+ nsINode* GetRoot() const
+ {
+ return mRoot;
+ }
+
+ nsINode* GetStartParent() const
+ {
+ return mStartParent;
+ }
+
+ nsINode* GetEndParent() const
+ {
+ return mEndParent;
+ }
+
+ int32_t StartOffset() const
+ {
+ return mStartOffset;
+ }
+
+ int32_t EndOffset() const
+ {
+ return mEndOffset;
+ }
+
+ bool IsPositioned() const
+ {
+ return mIsPositioned;
+ }
+
+ void SetMaySpanAnonymousSubtrees(bool aMaySpanAnonymousSubtrees)
+ {
+ mMaySpanAnonymousSubtrees = aMaySpanAnonymousSubtrees;
+ }
+
+ /**
+ * Return true iff this range is part of a Selection object
+ * and isn't detached.
+ */
+ bool IsInSelection() const
+ {
+ return !!mSelection;
+ }
+
+ /**
+ * Called when the range is added/removed from a Selection.
+ */
+ void SetSelection(mozilla::dom::Selection* aSelection);
+
+ /**
+ * 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::AddItem
+ * @see ExcludeNonSelectableNodes
+ */
+ void SetIsGenerated(bool aIsGenerated)
+ {
+ mIsGenerated = aIsGenerated;
+ }
+
+ nsINode* GetCommonAncestor() const;
+ void Reset();
+ nsresult SetStart(nsINode* aParent, int32_t aOffset);
+ nsresult SetEnd(nsINode* aParent, int32_t aOffset);
+ already_AddRefed<nsRange> CloneRange() const;
+
+ nsresult Set(nsINode* aStartParent, int32_t aStartOffset,
+ nsINode* aEndParent, int32_t aEndOffset)
+ {
+ // If this starts being hot, we may be able to optimize this a bit,
+ // but for now just set start and end separately.
+ nsresult rv = SetStart(aStartParent, aStartOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SetEnd(aEndParent, aEndOffset);
+ }
+
+ NS_IMETHOD GetUsedFontFaces(nsIDOMFontFaceList** aResult);
+
+ // 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);
+
+ bool Collapsed() const
+ {
+ return mIsPositioned && mStartParent == mEndParent &&
+ mStartOffset == mEndOffset;
+ }
+ already_AddRefed<mozilla::dom::DocumentFragment>
+ CreateContextualFragment(const nsAString& aString, ErrorResult& aError);
+ already_AddRefed<mozilla::dom::DocumentFragment>
+ CloneContents(ErrorResult& aErr);
+ int16_t CompareBoundaryPoints(uint16_t aHow, nsRange& aOther,
+ ErrorResult& aErr);
+ int16_t ComparePoint(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr);
+ void DeleteContents(ErrorResult& aRv);
+ already_AddRefed<mozilla::dom::DocumentFragment>
+ ExtractContents(ErrorResult& aErr);
+ nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const;
+ nsINode* GetStartContainer(ErrorResult& aRv) const;
+ uint32_t GetStartOffset(ErrorResult& aRv) const;
+ nsINode* GetEndContainer(ErrorResult& aRv) const;
+ uint32_t GetEndOffset(ErrorResult& aRv) const;
+ void InsertNode(nsINode& aNode, ErrorResult& aErr);
+ bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);
+ bool IsPointInRange(nsINode& aParent, uint32_t aOffset, ErrorResult& aErr);
+ void SelectNode(nsINode& aNode, ErrorResult& aErr);
+ void SelectNodeContents(nsINode& aNode, ErrorResult& aErr);
+ void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetEndAfter(nsINode& aNode, ErrorResult& aErr);
+ void SetEndBefore(nsINode& aNode, ErrorResult& aErr);
+ void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
+ void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
+ void SetStartBefore(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);
+ static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
+ mozilla::ErrorResult& aError,
+ nsIContent* aStartParent,
+ uint32_t aStartOffset,
+ nsIContent* aEndParent,
+ uint32_t aEndOffset);
+
+ nsINode* GetParentObject() const { return mOwner; }
+ virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override final;
+
+private:
+ // no copy's or assigns
+ nsRange(const nsRange&);
+ nsRange& operator=(const nsRange&);
+
+ /**
+ * Cut or delete the range's contents.
+ *
+ * @param aFragment nsIDOMDocumentFragment containing the nodes.
+ * May be null to indicate the caller doesn't want a fragment.
+ */
+ nsresult CutContents(mozilla::dom::DocumentFragment** frag);
+
+ static nsresult CloneParentsBetween(nsINode* aAncestor,
+ nsINode* aNode,
+ nsINode** aClosestAncestor,
+ nsINode** aFarthestAncestor);
+
+public:
+/******************************************************************************
+ * 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.
+ *
+ * XXX - callers responsibility to ensure node in same doc as range!
+ *
+ *****************************************************************************/
+ static nsresult CompareNodeToRange(nsINode* aNode, nsRange* aRange,
+ bool *outNodeBefore,
+ bool *outNodeAfter);
+
+ /**
+ * Return true if any part of (aNode, aStartOffset) .. (aNode, aEndOffset)
+ * overlaps any nsRange in aNode's GetNextRangeCommonAncestor ranges (i.e.
+ * where aNode is a descendant of a range's common ancestor node).
+ * If a nsRange starts in (aNode, aEndOffset) or if it ends in
+ * (aNode, aStartOffset) then it is non-overlapping and the result is false
+ * for that nsRange. Collapsed ranges always counts as non-overlapping.
+ */
+ static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
+ uint32_t aEndOffset);
+
+ /**
+ * This helper function gets rects and correlated text for the given range.
+ * @param aTextList optional where nullptr = don't retrieve text
+ */
+ static void CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
+ mozilla::dom::DOMStringList* aTextList,
+ nsRange* aRange,
+ nsINode* aStartParent, int32_t aStartOffset,
+ nsINode* aEndParent, int32_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);
+
+ typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable;
+protected:
+ void RegisterCommonAncestor(nsINode* aNode);
+ void UnregisterCommonAncestor(nsINode* aNode);
+ nsINode* IsValidBoundary(nsINode* aNode);
+
+ // CharacterDataChanged set aNotInsertedYet to true to disable an 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.
+ void DoSetRange(nsINode* aStartN, int32_t aStartOffset,
+ nsINode* aEndN, int32_t aEndOffset,
+ nsINode* aRoot, bool aNotInsertedYet = false);
+
+ /**
+ * For a range for which IsInSelection() is true, return the common
+ * ancestor for the range. This method uses the selection bits and
+ * nsGkAtoms::range property on the nodes to quickly find the ancestor.
+ * That is, it's a faster version of GetCommonAncestor 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* GetRegisteredCommonAncestor();
+
+ // Helper to IsNodeSelected.
+ static bool IsNodeInSortedRanges(nsINode* aNode,
+ uint32_t aStartOffset,
+ uint32_t aEndOffset,
+ const nsTArray<const nsRange*>& aRanges,
+ size_t aRangeStart,
+ size_t aRangeEnd);
+
+ struct MOZ_STACK_CLASS AutoInvalidateSelection
+ {
+ explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange)
+ {
+#ifdef DEBUG
+ mWasInSelection = mRange->IsInSelection();
+#endif
+ if (!mRange->IsInSelection() || mIsNested) {
+ return;
+ }
+ mIsNested = true;
+ mCommonAncestor = mRange->GetRegisteredCommonAncestor();
+ }
+ ~AutoInvalidateSelection();
+ nsRange* mRange;
+ RefPtr<nsINode> mCommonAncestor;
+#ifdef DEBUG
+ bool mWasInSelection;
+#endif
+ static bool mIsNested;
+ };
+
+ nsCOMPtr<nsIDocument> mOwner;
+ nsCOMPtr<nsINode> mRoot;
+ nsCOMPtr<nsINode> mStartParent;
+ nsCOMPtr<nsINode> mEndParent;
+ RefPtr<mozilla::dom::Selection> mSelection;
+ int32_t mStartOffset;
+ int32_t mEndOffset;
+
+ bool mIsPositioned : 1;
+ bool mMaySpanAnonymousSubtrees : 1;
+ bool mIsGenerated : 1;
+ bool mStartOffsetWasIncremented : 1;
+ bool mEndOffsetWasIncremented : 1;
+ bool mEnableGravitationOnElementRemoval : 1;
+#ifdef DEBUG
+ int32_t mAssertNextInsertOrAppendIndex;
+ nsINode* mAssertNextInsertOrAppendNode;
+#endif
+};
+
+inline nsISupports*
+ToCanonicalSupports(nsRange* aRange)
+{
+ return static_cast<nsIDOMRange*>(aRange);
+}
+
+inline nsISupports*
+ToSupports(nsRange* aRange)
+{
+ return static_cast<nsIDOMRange*>(aRange);
+}
+
+#endif /* nsRange_h___ */
diff --git a/dom/base/nsReferencedElement.cpp b/dom/base/nsReferencedElement.cpp
new file mode 100644
index 000000000..4d708eb16
--- /dev/null
+++ b/dom/base/nsReferencedElement.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsReferencedElement.h"
+#include "nsContentUtils.h"
+#include "nsIURI.h"
+#include "nsBindingManager.h"
+#include "nsEscape.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+#include "nsCycleCollectionParticipant.h"
+
+void
+nsReferencedElement::Reset(nsIContent* aFromContent, nsIURI* aURI,
+ bool aWatch, bool aReferenceImage)
+{
+ MOZ_ASSERT(aFromContent, "Reset() 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
+ // origin charset of the URL, hopefully...
+ NS_UnescapeURL(refPart);
+
+ nsAutoCString charset;
+ aURI->GetOriginCharset(charset);
+ nsAutoString ref;
+ nsresult rv = nsContentUtils::ConvertStringFromEncoding(charset,
+ refPart,
+ ref);
+ if (NS_FAILED(rv) || ref.IsEmpty()) {
+ return;
+ }
+
+ // Get the current document
+ nsIDocument *doc = aFromContent->OwnerDoc();
+ if (!doc)
+ return;
+
+ nsIContent* bindingParent = aFromContent->GetBindingParent();
+ if (bindingParent) {
+ nsXBLBinding* binding = bindingParent->GetXBLBinding();
+ if (!binding) {
+ // This happens, for example, if aFromContent is part of the content
+ // inserted by a call to nsIDocument::InsertAnonymousContent, which we
+ // also want to handle. (It also happens for <use>'s 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;
+ }
+ } else {
+ bool isEqualExceptRef;
+ rv = aURI->EqualsExceptRef(binding->PrototypeBinding()->DocURI(),
+ &isEqualExceptRef);
+ if (NS_SUCCEEDED(rv) && isEqualExceptRef) {
+ // XXX sXBL/XBL2 issue
+ // Our content is an anonymous XBL element from a binding inside the
+ // same document that the referenced URI points to. In order to avoid
+ // the risk of ID collisions we restrict ourselves to anonymous
+ // elements from this binding; specifically, URIs that are relative to
+ // the binding document should resolve to the copy of the target
+ // element that has been inserted into the bound document.
+ // If the URI points to a different document we don't need this
+ // restriction.
+ nsINodeList* anonymousChildren =
+ doc->BindingManager()->GetAnonymousNodesFor(bindingParent);
+
+ if (anonymousChildren) {
+ uint32_t length;
+ anonymousChildren->GetLength(&length);
+ for (uint32_t i = 0; i < length && !mElement; ++i) {
+ mElement =
+ nsContentUtils::MatchElementId(anonymousChildren->Item(i), ref);
+ }
+ }
+
+ // We don't have watching working yet for XBL, so bail out here.
+ return;
+ }
+ }
+ }
+
+ bool isEqualExceptRef;
+ rv = aURI->EqualsExceptRef(doc->GetDocumentURI(), &isEqualExceptRef);
+ if (NS_FAILED(rv) || !isEqualExceptRef) {
+ RefPtr<nsIDocument::ExternalResourceLoad> load;
+ doc = doc->RequestExternalResource(aURI, aFromContent,
+ getter_AddRefs(load));
+ if (!doc) {
+ if (!load || !aWatch) {
+ // Nothing will ever happen here
+ return;
+ }
+
+ DocumentLoadNotification* observer =
+ new DocumentLoadNotification(this, ref);
+ mPendingNotification = observer;
+ if (observer) {
+ load->AddObserver(observer);
+ }
+ // Keep going so we set up our watching stuff a bit
+ }
+ }
+
+ if (aWatch) {
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(ref);
+ if (!atom)
+ return;
+ atom.swap(mWatchID);
+ }
+
+ mReferencingImage = aReferenceImage;
+
+ HaveNewDocument(doc, aWatch, ref);
+}
+
+void
+nsReferencedElement::ResetWithID(nsIContent* aFromContent, const nsString& aID,
+ bool aWatch)
+{
+ nsIDocument *doc = aFromContent->OwnerDoc();
+ if (!doc)
+ return;
+
+ // XXX Need to take care of XBL/XBL2
+
+ if (aWatch) {
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(aID);
+ if (!atom)
+ return;
+ atom.swap(mWatchID);
+ }
+
+ mReferencingImage = false;
+
+ HaveNewDocument(doc, aWatch, aID);
+}
+
+void
+nsReferencedElement::HaveNewDocument(nsIDocument* aDocument, bool aWatch,
+ const nsString& aRef)
+{
+ if (aWatch) {
+ mWatchDocument = aDocument;
+ if (mWatchDocument) {
+ mElement = mWatchDocument->AddIDTargetObserver(mWatchID, Observe, this,
+ mReferencingImage);
+ }
+ return;
+ }
+
+ if (!aDocument) {
+ return;
+ }
+
+ Element *e = mReferencingImage ? aDocument->LookupImageElement(aRef) :
+ aDocument->GetElementById(aRef);
+ if (e) {
+ mElement = e;
+ }
+}
+
+void
+nsReferencedElement::Traverse(nsCycleCollectionTraversalCallback* aCB)
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocument");
+ aCB->NoteXPCOMChild(mWatchDocument);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mContent");
+ aCB->NoteXPCOMChild(mElement);
+}
+
+void
+nsReferencedElement::Unlink()
+{
+ if (mWatchDocument && mWatchID) {
+ mWatchDocument->RemoveIDTargetObserver(mWatchID, Observe, this,
+ mReferencingImage);
+ }
+ if (mPendingNotification) {
+ mPendingNotification->Clear();
+ mPendingNotification = nullptr;
+ }
+ mWatchDocument = nullptr;
+ mWatchID = nullptr;
+ mElement = nullptr;
+ mReferencingImage = false;
+}
+
+bool
+nsReferencedElement::Observe(Element* aOldElement,
+ Element* aNewElement, void* aData)
+{
+ nsReferencedElement* p = static_cast<nsReferencedElement*>(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->mWatchDocument = nullptr;
+ p->mWatchID = nullptr;
+ }
+ return keepTracking;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsReferencedElement::ChangeNotification,
+ mozilla::Runnable)
+
+NS_IMPL_ISUPPORTS(nsReferencedElement::DocumentLoadNotification,
+ nsIObserver)
+
+NS_IMETHODIMP
+nsReferencedElement::DocumentLoadNotification::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ NS_ASSERTION(PL_strcmp(aTopic, "external-resource-document-created") == 0,
+ "Unexpected topic");
+ if (mTarget) {
+ nsCOMPtr<nsIDocument> 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->HaveNewDocument(doc, mTarget->IsPersistent(), mRef);
+ mTarget->ElementChanged(nullptr, mTarget->mElement);
+ }
+ return NS_OK;
+}
diff --git a/dom/base/nsReferencedElement.h b/dom/base/nsReferencedElement.h
new file mode 100644
index 000000000..730a19610
--- /dev/null
+++ b/dom/base/nsReferencedElement.h
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 NSREFERENCEDELEMENT_H_
+#define NSREFERENCEDELEMENT_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Element.h"
+#include "nsIAtom.h"
+#include "nsIDocument.h"
+#include "nsThreadUtils.h"
+
+class nsIURI;
+
+/**
+ * Class to track what element is referenced by a given ID.
+ *
+ * To use it, call Reset() to set it up to watch a given URI. 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 nsReferencedElement {
+public:
+ typedef mozilla::dom::Element Element;
+
+ nsReferencedElement()
+ : mReferencingImage(false)
+ {}
+ ~nsReferencedElement() {
+ Unlink();
+ }
+
+ /**
+ * 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 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 Reset(nsIContent* aFrom, nsIURI* aURI, bool aWatch = true,
+ bool aReferenceImage = false);
+
+ /**
+ * A variation on Reset() 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(nsIContent* aFrom, const nsString& 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) {
+ mElement = 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 HaveNewDocument(nsIDocument* aDocument, 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() {}
+ protected:
+ explicit Notification(nsReferencedElement* aTarget)
+ : mTarget(aTarget)
+ {
+ NS_PRECONDITION(aTarget, "Must have a target");
+ }
+ nsReferencedElement* mTarget;
+ };
+
+ class ChangeNotification : public mozilla::Runnable,
+ public Notification
+ {
+ public:
+ ChangeNotification(nsReferencedElement* aTarget,
+ Element* aFrom, Element* aTo)
+ : Notification(aTarget), mFrom(aFrom), mTo(aTo)
+ {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHOD Run() override {
+ if (mTarget) {
+ mTarget->mPendingNotification = nullptr;
+ mTarget->ElementChanged(mFrom, mTo);
+ }
+ return NS_OK;
+ }
+ virtual void SetTo(Element* aTo) override { mTo = aTo; }
+ virtual void Clear() override
+ {
+ Notification::Clear(); mFrom = nullptr; mTo = nullptr;
+ }
+ protected:
+ virtual ~ChangeNotification() {}
+
+ RefPtr<Element> mFrom;
+ RefPtr<Element> mTo;
+ };
+ friend class ChangeNotification;
+
+ class DocumentLoadNotification : public Notification,
+ public nsIObserver
+ {
+ public:
+ DocumentLoadNotification(nsReferencedElement* aTarget,
+ const nsString& aRef) :
+ Notification(aTarget)
+ {
+ if (!mTarget->IsPersistent()) {
+ mRef = aRef;
+ }
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ private:
+ virtual ~DocumentLoadNotification() {}
+
+ virtual void SetTo(Element* aTo) override { }
+
+ nsString mRef;
+ };
+ friend class DocumentLoadNotification;
+
+ nsCOMPtr<nsIAtom> mWatchID;
+ nsCOMPtr<nsIDocument> mWatchDocument;
+ RefPtr<Element> mElement;
+ RefPtr<Notification> mPendingNotification;
+ bool mReferencingImage;
+};
+
+inline void
+ImplCycleCollectionUnlink(nsReferencedElement& aField)
+{
+ aField.Unlink();
+}
+
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ nsReferencedElement& aField,
+ const char* aName,
+ uint32_t aFlags = 0)
+{
+ aField.Traverse(&aCallback);
+}
+
+#endif /*NSREFERENCEDELEMENT_H_*/
diff --git a/dom/base/nsSandboxFlags.h b/dom/base/nsSandboxFlags.h
new file mode 100644
index 000000000..d18527597
--- /dev/null
+++ b/dom/base/nsSandboxFlags.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/. */
+
+/*
+ * 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, the window.open() method, or the
+ * showModalDialog() 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, the applet 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;
+
+const unsigned long SANDBOX_ALL_FLAGS = 0x7FFF;
+#endif
diff --git a/dom/base/nsScreen.cpp b/dom/base/nsScreen.cpp
new file mode 100644
index 000000000..8b129531f
--- /dev/null
+++ b/dom/base/nsScreen.cpp
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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" // for nsIDOMEvent::InternalDOMEvent()
+#include "mozilla/dom/ScreenBinding.h"
+#include "nsContentUtils.h"
+#include "nsScreen.h"
+#include "nsIDocument.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+#include "nsCOMPtr.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsLayoutUtils.h"
+#include "nsJSUtils.h"
+#include "nsDeviceContext.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/* static */ already_AddRefed<nsScreen>
+nsScreen::Create(nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+
+ 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()
+{
+}
+
+
+// QueryInterface implementation for nsScreen
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsScreen)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMScreen)
+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;
+ }
+
+ uint32_t depth;
+ context->GetDepth(depth);
+ return depth;
+}
+
+#define FORWARD_LONG_GETTER(_name) \
+ NS_IMETHODIMP \
+ nsScreen::Get ## _name(int32_t* aOut) \
+ { \
+ ErrorResult rv; \
+ *aOut = Get ## _name(rv); \
+ return rv.StealNSResult(); \
+ }
+
+FORWARD_LONG_GETTER(AvailWidth)
+FORWARD_LONG_GETTER(AvailHeight)
+FORWARD_LONG_GETTER(Width)
+FORWARD_LONG_GETTER(Height)
+
+FORWARD_LONG_GETTER(Top)
+FORWARD_LONG_GETTER(Left)
+FORWARD_LONG_GETTER(AvailTop)
+FORWARD_LONG_GETTER(AvailLeft)
+
+FORWARD_LONG_GETTER(PixelDepth)
+FORWARD_LONG_GETTER(ColorDepth)
+
+nsPIDOMWindowOuter*
+nsScreen::GetOuter() const
+{
+ if (nsPIDOMWindowInner* inner = GetOwner()) {
+ return inner->GetOuterWindow();
+ }
+
+ return nullptr;
+}
+
+nsDeviceContext*
+nsScreen::GetDeviceContext()
+{
+ return nsLayoutUtils::GetDeviceContextForScreenInfo(GetOuter());
+}
+
+nsresult
+nsScreen::GetRect(nsRect& aRect)
+{
+ // Return window inner rect to prevent fingerprinting.
+ if (ShouldResistFingerprinting()) {
+ return GetWindowInnerRect(aRect);
+ }
+
+ nsDeviceContext *context = GetDeviceContext();
+
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ context->GetRect(aRect);
+ LayoutDevicePoint screenTopLeftDev =
+ LayoutDevicePixel::FromAppUnits(aRect.TopLeft(),
+ context->AppUnitsPerDevPixel());
+ DesktopPoint screenTopLeftDesk =
+ screenTopLeftDev / context->GetDesktopToDeviceScale();
+
+ aRect.x = NSToIntRound(screenTopLeftDesk.x);
+ aRect.y = NSToIntRound(screenTopLeftDesk.y);
+
+ aRect.height = nsPresContext::AppUnitsToIntCSSPixels(aRect.height);
+ aRect.width = nsPresContext::AppUnitsToIntCSSPixels(aRect.width);
+
+ return NS_OK;
+}
+
+nsresult
+nsScreen::GetAvailRect(nsRect& aRect)
+{
+ // Return window inner rect to prevent fingerprinting.
+ if (ShouldResistFingerprinting()) {
+ return GetWindowInnerRect(aRect);
+ }
+
+ nsDeviceContext *context = GetDeviceContext();
+
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsRect r;
+ context->GetRect(r);
+ LayoutDevicePoint screenTopLeftDev =
+ LayoutDevicePixel::FromAppUnits(r.TopLeft(),
+ context->AppUnitsPerDevPixel());
+ DesktopPoint screenTopLeftDesk =
+ screenTopLeftDev / context->GetDesktopToDeviceScale();
+
+ context->GetClientRect(aRect);
+
+ aRect.x = NSToIntRound(screenTopLeftDesk.x) +
+ nsPresContext::AppUnitsToIntCSSPixels(aRect.x - r.x);
+ aRect.y = NSToIntRound(screenTopLeftDesk.y) +
+ nsPresContext::AppUnitsToIntCSSPixels(aRect.y - r.y);
+
+ aRect.height = nsPresContext::AppUnitsToIntCSSPixels(aRect.height);
+ aRect.width = nsPresContext::AppUnitsToIntCSSPixels(aRect.width);
+
+ return NS_OK;
+}
+
+mozilla::dom::ScreenOrientation*
+nsScreen::Orientation() const
+{
+ return mScreenOrientation;
+}
+
+void
+nsScreen::GetMozOrientation(nsString& aOrientation) const
+{
+ switch (mScreenOrientation->DeviceType()) {
+ 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.");
+ }
+}
+
+NS_IMETHODIMP
+nsScreen::GetSlowMozOrientation(nsAString& aOrientation)
+{
+ nsString orientation;
+ GetMozOrientation(orientation);
+ aOrientation = orientation;
+ return NS_OK;
+}
+
+static void
+UpdateDocShellOrientationLock(nsPIDOMWindowInner* aWindow,
+ ScreenOrientationInternal aOrientation)
+{
+ if (!aWindow) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
+ nsCOMPtr<nsIDocShell> rootShell(do_QueryInterface(root));
+ if (!rootShell) {
+ return;
+ }
+
+ rootShell->SetOrientationLock(aOrientation);
+}
+
+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);
+}
+
+bool
+nsScreen::MozLockOrientation(const Sequence<nsString>& aOrientations,
+ ErrorResult& aRv)
+{
+ if (ShouldResistFingerprinting()) {
+ return false;
+ }
+ ScreenOrientationInternal orientation = eScreenOrientation_None;
+
+ for (uint32_t i = 0; i < aOrientations.Length(); ++i) {
+ const nsString& item = aOrientations[i];
+
+ if (item.EqualsLiteral("portrait")) {
+ orientation |= eScreenOrientation_PortraitPrimary |
+ eScreenOrientation_PortraitSecondary;
+ } else if (item.EqualsLiteral("portrait-primary")) {
+ orientation |= eScreenOrientation_PortraitPrimary;
+ } else if (item.EqualsLiteral("portrait-secondary")) {
+ orientation |= eScreenOrientation_PortraitSecondary;
+ } else if (item.EqualsLiteral("landscape")) {
+ orientation |= eScreenOrientation_LandscapePrimary |
+ eScreenOrientation_LandscapeSecondary;
+ } else if (item.EqualsLiteral("landscape-primary")) {
+ orientation |= eScreenOrientation_LandscapePrimary;
+ } else if (item.EqualsLiteral("landscape-secondary")) {
+ orientation |= eScreenOrientation_LandscapeSecondary;
+ } else if (item.EqualsLiteral("default")) {
+ orientation |= eScreenOrientation_Default;
+ } else {
+ // If we don't recognize the token, we should just return 'false'
+ // without throwing.
+ return false;
+ }
+ }
+
+ switch (mScreenOrientation->GetLockOrientationPermission(false)) {
+ case ScreenOrientation::LOCK_DENIED:
+ return false;
+ case ScreenOrientation::LOCK_ALLOWED:
+ UpdateDocShellOrientationLock(GetOwner(), orientation);
+ return mScreenOrientation->LockDeviceOrientation(orientation, false, aRv);
+ case ScreenOrientation::FULLSCREEN_LOCK_ALLOWED:
+ UpdateDocShellOrientationLock(GetOwner(), orientation);
+ return mScreenOrientation->LockDeviceOrientation(orientation, true, aRv);
+ }
+
+ // This is only for compilers that don't understand that the previous switch
+ // will always return.
+ MOZ_CRASH("unexpected lock orientation permission value");
+}
+
+void
+nsScreen::MozUnlockOrientation()
+{
+ if (ShouldResistFingerprinting()) {
+ return;
+ }
+ UpdateDocShellOrientationLock(GetOwner(), eScreenOrientation_None);
+ mScreenOrientation->UnlockDeviceOrientation();
+}
+
+bool
+nsScreen::IsDeviceSizePageSize()
+{
+ if (nsPIDOMWindowInner* owner = GetOwner()) {
+ nsIDocShell* docShell = owner->GetDocShell();
+ if (docShell) {
+ return docShell->GetDeviceSizeIsPageSize();
+ }
+ }
+ return false;
+}
+
+/* virtual */
+JSObject*
+nsScreen::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ScreenBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult
+nsScreen::GetWindowInnerRect(nsRect& aRect)
+{
+ aRect.x = 0;
+ aRect.y = 0;
+ nsCOMPtr<nsPIDOMWindowInner> win = GetOwner();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = win->GetInnerWidth(&aRect.width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return win->GetInnerHeight(&aRect.height);
+}
+
+bool nsScreen::ShouldResistFingerprinting() const
+{
+ bool resist = false;
+ nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner();
+ if (owner) {
+ resist = nsContentUtils::ShouldResistFingerprinting(owner->GetDocShell());
+ }
+ return resist;
+}
diff --git a/dom/base/nsScreen.h b/dom/base/nsScreen.h
new file mode 100644
index 000000000..15bc602b7
--- /dev/null
+++ b/dom/base/nsScreen.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 nsScreen_h___
+#define nsScreen_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/ScreenOrientation.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/ErrorResult.h"
+#include "nsIDOMScreen.h"
+#include "nsCOMPtr.h"
+#include "nsRect.h"
+
+class nsDeviceContext;
+
+// Script "screen" object
+class nsScreen : public mozilla::DOMEventTargetHelper
+ , public nsIDOMScreen
+{
+ typedef mozilla::ErrorResult ErrorResult;
+public:
+ static already_AddRefed<nsScreen> Create(nsPIDOMWindowInner* aWindow);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMSCREEN
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsScreen, mozilla::DOMEventTargetHelper)
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(mozilla::DOMEventTargetHelper)
+
+ nsPIDOMWindowInner* GetParentObject() const
+ {
+ return GetOwner();
+ }
+
+ nsPIDOMWindowOuter* GetOuter() const;
+
+ int32_t GetTop(ErrorResult& aRv)
+ {
+ nsRect rect;
+ aRv = GetRect(rect);
+ return rect.y;
+ }
+
+ int32_t GetLeft(ErrorResult& aRv)
+ {
+ nsRect rect;
+ aRv = GetRect(rect);
+ return rect.x;
+ }
+
+ int32_t GetWidth(ErrorResult& aRv)
+ {
+ nsRect rect;
+ if (IsDeviceSizePageSize()) {
+ if (nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner()) {
+ int32_t innerWidth = 0;
+ aRv = owner->GetInnerWidth(&innerWidth);
+ return innerWidth;
+ }
+ }
+
+ aRv = GetRect(rect);
+ return rect.width;
+ }
+
+ int32_t GetHeight(ErrorResult& aRv)
+ {
+ nsRect rect;
+ if (IsDeviceSizePageSize()) {
+ if (nsCOMPtr<nsPIDOMWindowInner> owner = GetOwner()) {
+ int32_t innerHeight = 0;
+ aRv = owner->GetInnerHeight(&innerHeight);
+ return innerHeight;
+ }
+ }
+
+ aRv = GetRect(rect);
+ return rect.height;
+ }
+
+ int32_t GetPixelDepth(ErrorResult& aRv);
+ int32_t GetColorDepth(ErrorResult& aRv)
+ {
+ return GetPixelDepth(aRv);
+ }
+
+ int32_t GetAvailTop(ErrorResult& aRv)
+ {
+ nsRect rect;
+ aRv = GetAvailRect(rect);
+ return rect.y;
+ }
+
+ int32_t GetAvailLeft(ErrorResult& aRv)
+ {
+ nsRect rect;
+ aRv = GetAvailRect(rect);
+ return rect.x;
+ }
+
+ int32_t GetAvailWidth(ErrorResult& aRv)
+ {
+ nsRect rect;
+ aRv = GetAvailRect(rect);
+ return rect.width;
+ }
+
+ int32_t GetAvailHeight(ErrorResult& aRv)
+ {
+ nsRect rect;
+ aRv = GetAvailRect(rect);
+ return rect.height;
+ }
+
+ // Deprecated
+ void GetMozOrientation(nsString& aOrientation) 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;
+
+protected:
+ nsDeviceContext* GetDeviceContext();
+ nsresult GetRect(nsRect& aRect);
+ nsresult GetAvailRect(nsRect& aRect);
+ nsresult GetWindowInnerRect(nsRect& aRect);
+
+private:
+ explicit nsScreen(nsPIDOMWindowInner* aWindow);
+ virtual ~nsScreen();
+
+ bool IsDeviceSizePageSize();
+
+ bool ShouldResistFingerprinting() const;
+
+ RefPtr<mozilla::dom::ScreenOrientation> mScreenOrientation;
+};
+
+#endif /* nsScreen_h___ */
diff --git a/dom/base/nsScriptElement.cpp b/dom/base/nsScriptElement.cpp
new file mode 100644
index 000000000..ebeb18f81
--- /dev/null
+++ b/dom/base/nsScriptElement.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 "nsScriptElement.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Element.h"
+#include "nsContentUtils.h"
+#include "nsPresContext.h"
+#include "nsScriptLoader.h"
+#include "nsIParser.h"
+#include "nsGkAtoms.h"
+#include "nsContentSink.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMETHODIMP
+nsScriptElement::ScriptAvailable(nsresult aResult,
+ nsIScriptElement *aElement,
+ bool aIsInline,
+ nsIURI *aURI,
+ int32_t aLineNo)
+{
+ if (!aIsInline && NS_FAILED(aResult)) {
+ nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
+ if (parser) {
+ parser->PushDefinedInsertionPoint();
+ }
+ nsresult rv = FireErrorEvent();
+ if (parser) {
+ parser->PopDefinedInsertionPoint();
+ }
+ return rv;
+ }
+ return NS_OK;
+}
+
+/* virtual */ nsresult
+nsScriptElement::FireErrorEvent()
+{
+ nsCOMPtr<nsIContent> cont =
+ do_QueryInterface((nsIScriptElement*) this);
+
+ return nsContentUtils::DispatchTrustedEvent(cont->OwnerDoc(),
+ cont,
+ NS_LITERAL_STRING("error"),
+ false /* bubbles */,
+ false /* cancelable */);
+}
+
+NS_IMETHODIMP
+nsScriptElement::ScriptEvaluated(nsresult aResult,
+ nsIScriptElement *aElement,
+ bool aIsInline)
+{
+ nsresult rv = NS_OK;
+ if (!aIsInline) {
+ nsCOMPtr<nsIContent> cont =
+ do_QueryInterface((nsIScriptElement*) this);
+
+ RefPtr<nsPresContext> presContext =
+ nsContentUtils::GetContextForContent(cont);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventMessage message = NS_SUCCEEDED(aResult) ? eLoad : eLoadError;
+ WidgetEvent event(true, message);
+ // Load event doesn't bubble.
+ event.mFlags.mBubbles = (message != eLoad);
+
+ EventDispatcher::Dispatch(cont, presContext, &event, nullptr, &status);
+ }
+
+ return rv;
+}
+
+void
+nsScriptElement::CharacterDataChanged(nsIDocument *aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ MaybeProcessScript();
+}
+
+void
+nsScriptElement::AttributeChanged(nsIDocument* aDocument,
+ Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ MaybeProcessScript();
+}
+
+void
+nsScriptElement::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ MaybeProcessScript();
+}
+
+void
+nsScriptElement::ContentInserted(nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ MaybeProcessScript();
+}
+
+bool
+nsScriptElement::MaybeProcessScript()
+{
+ nsCOMPtr<nsIContent> cont =
+ do_QueryInterface((nsIScriptElement*) this);
+
+ NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.Contains(this),
+ "You forgot to add self as observer");
+
+ if (mAlreadyStarted || !mDoneAddingChildren ||
+ !cont->GetComposedDoc() || mMalformed || !HasScriptContent()) {
+ return false;
+ }
+
+ FreezeUriAsyncDefer();
+
+ mAlreadyStarted = true;
+
+ nsIDocument* ownerDoc = cont->OwnerDoc();
+ nsCOMPtr<nsIParser> parser = ((nsIScriptElement*) this)->GetCreatorParser();
+ if (parser) {
+ nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
+ if (sink) {
+ nsCOMPtr<nsIDocument> parserDoc = do_QueryInterface(sink->GetTarget());
+ if (ownerDoc != parserDoc) {
+ // Willful violation of HTML5 as of 2010-12-01
+ return false;
+ }
+ }
+ }
+
+ RefPtr<nsScriptLoader> loader = ownerDoc->ScriptLoader();
+ return loader->ProcessScriptElement(this);
+}
diff --git a/dom/base/nsScriptElement.h b/dom/base/nsScriptElement.h
new file mode 100644
index 000000000..4a2a584ac
--- /dev/null
+++ b/dom/base/nsScriptElement.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 nsScriptElement_h
+#define nsScriptElement_h
+
+#include "mozilla/Attributes.h"
+#include "nsIScriptLoaderObserver.h"
+#include "nsIScriptElement.h"
+#include "nsStubMutationObserver.h"
+
+/**
+ * Baseclass useful for script elements (such as <xhtml:script> and
+ * <svg:script>). Currently the class assumes that only the 'src'
+ * attribute and the children of the class affect what script to execute.
+ */
+
+class nsScriptElement : public nsIScriptElement,
+ public nsStubMutationObserver
+{
+public:
+ // nsIScriptLoaderObserver
+ NS_DECL_NSISCRIPTLOADEROBSERVER
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+
+ explicit nsScriptElement(mozilla::dom::FromParser aFromParser)
+ : nsIScriptElement(aFromParser)
+ {
+ }
+
+ virtual nsresult FireErrorEvent() override;
+
+protected:
+ // Internal methods
+
+ /**
+ * Check if this element contains any script, linked or inline
+ */
+ virtual bool HasScriptContent() = 0;
+
+ virtual bool MaybeProcessScript() override;
+};
+
+#endif // nsScriptElement_h
diff --git a/dom/base/nsScriptLoader.cpp b/dom/base/nsScriptLoader.cpp
new file mode 100644
index 000000000..6c732db6c
--- /dev/null
+++ b/dom/base/nsScriptLoader.cpp
@@ -0,0 +1,3016 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 that handles loading and evaluation of <script> elements.
+ */
+
+#include "nsScriptLoader.h"
+
+#include "prsystem.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "xpcpublic.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIContent.h"
+#include "nsJSUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "nsGkAtoms.h"
+#include "nsNetUtil.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIClassOfService.h"
+#include "nsITimedChannel.h"
+#include "nsIScriptElement.h"
+#include "nsIDOMHTMLScriptElement.h"
+#include "nsIDocShell.h"
+#include "nsContentUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsAutoPtr.h"
+#include "nsIXPConnect.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+#include "nsDocShellCID.h"
+#include "nsIContentSecurityPolicy.h"
+#include "mozilla/Logging.h"
+#include "nsCRT.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsProxyRelease.h"
+#include "nsSandboxFlags.h"
+#include "nsContentTypeParser.h"
+#include "nsINetworkPredictor.h"
+#include "ImportManager.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/ConsoleReportCollector.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+#include "nsIScriptError.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+using JS::SourceBufferHolder;
+
+static LazyLogModule gCspPRLog("CSP");
+
+void
+ImplCycleCollectionUnlink(nsScriptLoadRequestList& aField);
+
+void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ nsScriptLoadRequestList& aField,
+ const char* aName,
+ uint32_t aFlags = 0);
+
+//////////////////////////////////////////////////////////////
+// nsScriptLoadRequest
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsScriptLoadRequest)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_0(nsScriptLoadRequest)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsScriptLoadRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsScriptLoadRequest)
+
+nsScriptLoadRequest::~nsScriptLoadRequest()
+{
+ js_free(mScriptTextBuf);
+
+ // We should always clean up any off-thread script parsing resources.
+ MOZ_ASSERT(!mOffThreadToken);
+
+ // But play it safe in release builds and try to clean them up here
+ // as a fail safe.
+ MaybeCancelOffThreadScript();
+}
+
+void
+nsScriptLoadRequest::SetReady()
+{
+ MOZ_ASSERT(mProgress != Progress::Ready);
+ mProgress = Progress::Ready;
+}
+
+void
+nsScriptLoadRequest::Cancel()
+{
+ MaybeCancelOffThreadScript();
+ mIsCanceled = true;
+}
+
+void
+nsScriptLoadRequest::MaybeCancelOffThreadScript()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mOffThreadToken) {
+ return;
+ }
+
+ JSContext* cx = danger::GetJSContext();
+ JS::CancelOffThreadScript(cx, mOffThreadToken);
+ mOffThreadToken = nullptr;
+}
+
+//////////////////////////////////////////////////////////////
+// nsModuleLoadRequest
+//////////////////////////////////////////////////////////////
+
+// A load request for a module, created for every top level module script and
+// every module import. Load request can share an nsModuleScript if there are
+// multiple imports of the same module.
+
+class nsModuleLoadRequest final : public nsScriptLoadRequest
+{
+ ~nsModuleLoadRequest() {}
+
+ nsModuleLoadRequest(const nsModuleLoadRequest& aOther) = delete;
+ nsModuleLoadRequest(nsModuleLoadRequest&& aOther) = delete;
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsModuleLoadRequest, nsScriptLoadRequest)
+
+ nsModuleLoadRequest(nsIScriptElement* aElement,
+ uint32_t aVersion,
+ CORSMode aCORSMode,
+ const SRIMetadata& aIntegrity,
+ nsScriptLoader* aLoader);
+
+ bool IsTopLevel() const {
+ return mIsTopLevel;
+ }
+
+ void SetReady() override;
+ void Cancel() override;
+
+ void ModuleLoaded();
+ void DependenciesLoaded();
+ void LoadFailed();
+
+ // Is this a request for a top level module script or an import?
+ bool mIsTopLevel;
+
+ // The base URL used for resolving relative module imports.
+ nsCOMPtr<nsIURI> mBaseURL;
+
+ // Pointer to the script loader, used to trigger actions when the module load
+ // finishes.
+ RefPtr<nsScriptLoader> mLoader;
+
+ // The importing module, or nullptr for top level module scripts. Used to
+ // implement the ancestor list checked when fetching module dependencies.
+ RefPtr<nsModuleLoadRequest> mParent;
+
+ // Set to a module script object after a successful load or nullptr on
+ // failure.
+ RefPtr<nsModuleScript> mModuleScript;
+
+ // A promise that is completed on successful load of this module and all of
+ // its dependencies, indicating that the module is ready for instantiation and
+ // evaluation.
+ MozPromiseHolder<GenericPromise> mReady;
+
+ // Array of imported modules.
+ nsTArray<RefPtr<nsModuleLoadRequest>> mImports;
+};
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsModuleLoadRequest)
+NS_INTERFACE_MAP_END_INHERITING(nsScriptLoadRequest)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsModuleLoadRequest, nsScriptLoadRequest,
+ mBaseURL,
+ mLoader,
+ mParent,
+ mModuleScript,
+ mImports)
+
+NS_IMPL_ADDREF_INHERITED(nsModuleLoadRequest, nsScriptLoadRequest)
+NS_IMPL_RELEASE_INHERITED(nsModuleLoadRequest, nsScriptLoadRequest)
+
+nsModuleLoadRequest::nsModuleLoadRequest(nsIScriptElement* aElement,
+ uint32_t aVersion,
+ CORSMode aCORSMode,
+ const SRIMetadata &aIntegrity,
+ nsScriptLoader* aLoader)
+ : nsScriptLoadRequest(nsScriptKind::Module,
+ aElement,
+ aVersion,
+ aCORSMode,
+ aIntegrity),
+ mIsTopLevel(true),
+ mLoader(aLoader)
+{}
+
+inline nsModuleLoadRequest*
+nsScriptLoadRequest::AsModuleRequest()
+{
+ MOZ_ASSERT(IsModuleRequest());
+ return static_cast<nsModuleLoadRequest*>(this);
+}
+
+void nsModuleLoadRequest::Cancel()
+{
+ nsScriptLoadRequest::Cancel();
+ mModuleScript = nullptr;
+ mProgress = nsScriptLoadRequest::Progress::Ready;
+ for (size_t i = 0; i < mImports.Length(); i++) {
+ mImports[i]->Cancel();
+ }
+ mReady.RejectIfExists(NS_ERROR_FAILURE, __func__);
+}
+
+void
+nsModuleLoadRequest::SetReady()
+{
+#ifdef DEBUG
+ for (size_t i = 0; i < mImports.Length(); i++) {
+ MOZ_ASSERT(mImports[i]->IsReadyToRun());
+ }
+#endif
+
+ nsScriptLoadRequest::SetReady();
+ mReady.ResolveIfExists(true, __func__);
+}
+
+void
+nsModuleLoadRequest::ModuleLoaded()
+{
+ // A module that was found to be marked as fetching in the module map has now
+ // been loaded.
+
+ mModuleScript = mLoader->GetFetchedModule(mURI);
+ mLoader->StartFetchingModuleDependencies(this);
+}
+
+void
+nsModuleLoadRequest::DependenciesLoaded()
+{
+ // The module and all of its dependencies have been successfully fetched and
+ // compiled.
+
+ if (!mLoader->InstantiateModuleTree(this)) {
+ LoadFailed();
+ return;
+ }
+
+ SetReady();
+ mLoader->ProcessLoadedModuleTree(this);
+ mLoader = nullptr;
+ mParent = nullptr;
+}
+
+void
+nsModuleLoadRequest::LoadFailed()
+{
+ Cancel();
+ mLoader->ProcessLoadedModuleTree(this);
+ mLoader = nullptr;
+ mParent = nullptr;
+}
+
+//////////////////////////////////////////////////////////////
+// nsModuleScript
+//////////////////////////////////////////////////////////////
+
+// A single module script. May be used to satisfy multiple load requests.
+
+class nsModuleScript final : public nsISupports
+{
+ enum InstantiationState {
+ Uninstantiated,
+ Instantiated,
+ Errored
+ };
+
+ RefPtr<nsScriptLoader> mLoader;
+ nsCOMPtr<nsIURI> mBaseURL;
+ JS::Heap<JSObject*> mModuleRecord;
+ JS::Heap<JS::Value> mException;
+ InstantiationState mInstantiationState;
+
+ ~nsModuleScript();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsModuleScript)
+
+ nsModuleScript(nsScriptLoader* aLoader,
+ nsIURI* aBaseURL,
+ JS::Handle<JSObject*> aModuleRecord);
+
+ nsScriptLoader* Loader() const { return mLoader; }
+ JSObject* ModuleRecord() const { return mModuleRecord; }
+ JS::Value Exception() const { return mException; }
+ nsIURI* BaseURL() const { return mBaseURL; }
+
+ void SetInstantiationResult(JS::Handle<JS::Value> aMaybeException);
+ bool IsUninstantiated() const {
+ return mInstantiationState == Uninstantiated;
+ }
+ bool IsInstantiated() const {
+ return mInstantiationState == Instantiated;
+ }
+ bool InstantiationFailed() const {
+ return mInstantiationState == Errored;
+ }
+
+ void UnlinkModuleRecord();
+};
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsModuleScript)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsModuleScript)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsModuleScript)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL)
+ tmp->UnlinkModuleRecord();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsModuleScript)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsModuleScript)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mModuleRecord)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mException)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsModuleScript)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsModuleScript)
+
+nsModuleScript::nsModuleScript(nsScriptLoader *aLoader, nsIURI* aBaseURL,
+ JS::Handle<JSObject*> aModuleRecord)
+ : mLoader(aLoader),
+ mBaseURL(aBaseURL),
+ mModuleRecord(aModuleRecord),
+ mInstantiationState(Uninstantiated)
+{
+ MOZ_ASSERT(mLoader);
+ MOZ_ASSERT(mBaseURL);
+ MOZ_ASSERT(mModuleRecord);
+ MOZ_ASSERT(mException.isUndefined());
+
+ // Make module's host defined field point to this module script object.
+ // This is cleared in the UnlinkModuleRecord().
+ JS::SetModuleHostDefinedField(mModuleRecord, JS::PrivateValue(this));
+ HoldJSObjects(this);
+}
+
+void
+nsModuleScript::UnlinkModuleRecord()
+{
+ // Remove module's back reference to this object request if present.
+ if (mModuleRecord) {
+ MOZ_ASSERT(JS::GetModuleHostDefinedField(mModuleRecord).toPrivate() ==
+ this);
+ JS::SetModuleHostDefinedField(mModuleRecord, JS::UndefinedValue());
+ }
+ mModuleRecord = nullptr;
+ mException.setUndefined();
+}
+
+nsModuleScript::~nsModuleScript()
+{
+ if (mModuleRecord) {
+ // The object may be destroyed without being unlinked first.
+ UnlinkModuleRecord();
+ }
+ DropJSObjects(this);
+}
+
+void
+nsModuleScript::SetInstantiationResult(JS::Handle<JS::Value> aMaybeException)
+{
+ MOZ_ASSERT(mInstantiationState == Uninstantiated);
+ MOZ_ASSERT(mModuleRecord);
+ MOZ_ASSERT(mException.isUndefined());
+
+ if (aMaybeException.isUndefined()) {
+ mInstantiationState = Instantiated;
+ } else {
+ mModuleRecord = nullptr;
+ mException = aMaybeException;
+ mInstantiationState = Errored;
+ }
+}
+
+//////////////////////////////////////////////////////////////
+
+// nsScriptLoadRequestList
+//////////////////////////////////////////////////////////////
+
+nsScriptLoadRequestList::~nsScriptLoadRequestList()
+{
+ Clear();
+}
+
+void
+nsScriptLoadRequestList::Clear()
+{
+ while (!isEmpty()) {
+ RefPtr<nsScriptLoadRequest> first = StealFirst();
+ first->Cancel();
+ // And just let it go out of scope and die.
+ }
+}
+
+#ifdef DEBUG
+bool
+nsScriptLoadRequestList::Contains(nsScriptLoadRequest* aElem) const
+{
+ for (const nsScriptLoadRequest* req = getFirst();
+ req; req = req->getNext()) {
+ if (req == aElem) {
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // DEBUG
+
+inline void
+ImplCycleCollectionUnlink(nsScriptLoadRequestList& aField)
+{
+ while (!aField.isEmpty()) {
+ RefPtr<nsScriptLoadRequest> first = aField.StealFirst();
+ }
+}
+
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ nsScriptLoadRequestList& aField,
+ const char* aName,
+ uint32_t aFlags)
+{
+ for (nsScriptLoadRequest* request = aField.getFirst();
+ request; request = request->getNext())
+ {
+ CycleCollectionNoteChild(aCallback, request, aName, aFlags);
+ }
+}
+
+//////////////////////////////////////////////////////////////
+// nsScriptLoader::PreloadInfo
+//////////////////////////////////////////////////////////////
+
+inline void
+ImplCycleCollectionUnlink(nsScriptLoader::PreloadInfo& aField)
+{
+ ImplCycleCollectionUnlink(aField.mRequest);
+}
+
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ nsScriptLoader::PreloadInfo& aField,
+ const char* aName,
+ uint32_t aFlags = 0)
+{
+ ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags);
+}
+
+//////////////////////////////////////////////////////////////
+// nsScriptLoader
+//////////////////////////////////////////////////////////////
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsScriptLoader)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsScriptLoader,
+ mNonAsyncExternalScriptInsertedRequests,
+ mLoadingAsyncRequests,
+ mLoadedAsyncRequests,
+ mDeferRequests,
+ mXSLTRequests,
+ mParserBlockingRequest,
+ mPreloads,
+ mPendingChildLoaders,
+ mFetchedModules)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsScriptLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsScriptLoader)
+
+nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
+ : mDocument(aDocument),
+ mParserBlockingBlockerCount(0),
+ mBlockerCount(0),
+ mNumberOfProcessors(0),
+ mEnabled(true),
+ mDeferEnabled(false),
+ mDocumentParsingDone(false),
+ mBlockingDOMContentLoaded(false),
+ mReporter(new ConsoleReportCollector())
+{
+}
+
+nsScriptLoader::~nsScriptLoader()
+{
+ mObservers.Clear();
+
+ if (mParserBlockingRequest) {
+ mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (nsScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (nsScriptLoadRequest* req = mDeferRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (nsScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for (nsScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ for(nsScriptLoadRequest* req = mNonAsyncExternalScriptInsertedRequests.getFirst();
+ req;
+ req = req->getNext()) {
+ req->FireScriptAvailable(NS_ERROR_ABORT);
+ }
+
+ // Unblock the kids, in case any of them moved to a different document
+ // subtree in the meantime and therefore aren't actually going away.
+ for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
+ mPendingChildLoaders[j]->RemoveParserBlockingScriptExecutionBlocker();
+ }
+}
+
+// Helper method for checking if the script element is an event-handler
+// This means that it has both a for-attribute and a event-attribute.
+// Also, if the for-attribute has a value that matches "\s*window\s*",
+// and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
+// eventhandler. (both matches are case insensitive).
+// This is how IE seems to filter out a window's onload handler from a
+// <script for=... event=...> element.
+
+static bool
+IsScriptEventHandler(nsIContent* aScriptElement)
+{
+ if (!aScriptElement->IsHTMLElement()) {
+ return false;
+ }
+
+ nsAutoString forAttr, eventAttr;
+ if (!aScriptElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, forAttr) ||
+ !aScriptElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, eventAttr)) {
+ return false;
+ }
+
+ const nsAString& for_str =
+ nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr);
+ if (!for_str.LowerCaseEqualsLiteral("window")) {
+ return true;
+ }
+
+ // We found for="window", now check for event="onload".
+ const nsAString& event_str =
+ nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false);
+ if (!StringBeginsWith(event_str, NS_LITERAL_STRING("onload"),
+ nsCaseInsensitiveStringComparator())) {
+ // It ain't "onload.*".
+
+ return true;
+ }
+
+ nsAutoString::const_iterator start, end;
+ event_str.BeginReading(start);
+ event_str.EndReading(end);
+
+ start.advance(6); // advance past "onload"
+
+ if (start != end && *start != '(' && *start != ' ') {
+ // We got onload followed by something other than space or
+ // '('. Not good enough.
+
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsScriptLoader::CheckContentPolicy(nsIDocument* aDocument,
+ nsISupports *aContext,
+ nsIURI *aURI,
+ const nsAString &aType,
+ bool aIsPreLoad)
+{
+ nsContentPolicyType contentPolicyType = aIsPreLoad
+ ? nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
+ : nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ nsresult rv = NS_CheckContentLoadPolicy(contentPolicyType,
+ aURI,
+ aDocument->NodePrincipal(),
+ aContext,
+ NS_LossyConvertUTF16toASCII(aType),
+ nullptr, //extra
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsScriptLoader::ModuleMapContainsModule(nsModuleLoadRequest *aRequest) const
+{
+ // Returns whether we have fetched, or are currently fetching, a module script
+ // for the request's URL.
+ return mFetchingModules.Contains(aRequest->mURI) ||
+ mFetchedModules.Contains(aRequest->mURI);
+}
+
+bool
+nsScriptLoader::IsFetchingModule(nsModuleLoadRequest *aRequest) const
+{
+ bool fetching = mFetchingModules.Contains(aRequest->mURI);
+ MOZ_ASSERT_IF(fetching, !mFetchedModules.Contains(aRequest->mURI));
+ return fetching;
+}
+
+void
+nsScriptLoader::SetModuleFetchStarted(nsModuleLoadRequest *aRequest)
+{
+ // Update the module map to indicate that a module is currently being fetched.
+
+ MOZ_ASSERT(aRequest->IsLoading());
+ MOZ_ASSERT(!ModuleMapContainsModule(aRequest));
+ mFetchingModules.Put(aRequest->mURI, nullptr);
+}
+
+void
+nsScriptLoader::SetModuleFetchFinishedAndResumeWaitingRequests(nsModuleLoadRequest *aRequest,
+ nsresult aResult)
+{
+ // Update module map with the result of fetching a single module script. The
+ // module script pointer is nullptr on error.
+
+ MOZ_ASSERT(!aRequest->IsReadyToRun());
+
+ RefPtr<GenericPromise::Private> promise;
+ MOZ_ALWAYS_TRUE(mFetchingModules.Get(aRequest->mURI, getter_AddRefs(promise)));
+ mFetchingModules.Remove(aRequest->mURI);
+
+ RefPtr<nsModuleScript> ms(aRequest->mModuleScript);
+ MOZ_ASSERT(NS_SUCCEEDED(aResult) == (ms != nullptr));
+ mFetchedModules.Put(aRequest->mURI, ms);
+
+ if (promise) {
+ if (ms) {
+ promise->Resolve(true, __func__);
+ } else {
+ promise->Reject(aResult, __func__);
+ }
+ }
+}
+
+RefPtr<GenericPromise>
+nsScriptLoader::WaitForModuleFetch(nsModuleLoadRequest *aRequest)
+{
+ MOZ_ASSERT(ModuleMapContainsModule(aRequest));
+
+ RefPtr<GenericPromise::Private> promise;
+ if (mFetchingModules.Get(aRequest->mURI, getter_AddRefs(promise))) {
+ if (!promise) {
+ promise = new GenericPromise::Private(__func__);
+ mFetchingModules.Put(aRequest->mURI, promise);
+ }
+ return promise;
+ }
+
+ RefPtr<nsModuleScript> ms;
+ MOZ_ALWAYS_TRUE(mFetchedModules.Get(aRequest->mURI, getter_AddRefs(ms)));
+ if (!ms) {
+ return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ return GenericPromise::CreateAndResolve(true, __func__);
+}
+
+nsModuleScript*
+nsScriptLoader::GetFetchedModule(nsIURI* aURL) const
+{
+ bool found;
+ nsModuleScript* ms = mFetchedModules.GetWeak(aURL, &found);
+ MOZ_ASSERT(found);
+ return ms;
+}
+
+nsresult
+nsScriptLoader::ProcessFetchedModuleSource(nsModuleLoadRequest* aRequest)
+{
+ MOZ_ASSERT(!aRequest->mModuleScript);
+
+ nsresult rv = CreateModuleScript(aRequest);
+ SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
+
+ free(aRequest->mScriptTextBuf);
+ aRequest->mScriptTextBuf = nullptr;
+ aRequest->mScriptTextLength = 0;
+
+ if (NS_SUCCEEDED(rv)) {
+ StartFetchingModuleDependencies(aRequest);
+ }
+
+ return rv;
+}
+
+nsresult
+nsScriptLoader::CreateModuleScript(nsModuleLoadRequest* aRequest)
+{
+ MOZ_ASSERT(!aRequest->mModuleScript);
+ MOZ_ASSERT(aRequest->mBaseURL);
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ if (!globalObject) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoMicroTask mt;
+ AutoEntryScript aes(globalObject, "CompileModule", true);
+
+ bool oldProcessingScriptTag = context->GetProcessingScriptTag();
+ context->SetProcessingScriptTag(true);
+
+ nsresult rv;
+ {
+ // Update our current script.
+ AutoCurrentScriptUpdater scriptUpdater(this, aRequest->mElement);
+ Maybe<AutoCurrentScriptUpdater> masterScriptUpdater;
+ nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
+ if (master != mDocument) {
+ masterScriptUpdater.emplace(master->ScriptLoader(),
+ aRequest->mElement);
+ }
+
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> module(cx);
+
+ if (aRequest->mWasCompiledOMT) {
+ module = JS::FinishOffThreadModule(cx, aRequest->mOffThreadToken);
+ aRequest->mOffThreadToken = nullptr;
+ rv = module ? NS_OK : NS_ERROR_FAILURE;
+ } else {
+ JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
+
+ JS::CompileOptions options(cx);
+ rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString inlineData;
+ SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
+ rv = nsJSUtils::CompileModule(cx, srcBuf, global, options, &module);
+ }
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
+ if (module) {
+ aRequest->mModuleScript =
+ new nsModuleScript(this, aRequest->mBaseURL, module);
+ }
+ }
+
+ context->SetProcessingScriptTag(oldProcessingScriptTag);
+
+ return rv;
+}
+
+static bool
+ThrowTypeError(JSContext* aCx, nsModuleScript* aScript,
+ const nsString& aMessage)
+{
+ JS::Rooted<JSObject*> module(aCx, aScript->ModuleRecord());
+ JS::Rooted<JSScript*> script(aCx, JS::GetModuleScript(aCx, module));
+ JS::Rooted<JSString*> filename(aCx);
+ filename = JS_NewStringCopyZ(aCx, JS_GetScriptFilename(script));
+ if (!filename) {
+ return false;
+ }
+
+ JS::Rooted<JSString*> message(aCx, JS_NewUCStringCopyZ(aCx, aMessage.get()));
+ if (!message) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> error(aCx);
+ if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0, 0, nullptr,
+ message, &error)) {
+ return false;
+ }
+
+ JS_SetPendingException(aCx, error);
+ return false;
+}
+
+static bool
+HandleResolveFailure(JSContext* aCx, nsModuleScript* aScript,
+ const nsAString& aSpecifier)
+{
+ // TODO: How can we get the line number of the failed import?
+
+ nsAutoString message(NS_LITERAL_STRING("Error resolving module specifier: "));
+ message.Append(aSpecifier);
+
+ return ThrowTypeError(aCx, aScript, message);
+}
+
+static bool
+HandleModuleNotFound(JSContext* aCx, nsModuleScript* aScript,
+ const nsAString& aSpecifier)
+{
+ // TODO: How can we get the line number of the failed import?
+
+ nsAutoString message(NS_LITERAL_STRING("Resolved module not found in map: "));
+ message.Append(aSpecifier);
+
+ return ThrowTypeError(aCx, aScript, message);
+}
+
+static already_AddRefed<nsIURI>
+ResolveModuleSpecifier(nsModuleScript* aScript,
+ const nsAString& aSpecifier)
+{
+ // The following module specifiers are allowed by the spec:
+ // - a valid absolute URL
+ // - a valid relative URL that starts with "/", "./" or "../"
+ //
+ // Bareword module specifiers are currently disallowed as these may be given
+ // special meanings in the future.
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
+ if (NS_SUCCEEDED(rv)) {
+ return uri.forget();
+ }
+
+ if (rv != NS_ERROR_MALFORMED_URI) {
+ return nullptr;
+ }
+
+ if (!StringBeginsWith(aSpecifier, NS_LITERAL_STRING("/")) &&
+ !StringBeginsWith(aSpecifier, NS_LITERAL_STRING("./")) &&
+ !StringBeginsWith(aSpecifier, NS_LITERAL_STRING("../"))) {
+ return nullptr;
+ }
+
+ rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, aScript->BaseURL());
+ if (NS_SUCCEEDED(rv)) {
+ return uri.forget();
+ }
+
+ return nullptr;
+}
+
+static nsresult
+RequestedModuleIsInAncestorList(nsModuleLoadRequest* aRequest, nsIURI* aURL, bool* aResult)
+{
+ const size_t ImportDepthLimit = 100;
+
+ *aResult = false;
+ size_t depth = 0;
+ while (aRequest) {
+ if (depth++ == ImportDepthLimit) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool equal;
+ nsresult rv = aURL->Equals(aRequest->mURI, &equal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (equal) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ aRequest = aRequest->mParent;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+ResolveRequestedModules(nsModuleLoadRequest* aRequest, nsCOMArray<nsIURI> &aUrls)
+{
+ nsModuleScript* ms = aRequest->mModuleScript;
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(ms->ModuleRecord())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> moduleRecord(cx, ms->ModuleRecord());
+ JS::Rooted<JSObject*> specifiers(cx, JS::GetRequestedModules(cx, moduleRecord));
+
+ uint32_t length;
+ if (!JS_GetArrayLength(cx, specifiers, &length)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ for (uint32_t i = 0; i < length; i++) {
+ if (!JS_GetElement(cx, specifiers, i, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoJSString specifier;
+ if (!specifier.init(cx, val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Let url be the result of resolving a module specifier given module script and requested.
+ nsModuleScript* ms = aRequest->mModuleScript;
+ nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(ms, specifier);
+ if (!uri) {
+ HandleResolveFailure(cx, ms, specifier);
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isAncestor;
+ nsresult rv = RequestedModuleIsInAncestorList(aRequest, uri, &isAncestor);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isAncestor) {
+ aUrls.AppendElement(uri.forget());
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsScriptLoader::StartFetchingModuleDependencies(nsModuleLoadRequest* aRequest)
+{
+ MOZ_ASSERT(aRequest->mModuleScript);
+ MOZ_ASSERT(!aRequest->IsReadyToRun());
+ aRequest->mProgress = nsModuleLoadRequest::Progress::FetchingImports;
+
+ nsCOMArray<nsIURI> urls;
+ nsresult rv = ResolveRequestedModules(aRequest, urls);
+ if (NS_FAILED(rv)) {
+ aRequest->LoadFailed();
+ return;
+ }
+
+ if (urls.Length() == 0) {
+ // There are no descendents to load so this request is ready.
+ aRequest->DependenciesLoaded();
+ return;
+ }
+
+ // For each url in urls, fetch a module script tree given url, module script's
+ // CORS setting, and module script's settings object.
+ nsTArray<RefPtr<GenericPromise>> importsReady;
+ for (size_t i = 0; i < urls.Length(); i++) {
+ RefPtr<GenericPromise> childReady =
+ StartFetchingModuleAndDependencies(aRequest, urls[i]);
+ importsReady.AppendElement(childReady);
+ }
+
+ // Wait for all imports to become ready.
+ RefPtr<GenericPromise::AllPromiseType> allReady =
+ GenericPromise::All(AbstractThread::GetCurrent(), importsReady);
+ allReady->Then(AbstractThread::GetCurrent(), __func__, aRequest,
+ &nsModuleLoadRequest::DependenciesLoaded,
+ &nsModuleLoadRequest::LoadFailed);
+}
+
+RefPtr<GenericPromise>
+nsScriptLoader::StartFetchingModuleAndDependencies(nsModuleLoadRequest* aRequest,
+ nsIURI* aURI)
+{
+ MOZ_ASSERT(aURI);
+
+ RefPtr<nsModuleLoadRequest> childRequest =
+ new nsModuleLoadRequest(aRequest->mElement, aRequest->mJSVersion,
+ aRequest->mCORSMode, aRequest->mIntegrity, this);
+
+ childRequest->mIsTopLevel = false;
+ childRequest->mURI = aURI;
+ childRequest->mIsInline = false;
+ childRequest->mReferrerPolicy = aRequest->mReferrerPolicy;
+ childRequest->mParent = aRequest;
+
+ RefPtr<GenericPromise> ready = childRequest->mReady.Ensure(__func__);
+
+ nsresult rv = StartLoad(childRequest, NS_LITERAL_STRING("module"), false);
+ if (NS_FAILED(rv)) {
+ childRequest->mReady.Reject(rv, __func__);
+ return ready;
+ }
+
+ aRequest->mImports.AppendElement(childRequest);
+ return ready;
+}
+
+bool
+HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp)
+{
+ MOZ_ASSERT(argc == 2);
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::Rooted<JSObject*> module(aCx, &args[0].toObject());
+ JS::Rooted<JSString*> specifier(aCx, args[1].toString());
+
+ // Let referencing module script be referencingModule.[[HostDefined]].
+ JS::Value value = JS::GetModuleHostDefinedField(module);
+ auto script = static_cast<nsModuleScript*>(value.toPrivate());
+ MOZ_ASSERT(script->ModuleRecord() == module);
+
+ // Let url be the result of resolving a module specifier given referencing
+ // module script and specifier. If the result is failure, throw a TypeError
+ // exception and abort these steps.
+ nsAutoJSString string;
+ if (!string.init(aCx, specifier)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(script, string);
+ if (!uri) {
+ return HandleResolveFailure(aCx, script, string);
+ }
+
+ // Let resolved module script be the value of the entry in module map whose
+ // key is url. If no such entry exists, throw a TypeError exception and abort
+ // these steps.
+ nsModuleScript* ms = script->Loader()->GetFetchedModule(uri);
+ if (!ms) {
+ return HandleModuleNotFound(aCx, script, string);
+ }
+
+ if (ms->InstantiationFailed()) {
+ JS::Rooted<JS::Value> exception(aCx, ms->Exception());
+ JS_SetPendingException(aCx, exception);
+ return false;
+ }
+
+ *vp = JS::ObjectValue(*ms->ModuleRecord());
+ return true;
+}
+
+static nsresult
+EnsureModuleResolveHook(JSContext* aCx)
+{
+ if (JS::GetModuleResolveHook(aCx)) {
+ return NS_OK;
+ }
+
+ JS::Rooted<JSFunction*> func(aCx);
+ func = JS_NewFunction(aCx, HostResolveImportedModule, 2, 0,
+ "HostResolveImportedModule");
+ if (!func) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::SetModuleResolveHook(aCx, func);
+ return NS_OK;
+}
+
+void
+nsScriptLoader::ProcessLoadedModuleTree(nsModuleLoadRequest* aRequest)
+{
+ if (aRequest->IsTopLevel()) {
+ MaybeMoveToLoadedList(aRequest);
+ ProcessPendingRequests();
+ }
+
+ if (aRequest->mWasCompiledOMT) {
+ mDocument->UnblockOnload(false);
+ }
+}
+
+bool
+nsScriptLoader::InstantiateModuleTree(nsModuleLoadRequest* aRequest)
+{
+ // Perform eager instantiation of the loaded module tree.
+
+ MOZ_ASSERT(aRequest);
+
+ nsModuleScript* ms = aRequest->mModuleScript;
+ MOZ_ASSERT(ms);
+ if (!ms->ModuleRecord()) {
+ return false;
+ }
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(ms->ModuleRecord()))) {
+ return false;
+ }
+
+ nsresult rv = EnsureModuleResolveHook(jsapi.cx());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ JS::Rooted<JSObject*> module(jsapi.cx(), ms->ModuleRecord());
+ bool ok = NS_SUCCEEDED(nsJSUtils::ModuleDeclarationInstantiation(jsapi.cx(), module));
+
+ JS::RootedValue exception(jsapi.cx());
+ if (!ok) {
+ MOZ_ASSERT(jsapi.HasException());
+ if (!jsapi.StealException(&exception)) {
+ return false;
+ }
+ MOZ_ASSERT(!exception.isUndefined());
+ }
+
+ // Mark this module and any uninstantiated dependencies found via depth-first
+ // search as instantiated and record any error.
+
+ mozilla::Vector<nsModuleLoadRequest*, 1> requests;
+ if (!requests.append(aRequest)) {
+ return false;
+ }
+
+ while (!requests.empty()) {
+ nsModuleLoadRequest* request = requests.popCopy();
+ nsModuleScript* ms = request->mModuleScript;
+ if (!ms->IsUninstantiated()) {
+ continue;
+ }
+
+ ms->SetInstantiationResult(exception);
+
+ for (auto import : request->mImports) {
+ if (import->mModuleScript->IsUninstantiated() &&
+ !requests.append(import))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+nsresult
+nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
+ bool aScriptFromHead)
+{
+ MOZ_ASSERT(aRequest->IsLoading());
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
+
+ // If this document is sandboxed without 'allow-scripts', abort.
+ if (mDocument->HasScriptsBlockedBySandbox()) {
+ return NS_OK;
+ }
+
+ if (aRequest->IsModuleRequest()) {
+ // Check whether the module has been fetched or is currently being fetched,
+ // and if so wait for it.
+ nsModuleLoadRequest* request = aRequest->AsModuleRequest();
+ if (ModuleMapContainsModule(request)) {
+ WaitForModuleFetch(request)
+ ->Then(AbstractThread::GetCurrent(), __func__, request,
+ &nsModuleLoadRequest::ModuleLoaded,
+ &nsModuleLoadRequest::LoadFailed);
+ return NS_OK;
+ }
+
+ // Otherwise put the URL in the module map and mark it as fetching.
+ SetModuleFetchStarted(request);
+ }
+
+ nsContentPolicyType contentPolicyType = aRequest->IsPreload()
+ ? nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
+ : nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
+ nsCOMPtr<nsINode> context;
+ if (aRequest->mElement) {
+ context = do_QueryInterface(aRequest->mElement);
+ }
+ else {
+ context = mDocument;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->MasterDocument()->GetWindow();
+ NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER);
+ nsIDocShell *docshell = window->GetDocShell();
+ nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
+
+ nsSecurityFlags securityFlags;
+ // TODO: the spec currently gives module scripts different CORS behaviour to
+ // classic scripts.
+ securityFlags = aRequest->mCORSMode == CORS_NONE
+ ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
+ : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+ if (aRequest->mCORSMode == CORS_ANONYMOUS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
+ } else if (aRequest->mCORSMode == CORS_USE_CREDENTIALS) {
+ securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ }
+ securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+ aRequest->mURI,
+ context,
+ securityFlags,
+ contentPolicyType,
+ loadGroup,
+ prompter,
+ nsIRequest::LOAD_NORMAL |
+ nsIChannel::LOAD_CLASSIFY_URI);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIScriptElement *script = aRequest->mElement;
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
+
+ if (cos) {
+ if (aScriptFromHead &&
+ !(script && (script->GetScriptAsync() || script->GetScriptDeferred()))) {
+ // synchronous head scripts block lading of most other non js/css
+ // content such as images
+ cos->AddClassFlags(nsIClassOfService::Leader);
+ } else if (!(script && script->GetScriptDeferred())) {
+ // other scripts are neither blocked nor prioritized unless marked deferred
+ cos->AddClassFlags(nsIClassOfService::Unblocked);
+ }
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ // HTTP content negotation has little value in this context.
+ httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ NS_LITERAL_CSTRING("*/*"),
+ false);
+ httpChannel->SetReferrerWithPolicy(mDocument->GetDocumentURI(),
+ aRequest->mReferrerPolicy);
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel(do_QueryInterface(httpChannel));
+ if (internalChannel) {
+ internalChannel->SetIntegrityMetadata(aRequest->mIntegrity.GetIntegrityString());
+ }
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(docshell));
+ mozilla::net::PredictorLearn(aRequest->mURI, mDocument->GetDocumentURI(),
+ nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, loadContext);
+
+ // Set the initiator type
+ nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
+ if (timedChannel) {
+ timedChannel->SetInitiatorType(NS_LITERAL_STRING("script"));
+ }
+
+ nsAutoPtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier;
+ if (!aRequest->mIntegrity.IsEmpty()) {
+ nsAutoCString sourceUri;
+ if (mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ sriDataVerifier = new SRICheckDataVerifier(aRequest->mIntegrity, sourceUri,
+ mReporter);
+ }
+
+ RefPtr<nsScriptLoadHandler> handler =
+ new nsScriptLoadHandler(this, aRequest, sriDataVerifier.forget());
+
+ nsCOMPtr<nsIIncrementalStreamLoader> loader;
+ rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return channel->AsyncOpen2(loader);
+}
+
+bool
+nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
+ nsIURI * const &aURI) const
+{
+ bool same;
+ return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
+ same;
+}
+
+class nsScriptRequestProcessor : public Runnable
+{
+private:
+ RefPtr<nsScriptLoader> mLoader;
+ RefPtr<nsScriptLoadRequest> mRequest;
+public:
+ nsScriptRequestProcessor(nsScriptLoader* aLoader,
+ nsScriptLoadRequest* aRequest)
+ : mLoader(aLoader)
+ , mRequest(aRequest)
+ {}
+ NS_IMETHOD Run() override
+ {
+ return mLoader->ProcessRequest(mRequest);
+ }
+};
+
+static inline bool
+ParseTypeAttribute(const nsAString& aType, JSVersion* aVersion)
+{
+ MOZ_ASSERT(!aType.IsEmpty());
+ MOZ_ASSERT(aVersion);
+ MOZ_ASSERT(*aVersion == JSVERSION_DEFAULT);
+
+ nsContentTypeParser parser(aType);
+
+ nsAutoString mimeType;
+ nsresult rv = parser.GetType(mimeType);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (!nsContentUtils::IsJavascriptMIMEType(mimeType)) {
+ return false;
+ }
+
+ // Get the version string, and ensure the language supports it.
+ nsAutoString versionName;
+ rv = parser.GetParameter("version", versionName);
+
+ if (NS_SUCCEEDED(rv)) {
+ *aVersion = nsContentUtils::ParseJavascriptVersion(versionName);
+ } else if (rv != NS_ERROR_INVALID_ARG) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument)
+{
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ // Note: For imports NodePrincipal and the principal of the master are
+ // the same.
+ nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (!csp) {
+ // no CSP --> allow
+ return true;
+ }
+
+ // query the nonce
+ nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
+ nsAutoString nonce;
+ scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
+ bool parserCreated = aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER;
+
+ // query the scripttext
+ nsAutoString scriptText;
+ aElement->GetScriptText(scriptText);
+
+ bool allowInlineScript = false;
+ rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
+ nonce, parserCreated, scriptText,
+ aElement->GetScriptLineNumber(),
+ &allowInlineScript);
+ return allowInlineScript;
+}
+
+nsScriptLoadRequest*
+nsScriptLoader::CreateLoadRequest(nsScriptKind aKind,
+ nsIScriptElement* aElement,
+ uint32_t aVersion, CORSMode aCORSMode,
+ const SRIMetadata &aIntegrity)
+{
+ if (aKind == nsScriptKind::Classic) {
+ return new nsScriptLoadRequest(aKind, aElement, aVersion, aCORSMode,
+ aIntegrity);
+ }
+
+ MOZ_ASSERT(aKind == nsScriptKind::Module);
+ return new nsModuleLoadRequest(aElement, aVersion, aCORSMode, aIntegrity,
+ this);
+}
+
+bool
+nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
+{
+ // We need a document to evaluate scripts.
+ NS_ENSURE_TRUE(mDocument, false);
+
+ // Check to see if scripts has been turned off.
+ if (!mEnabled || !mDocument->IsScriptEnabled()) {
+ return false;
+ }
+
+ NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
+
+ nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
+
+ // Step 12. Check that the script is not an eventhandler
+ if (IsScriptEventHandler(scriptContent)) {
+ return false;
+ }
+
+ JSVersion version = JSVERSION_DEFAULT;
+
+ // Check the type attribute to determine language and version.
+ // If type exists, it trumps the deprecated 'language='
+ nsAutoString type;
+ bool hasType = aElement->GetScriptType(type);
+
+ nsScriptKind scriptKind = nsScriptKind::Classic;
+ if (!type.IsEmpty()) {
+ // Support type="module" only for chrome documents.
+ if (nsContentUtils::IsChromeDoc(mDocument) && type.LowerCaseEqualsASCII("module")) {
+ scriptKind = nsScriptKind::Module;
+ } else {
+ NS_ENSURE_TRUE(ParseTypeAttribute(type, &version), false);
+ }
+ } else if (!hasType) {
+ // no 'type=' element
+ // "language" is a deprecated attribute of HTML, so we check it only for
+ // HTML script elements.
+ if (scriptContent->IsHTMLElement()) {
+ nsAutoString language;
+ scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::language, language);
+ if (!language.IsEmpty()) {
+ if (!nsContentUtils::IsJavaScriptLanguage(language)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ // Step 14. in the HTML5 spec
+ nsresult rv = NS_OK;
+ RefPtr<nsScriptLoadRequest> request;
+ if (aElement->GetScriptExternal()) {
+ // external script
+ nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
+ if (!scriptURI) {
+ // Asynchronously report the failure to create a URI object
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod(aElement,
+ &nsIScriptElement::FireErrorEvent));
+ return false;
+ }
+
+ // Double-check that the preload matches what we're asked to load now.
+ mozilla::net::ReferrerPolicy ourRefPolicy = mDocument->GetReferrerPolicy();
+ CORSMode ourCORSMode = aElement->GetCORSMode();
+ nsTArray<PreloadInfo>::index_type i =
+ mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator());
+ if (i != nsTArray<PreloadInfo>::NoIndex) {
+ // preloaded
+ // note that a script-inserted script can steal a preload!
+ request = mPreloads[i].mRequest;
+ request->mElement = aElement;
+ nsString preloadCharset(mPreloads[i].mCharset);
+ mPreloads.RemoveElementAt(i);
+
+ // Double-check that the charset the preload used is the same as
+ // the charset we have now.
+ nsAutoString elementCharset;
+ aElement->GetScriptCharset(elementCharset);
+ if (elementCharset.Equals(preloadCharset) &&
+ ourCORSMode == request->mCORSMode &&
+ ourRefPolicy == request->mReferrerPolicy &&
+ scriptKind == request->mKind) {
+ rv = CheckContentPolicy(mDocument, aElement, request->mURI, type, false);
+ if (NS_FAILED(rv)) {
+ // probably plans have changed; even though the preload was allowed seems
+ // like the actual load is not; let's cancel the preload request.
+ request->Cancel();
+ return false;
+ }
+ } else {
+ // Drop the preload
+ request = nullptr;
+ }
+ }
+
+ if (!request) {
+ // no usable preload
+
+ SRIMetadata sriMetadata;
+ {
+ nsAutoString integrity;
+ scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
+ integrity);
+ if (!integrity.IsEmpty()) {
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
+ ("nsScriptLoader::ProcessScriptElement, integrity=%s",
+ NS_ConvertUTF16toUTF8(integrity).get()));
+ nsAutoCString sourceUri;
+ if (mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ SRICheck::IntegrityMetadata(integrity, sourceUri, mReporter,
+ &sriMetadata);
+ }
+ }
+
+ request = CreateLoadRequest(scriptKind, aElement, version, ourCORSMode,
+ sriMetadata);
+ request->mURI = scriptURI;
+ request->mIsInline = false;
+ request->mReferrerPolicy = ourRefPolicy;
+
+ // set aScriptFromHead to false so we don't treat non preloaded scripts as
+ // blockers for full page load. See bug 792438.
+ rv = StartLoad(request, type, false);
+ if (NS_FAILED(rv)) {
+ // Asynchronously report the load failure
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod(aElement,
+ &nsIScriptElement::FireErrorEvent));
+ return false;
+ }
+ }
+
+ // Should still be in loading stage of script.
+ NS_ASSERTION(!request->InCompilingStage(),
+ "Request should not yet be in compiling stage.");
+
+ request->mJSVersion = version;
+
+ if (aElement->GetScriptAsync()) {
+ request->mIsAsync = true;
+ if (request->IsReadyToRun()) {
+ mLoadedAsyncRequests.AppendElement(request);
+ // The script is available already. Run it ASAP when the event
+ // loop gets a chance to spin.
+
+ // KVKV TODO: Instead of processing immediately, try off-thread-parsing
+ // it and only schedule a pending ProcessRequest if that fails.
+ ProcessPendingRequestsAsync();
+ } else {
+ mLoadingAsyncRequests.AppendElement(request);
+ }
+ return false;
+ }
+ if (!aElement->GetParserCreated() && !request->IsModuleRequest()) {
+ // Violate the HTML5 spec in order to make LABjs and the "order" plug-in
+ // for RequireJS work with their Gecko-sniffed code path. See
+ // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
+ request->mIsNonAsyncScriptInserted = true;
+ mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
+ if (request->IsReadyToRun()) {
+ // The script is available already. Run it ASAP when the event
+ // loop gets a chance to spin.
+ ProcessPendingRequestsAsync();
+ }
+ return false;
+ }
+ // we now have a parser-inserted request that may or may not be still
+ // loading
+ if (aElement->GetScriptDeferred() || request->IsModuleRequest()) {
+ // We don't want to run this yet.
+ // If we come here, the script is a parser-created script and it has
+ // the defer attribute but not the async attribute. Since a
+ // a parser-inserted script is being run, we came here by the parser
+ // running the script, which means the parser is still alive and the
+ // parse is ongoing.
+ NS_ASSERTION(mDocument->GetCurrentContentSink() ||
+ aElement->GetParserCreated() == FROM_PARSER_XSLT,
+ "Non-XSLT Defer script on a document without an active parser; bug 592366.");
+ AddDeferRequest(request);
+ return false;
+ }
+
+ if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
+ // Need to maintain order for XSLT-inserted scripts
+ NS_ASSERTION(!mParserBlockingRequest,
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ request->mIsXSLT = true;
+ mXSLTRequests.AppendElement(request);
+ if (request->IsReadyToRun()) {
+ // The script is available already. Run it ASAP when the event
+ // loop gets a chance to spin.
+ ProcessPendingRequestsAsync();
+ }
+ return true;
+ }
+
+ if (request->IsReadyToRun() && ReadyToExecuteParserBlockingScripts()) {
+ // The request has already been loaded and there are no pending style
+ // sheets. If the script comes from the network stream, cheat for
+ // performance reasons and avoid a trip through the event loop.
+ if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
+ return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
+ }
+ // Otherwise, we've got a document.written script, make a trip through
+ // the event loop to hide the preload effects from the scripts on the
+ // Web page.
+ NS_ASSERTION(!mParserBlockingRequest,
+ "There can be only one parser-blocking script at a time");
+ NS_ASSERTION(mXSLTRequests.isEmpty(),
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ mParserBlockingRequest = request;
+ ProcessPendingRequestsAsync();
+ return true;
+ }
+
+ // The script hasn't loaded yet or there's a style sheet blocking it.
+ // The script will be run when it loads or the style sheet loads.
+ NS_ASSERTION(!mParserBlockingRequest,
+ "There can be only one parser-blocking script at a time");
+ NS_ASSERTION(mXSLTRequests.isEmpty(),
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ mParserBlockingRequest = request;
+ return true;
+ }
+
+ // inline script
+ // Is this document sandboxed without 'allow-scripts'?
+ if (mDocument->HasScriptsBlockedBySandbox()) {
+ return false;
+ }
+
+ // Does CSP allow this inline script to run?
+ if (!CSPAllowsInlineScript(aElement, mDocument)) {
+ return false;
+ }
+
+ // Inline scripts ignore ther CORS mode and are always CORS_NONE
+ request = CreateLoadRequest(scriptKind, aElement, version, CORS_NONE,
+ SRIMetadata()); // SRI doesn't apply
+ request->mJSVersion = version;
+ request->mIsInline = true;
+ request->mURI = mDocument->GetDocumentURI();
+ request->mLineNo = aElement->GetScriptLineNumber();
+
+ if (request->IsModuleRequest()) {
+ nsModuleLoadRequest* modReq = request->AsModuleRequest();
+ modReq->mBaseURL = mDocument->GetDocBaseURI();
+ rv = CreateModuleScript(modReq);
+ NS_ENSURE_SUCCESS(rv, false);
+ StartFetchingModuleDependencies(modReq);
+ if (aElement->GetScriptAsync()) {
+ mLoadingAsyncRequests.AppendElement(request);
+ } else {
+ AddDeferRequest(request);
+ }
+ return false;
+ }
+ request->mProgress = nsScriptLoadRequest::Progress::Ready;
+ if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
+ (!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
+ // Need to maintain order for XSLT-inserted scripts
+ NS_ASSERTION(!mParserBlockingRequest,
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ mXSLTRequests.AppendElement(request);
+ return true;
+ }
+ if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "A script-inserted script is inserted without an update batch?");
+ nsContentUtils::AddScriptRunner(new nsScriptRequestProcessor(this,
+ request));
+ return false;
+ }
+ if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
+ !ReadyToExecuteParserBlockingScripts()) {
+ NS_ASSERTION(!mParserBlockingRequest,
+ "There can be only one parser-blocking script at a time");
+ mParserBlockingRequest = request;
+ NS_ASSERTION(mXSLTRequests.isEmpty(),
+ "Parser-blocking scripts and XSLT scripts in the same doc!");
+ return true;
+ }
+ // We now have a document.written inline script or we have an inline script
+ // from the network but there is no style sheet that is blocking scripts.
+ // Don't check for style sheets blocking scripts in the document.write
+ // case to avoid style sheet network activity affecting when
+ // document.write returns. It's not really necessary to do this if
+ // there's no document.write currently on the call stack. However,
+ // this way matches IE more closely than checking if document.write
+ // is on the call stack.
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Not safe to run a parser-inserted script?");
+ return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
+}
+
+namespace {
+
+class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable
+{
+ RefPtr<nsScriptLoadRequest> mRequest;
+ RefPtr<nsScriptLoader> mLoader;
+ void *mToken;
+
+public:
+ NotifyOffThreadScriptLoadCompletedRunnable(nsScriptLoadRequest* aRequest,
+ nsScriptLoader* aLoader)
+ : mRequest(aRequest), mLoader(aLoader), mToken(nullptr)
+ {}
+
+ virtual ~NotifyOffThreadScriptLoadCompletedRunnable();
+
+ void SetToken(void* aToken) {
+ MOZ_ASSERT(aToken && !mToken);
+ mToken = aToken;
+ }
+
+ NS_DECL_NSIRUNNABLE
+};
+
+} /* anonymous namespace */
+
+nsresult
+nsScriptLoader::ProcessOffThreadRequest(nsScriptLoadRequest* aRequest)
+{
+ MOZ_ASSERT(aRequest->mProgress == nsScriptLoadRequest::Progress::Compiling);
+ MOZ_ASSERT(!aRequest->mWasCompiledOMT);
+
+ aRequest->mWasCompiledOMT = true;
+
+ if (aRequest->IsModuleRequest()) {
+ MOZ_ASSERT(aRequest->mOffThreadToken);
+ nsModuleLoadRequest* request = aRequest->AsModuleRequest();
+ nsresult rv = ProcessFetchedModuleSource(request);
+ if (NS_FAILED(rv)) {
+ request->LoadFailed();
+ }
+ return rv;
+ }
+
+ aRequest->SetReady();
+
+ if (aRequest == mParserBlockingRequest) {
+ if (!ReadyToExecuteParserBlockingScripts()) {
+ // If not ready to execute scripts, schedule an async call to
+ // ProcessPendingRequests to handle it.
+ ProcessPendingRequestsAsync();
+ return NS_OK;
+ }
+
+ // Same logic as in top of ProcessPendingRequests.
+ mParserBlockingRequest = nullptr;
+ UnblockParser(aRequest);
+ ProcessRequest(aRequest);
+ mDocument->UnblockOnload(false);
+ ContinueParserAsync(aRequest);
+ return NS_OK;
+ }
+
+ nsresult rv = ProcessRequest(aRequest);
+ mDocument->UnblockOnload(false);
+ return rv;
+}
+
+NotifyOffThreadScriptLoadCompletedRunnable::~NotifyOffThreadScriptLoadCompletedRunnable()
+{
+ if (MOZ_UNLIKELY(mRequest || mLoader) && !NS_IsMainThread()) {
+ NS_ReleaseOnMainThread(mRequest.forget());
+ NS_ReleaseOnMainThread(mLoader.forget());
+ }
+}
+
+NS_IMETHODIMP
+NotifyOffThreadScriptLoadCompletedRunnable::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We want these to be dropped on the main thread, once we return from this
+ // function.
+ RefPtr<nsScriptLoadRequest> request = mRequest.forget();
+ RefPtr<nsScriptLoader> loader = mLoader.forget();
+
+ request->mOffThreadToken = mToken;
+ nsresult rv = loader->ProcessOffThreadRequest(request);
+
+ return rv;
+}
+
+static void
+OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
+{
+ RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable =
+ dont_AddRef(static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData));
+ aRunnable->SetToken(aToken);
+ NS_DispatchToMainThread(aRunnable);
+}
+
+nsresult
+nsScriptLoader::AttemptAsyncScriptCompile(nsScriptLoadRequest* aRequest)
+{
+ MOZ_ASSERT_IF(!aRequest->IsModuleRequest(), aRequest->IsReadyToRun());
+ MOZ_ASSERT(!aRequest->mWasCompiledOMT);
+
+ // Don't off-thread compile inline scripts.
+ if (aRequest->mIsInline) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ if (!globalObject) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
+ JS::CompileOptions options(cx);
+
+ nsresult rv = FillCompileOptionsForRequest(jsapi, aRequest, global, &options);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!JS::CanCompileOffThread(cx, options, aRequest->mScriptTextLength)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable =
+ new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
+
+ if (aRequest->IsModuleRequest()) {
+ if (!JS::CompileOffThreadModule(cx, options,
+ aRequest->mScriptTextBuf, aRequest->mScriptTextLength,
+ OffThreadScriptLoaderCallback,
+ static_cast<void*>(runnable))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ if (!JS::CompileOffThread(cx, options,
+ aRequest->mScriptTextBuf, aRequest->mScriptTextLength,
+ OffThreadScriptLoaderCallback,
+ static_cast<void*>(runnable))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mDocument->BlockOnload();
+ aRequest->mProgress = nsScriptLoadRequest::Progress::Compiling;
+
+ Unused << runnable.forget();
+ return NS_OK;
+}
+
+nsresult
+nsScriptLoader::CompileOffThreadOrProcessRequest(nsScriptLoadRequest* aRequest)
+{
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Processing requests when running scripts is unsafe.");
+ NS_ASSERTION(!aRequest->mOffThreadToken,
+ "Candidate for off-thread compile is already parsed off-thread");
+ NS_ASSERTION(!aRequest->InCompilingStage(),
+ "Candidate for off-thread compile is already in compiling stage.");
+
+ nsresult rv = AttemptAsyncScriptCompile(aRequest);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ return ProcessRequest(aRequest);
+}
+
+SourceBufferHolder
+nsScriptLoader::GetScriptSource(nsScriptLoadRequest* aRequest, nsAutoString& inlineData)
+{
+ // Return a SourceBufferHolder object holding the script's source text.
+ // |inlineData| is used to hold the text for inline objects.
+
+ // If there's no script text, we try to get it from the element
+ if (aRequest->mIsInline) {
+ // XXX This is inefficient - GetText makes multiple
+ // copies.
+ aRequest->mElement->GetScriptText(inlineData);
+ return SourceBufferHolder(inlineData.get(),
+ inlineData.Length(),
+ SourceBufferHolder::NoOwnership);
+ }
+
+ return SourceBufferHolder(aRequest->mScriptTextBuf,
+ aRequest->mScriptTextLength,
+ SourceBufferHolder::NoOwnership);
+}
+
+nsresult
+nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
+{
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "Processing requests when running scripts is unsafe.");
+ NS_ASSERTION(aRequest->IsReadyToRun(),
+ "Processing a request that is not ready to run.");
+
+ NS_ENSURE_ARG(aRequest);
+
+ if (aRequest->IsModuleRequest() &&
+ !aRequest->AsModuleRequest()->mModuleScript)
+ {
+ // There was an error parsing a module script. Nothing to do here.
+ FireScriptAvailable(NS_ERROR_FAILURE, aRequest);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> scriptElem = do_QueryInterface(aRequest->mElement);
+
+ nsCOMPtr<nsIDocument> doc;
+ if (!aRequest->mIsInline) {
+ doc = scriptElem->OwnerDoc();
+ }
+
+ nsCOMPtr<nsIScriptElement> oldParserInsertedScript;
+ uint32_t parserCreated = aRequest->mElement->GetParserCreated();
+ if (parserCreated) {
+ oldParserInsertedScript = mCurrentParserInsertedScript;
+ mCurrentParserInsertedScript = aRequest->mElement;
+ }
+
+ aRequest->mElement->BeginEvaluating();
+
+ FireScriptAvailable(NS_OK, aRequest);
+
+ // The window may have gone away by this point, in which case there's no point
+ // in trying to run the script.
+ nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
+ {
+ // Try to perform a microtask checkpoint
+ nsAutoMicroTask mt;
+ }
+
+ nsPIDOMWindowInner *pwin = master->GetInnerWindow();
+ bool runScript = !!pwin;
+ if (runScript) {
+ nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
+ scriptElem,
+ NS_LITERAL_STRING("beforescriptexecute"),
+ true, true, &runScript);
+ }
+
+ // Inner window could have gone away after firing beforescriptexecute
+ pwin = master->GetInnerWindow();
+ if (!pwin) {
+ runScript = false;
+ }
+
+ nsresult rv = NS_OK;
+ if (runScript) {
+ if (doc) {
+ doc->BeginEvaluatingExternalScript();
+ }
+ rv = EvaluateScript(aRequest);
+ if (doc) {
+ doc->EndEvaluatingExternalScript();
+ }
+
+ nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
+ scriptElem,
+ NS_LITERAL_STRING("afterscriptexecute"),
+ true, false);
+ }
+
+ FireScriptEvaluated(rv, aRequest);
+
+ aRequest->mElement->EndEvaluating();
+
+ if (parserCreated) {
+ mCurrentParserInsertedScript = oldParserInsertedScript;
+ }
+
+ if (aRequest->mOffThreadToken) {
+ // The request was parsed off-main-thread, but the result of the off
+ // thread parse was not actually needed to process the request
+ // (disappearing window, some other error, ...). Finish the
+ // request to avoid leaks in the JS engine.
+ MOZ_ASSERT(!aRequest->IsModuleRequest());
+ aRequest->MaybeCancelOffThreadScript();
+ }
+
+ // Free any source data.
+ free(aRequest->mScriptTextBuf);
+ aRequest->mScriptTextBuf = nullptr;
+ aRequest->mScriptTextLength = 0;
+
+ return rv;
+}
+
+void
+nsScriptLoader::FireScriptAvailable(nsresult aResult,
+ nsScriptLoadRequest* aRequest)
+{
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
+ obs->ScriptAvailable(aResult, aRequest->mElement,
+ aRequest->mIsInline, aRequest->mURI,
+ aRequest->mLineNo);
+ }
+
+ aRequest->FireScriptAvailable(aResult);
+}
+
+void
+nsScriptLoader::FireScriptEvaluated(nsresult aResult,
+ nsScriptLoadRequest* aRequest)
+{
+ for (int32_t i = 0; i < mObservers.Count(); i++) {
+ nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
+ obs->ScriptEvaluated(aResult, aRequest->mElement,
+ aRequest->mIsInline);
+ }
+
+ aRequest->FireScriptEvaluated(aResult);
+}
+
+already_AddRefed<nsIScriptGlobalObject>
+nsScriptLoader::GetScriptGlobalObject()
+{
+ nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
+ nsPIDOMWindowInner *pwin = master->GetInnerWindow();
+ if (!pwin) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
+ NS_ASSERTION(globalObject, "windows must be global objects");
+
+ // and make sure we are setup for this type of script.
+ nsresult rv = globalObject->EnsureScriptEnvironment();
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return globalObject.forget();
+}
+
+nsresult
+nsScriptLoader::FillCompileOptionsForRequest(const AutoJSAPI&jsapi,
+ nsScriptLoadRequest* aRequest,
+ JS::Handle<JSObject*> aScopeChain,
+ JS::CompileOptions* aOptions)
+{
+ // It's very important to use aRequest->mURI, not the final URI of the channel
+ // aRequest ended up getting script data from, as the script filename.
+ nsresult rv;
+ nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI,
+ aRequest->mURL, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ bool isScriptElement = !aRequest->IsModuleRequest() ||
+ aRequest->AsModuleRequest()->IsTopLevel();
+ aOptions->setIntroductionType(isScriptElement ? "scriptElement"
+ : "importedModule");
+ aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo);
+ aOptions->setVersion(JSVersion(aRequest->mJSVersion));
+ aOptions->setIsRunOnce(true);
+ // We only need the setNoScriptRval bit when compiling off-thread here, since
+ // otherwise nsJSUtils::EvaluateString will set it up for us.
+ aOptions->setNoScriptRval(true);
+ if (aRequest->mHasSourceMapURL) {
+ aOptions->setSourceMapURL(aRequest->mSourceMapURL.get());
+ }
+ if (aRequest->mOriginPrincipal) {
+ nsIPrincipal* scriptPrin = nsContentUtils::ObjectPrincipal(aScopeChain);
+ bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal);
+ aOptions->setMutedErrors(!subsumes);
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> elementVal(cx);
+ MOZ_ASSERT(aRequest->mElement);
+ if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, aRequest->mElement,
+ &elementVal,
+ /* aAllowWrapping = */ true))) {
+ MOZ_ASSERT(elementVal.isObject());
+ aOptions->setElement(&elementVal.toObject());
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest)
+{
+ // We need a document to evaluate scripts.
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->mElement));
+ nsIDocument* ownerDoc = scriptContent->OwnerDoc();
+ if (ownerDoc != mDocument) {
+ // Willful violation of HTML5 as of 2010-12-01
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the script-type to be used by this element.
+ NS_ASSERTION(scriptContent, "no content - what is default script-type?");
+
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
+ if (!globalObject) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure context is a strong reference since we access it after
+ // we've executed a script, which may cause all other references to
+ // the context to go away.
+ nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
+ if (!context) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSVersion version = JSVersion(aRequest->mJSVersion);
+ if (version == JSVERSION_UNKNOWN) {
+ return NS_OK;
+ }
+
+ // New script entry point required, due to the "Create a script" sub-step of
+ // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
+ nsAutoMicroTask mt;
+ AutoEntryScript aes(globalObject, "<script> element", true);
+ JS::Rooted<JSObject*> global(aes.cx(),
+ globalObject->GetGlobalJSObject());
+
+ bool oldProcessingScriptTag = context->GetProcessingScriptTag();
+ context->SetProcessingScriptTag(true);
+ nsresult rv;
+ {
+ // Update our current script.
+ AutoCurrentScriptUpdater scriptUpdater(this, aRequest->mElement);
+ Maybe<AutoCurrentScriptUpdater> masterScriptUpdater;
+ nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
+ if (master != mDocument) {
+ // If this script belongs to an import document, it will be
+ // executed in the context of the master document. During the
+ // execution currentScript of the master should refer to this
+ // script. So let's update the mCurrentScript of the ScriptLoader
+ // of the master document too.
+ masterScriptUpdater.emplace(master->ScriptLoader(),
+ aRequest->mElement);
+ }
+
+ if (aRequest->IsModuleRequest()) {
+ nsModuleLoadRequest* request = aRequest->AsModuleRequest();
+ MOZ_ASSERT(request->mModuleScript);
+ MOZ_ASSERT(!request->mOffThreadToken);
+ nsModuleScript* ms = request->mModuleScript;
+ MOZ_ASSERT(!ms->IsUninstantiated());
+ if (ms->InstantiationFailed()) {
+ JS::Rooted<JS::Value> exception(aes.cx(), ms->Exception());
+ JS_SetPendingException(aes.cx(), exception);
+ rv = NS_ERROR_FAILURE;
+ } else {
+ JS::Rooted<JSObject*> module(aes.cx(), ms->ModuleRecord());
+ MOZ_ASSERT(module);
+ rv = nsJSUtils::ModuleEvaluation(aes.cx(), module);
+ }
+ } else {
+ JS::CompileOptions options(aes.cx());
+ rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString inlineData;
+ SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
+ rv = nsJSUtils::EvaluateString(aes.cx(), srcBuf, global, options,
+ aRequest->OffThreadTokenPtr());
+ }
+ }
+ }
+
+ context->SetProcessingScriptTag(oldProcessingScriptTag);
+ return rv;
+}
+
+void
+nsScriptLoader::ProcessPendingRequestsAsync()
+{
+ if (mParserBlockingRequest ||
+ !mXSLTRequests.isEmpty() ||
+ !mLoadedAsyncRequests.isEmpty() ||
+ !mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
+ !mDeferRequests.isEmpty() ||
+ !mPendingChildLoaders.IsEmpty()) {
+ NS_DispatchToCurrentThread(NewRunnableMethod(this,
+ &nsScriptLoader::ProcessPendingRequests));
+ }
+}
+
+void
+nsScriptLoader::ProcessPendingRequests()
+{
+ RefPtr<nsScriptLoadRequest> request;
+
+ if (mParserBlockingRequest &&
+ mParserBlockingRequest->IsReadyToRun() &&
+ ReadyToExecuteParserBlockingScripts()) {
+ request.swap(mParserBlockingRequest);
+ UnblockParser(request);
+ ProcessRequest(request);
+ if (request->mWasCompiledOMT) {
+ mDocument->UnblockOnload(false);
+ }
+ ContinueParserAsync(request);
+ }
+
+ while (ReadyToExecuteParserBlockingScripts() &&
+ !mXSLTRequests.isEmpty() &&
+ mXSLTRequests.getFirst()->IsReadyToRun()) {
+ request = mXSLTRequests.StealFirst();
+ ProcessRequest(request);
+ }
+
+ while (ReadyToExecuteScripts() && !mLoadedAsyncRequests.isEmpty()) {
+ request = mLoadedAsyncRequests.StealFirst();
+ if (request->IsModuleRequest()) {
+ ProcessRequest(request);
+ } else {
+ CompileOffThreadOrProcessRequest(request);
+ }
+ }
+
+ while (ReadyToExecuteScripts() &&
+ !mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
+ mNonAsyncExternalScriptInsertedRequests.getFirst()->IsReadyToRun()) {
+ // Violate the HTML5 spec and execute these in the insertion order in
+ // order to make LABjs and the "order" plug-in for RequireJS work with
+ // their Gecko-sniffed code path. See
+ // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
+ request = mNonAsyncExternalScriptInsertedRequests.StealFirst();
+ ProcessRequest(request);
+ }
+
+ if (mDocumentParsingDone && mXSLTRequests.isEmpty()) {
+ while (ReadyToExecuteScripts() &&
+ !mDeferRequests.isEmpty() &&
+ mDeferRequests.getFirst()->IsReadyToRun()) {
+ request = mDeferRequests.StealFirst();
+ ProcessRequest(request);
+ }
+ }
+
+ while (!mPendingChildLoaders.IsEmpty() &&
+ ReadyToExecuteParserBlockingScripts()) {
+ RefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
+ mPendingChildLoaders.RemoveElementAt(0);
+ child->RemoveParserBlockingScriptExecutionBlocker();
+ }
+
+ if (mDocumentParsingDone && mDocument && !mParserBlockingRequest &&
+ mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
+ mXSLTRequests.isEmpty() && mDeferRequests.isEmpty() &&
+ MaybeRemovedDeferRequests()) {
+ return ProcessPendingRequests();
+ }
+
+ if (mDocumentParsingDone && mDocument &&
+ !mParserBlockingRequest && mLoadingAsyncRequests.isEmpty() &&
+ mLoadedAsyncRequests.isEmpty() &&
+ mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
+ mXSLTRequests.isEmpty() && mDeferRequests.isEmpty()) {
+ // No more pending scripts; time to unblock onload.
+ // OK to unblock onload synchronously here, since callers must be
+ // prepared for the world changing anyway.
+ mDocumentParsingDone = false;
+ mDocument->UnblockOnload(true);
+ }
+}
+
+bool
+nsScriptLoader::ReadyToExecuteParserBlockingScripts()
+{
+ // Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so
+ // that we don't block twice on an ancestor.
+ if (!SelfReadyToExecuteParserBlockingScripts()) {
+ return false;
+ }
+
+ for (nsIDocument* doc = mDocument; doc; doc = doc->GetParentDocument()) {
+ nsScriptLoader* ancestor = doc->ScriptLoader();
+ if (!ancestor->SelfReadyToExecuteParserBlockingScripts() &&
+ ancestor->AddPendingChildLoader(this)) {
+ AddParserBlockingScriptExecutionBlocker();
+ return false;
+ }
+ }
+
+ if (mDocument && !mDocument->IsMasterDocument()) {
+ RefPtr<ImportManager> im = mDocument->ImportManager();
+ RefPtr<ImportLoader> loader = im->Find(mDocument);
+ MOZ_ASSERT(loader, "How can we have an import document without a loader?");
+
+ // The referring link that counts in the execution order calculation
+ // (in spec: flagged as branch)
+ nsCOMPtr<nsINode> referrer = loader->GetMainReferrer();
+ MOZ_ASSERT(referrer, "There has to be a main referring link for each imports");
+
+ // Import documents are blocked by their import predecessors. We need to
+ // wait with script execution until all the predecessors are done.
+ // Technically it means we have to wait for the last one to finish,
+ // which is the neares one to us in the order.
+ RefPtr<ImportLoader> lastPred = im->GetNearestPredecessor(referrer);
+ if (!lastPred) {
+ // If there is no predecessor we can run.
+ return true;
+ }
+
+ nsCOMPtr<nsIDocument> doc = lastPred->GetDocument();
+ if (lastPred->IsBlocking() || !doc ||
+ !doc->ScriptLoader()->SelfReadyToExecuteParserBlockingScripts()) {
+ // Document has not been created yet or it was created but not ready.
+ // Either case we are blocked by it. The ImportLoader will take care
+ // of blocking us, and adding the pending child loader to the blocking
+ // ScriptLoader when it's possible (at this point the blocking loader
+ // might not have created the document/ScriptLoader)
+ lastPred->AddBlockedScriptLoader(this);
+ // As more imports are parsed, this can change, let's cache what we
+ // blocked, so it can be later updated if needed (see: ImportLoader::Updater).
+ loader->SetBlockingPredecessor(lastPred);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* static */ nsresult
+nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
+ uint32_t aLength, const nsAString& aHintCharset,
+ nsIDocument* aDocument,
+ char16_t*& aBufOut, size_t& aLengthOut)
+{
+ if (!aLength) {
+ aBufOut = nullptr;
+ aLengthOut = 0;
+ return NS_OK;
+ }
+
+ // The encoding info precedence is as follows from high to low:
+ // The BOM
+ // HTTP Content-Type (if name recognized)
+ // charset attribute (if name recognized)
+ // The encoding of the document
+
+ nsAutoCString charset;
+
+ nsCOMPtr<nsIUnicodeDecoder> unicodeDecoder;
+
+ if (nsContentUtils::CheckForBOM(aData, aLength, charset)) {
+ // charset is now one of "UTF-16BE", "UTF-16BE" or "UTF-8". Those decoder
+ // will take care of swallowing the BOM.
+ unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
+ }
+
+ if (!unicodeDecoder &&
+ aChannel &&
+ NS_SUCCEEDED(aChannel->GetContentCharset(charset)) &&
+ EncodingUtils::FindEncodingForLabel(charset, charset)) {
+ unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
+ }
+
+ if (!unicodeDecoder &&
+ EncodingUtils::FindEncodingForLabel(aHintCharset, charset)) {
+ unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
+ }
+
+ if (!unicodeDecoder && aDocument) {
+ charset = aDocument->GetDocumentCharacterSet();
+ unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
+ }
+
+ if (!unicodeDecoder) {
+ // Curiously, there are various callers that don't pass aDocument. The
+ // fallback in the old code was ISO-8859-1, which behaved like
+ // windows-1252. Saying windows-1252 for clarity and for compliance
+ // with the Encoding Standard.
+ unicodeDecoder = EncodingUtils::DecoderForEncoding("windows-1252");
+ }
+
+ int32_t unicodeLength = 0;
+
+ nsresult rv =
+ unicodeDecoder->GetMaxLength(reinterpret_cast<const char*>(aData),
+ aLength, &unicodeLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aBufOut = static_cast<char16_t*>(js_malloc(unicodeLength * sizeof(char16_t)));
+ if (!aBufOut) {
+ aLengthOut = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aLengthOut = unicodeLength;
+
+ rv = unicodeDecoder->Convert(reinterpret_cast<const char*>(aData),
+ (int32_t *) &aLength, aBufOut,
+ &unicodeLength);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ aLengthOut = unicodeLength;
+ if (NS_FAILED(rv)) {
+ js_free(aBufOut);
+ aBufOut = nullptr;
+ aLengthOut = 0;
+ }
+ return rv;
+}
+
+nsresult
+nsScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aChannelStatus,
+ nsresult aSRIStatus,
+ mozilla::Vector<char16_t> &aString,
+ mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier)
+{
+ nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
+ NS_ASSERTION(request, "null request in stream complete handler");
+ NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIRequest> channelRequest;
+ aLoader->GetRequest(getter_AddRefs(channelRequest));
+ nsCOMPtr<nsIChannel> channel;
+ channel = do_QueryInterface(channelRequest);
+
+ nsresult rv = NS_OK;
+ if (!request->mIntegrity.IsEmpty() &&
+ NS_SUCCEEDED((rv = aSRIStatus))) {
+ MOZ_ASSERT(aSRIDataVerifier);
+ MOZ_ASSERT(mReporter);
+
+ nsAutoCString sourceUri;
+ if (mDocument && mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ rv = aSRIDataVerifier->Verify(request->mIntegrity, channel, sourceUri,
+ mReporter);
+ mReporter->FlushConsoleReports(mDocument);
+ if (NS_FAILED(rv)) {
+ rv = NS_ERROR_SRI_CORRUPT;
+ }
+ } else {
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+
+ if (loadInfo->GetEnforceSRI()) {
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
+ ("nsScriptLoader::OnStreamComplete, required SRI not found"));
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
+ nsAutoCString violationURISpec;
+ mDocument->GetDocumentURI()->GetAsciiSpec(violationURISpec);
+ uint32_t lineNo = request->mElement ? request->mElement->GetScriptLineNumber() : 0;
+ csp->LogViolationDetails(
+ nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT,
+ NS_ConvertUTF8toUTF16(violationURISpec),
+ EmptyString(), lineNo, EmptyString(), EmptyString());
+ rv = NS_ERROR_SRI_CORRUPT;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = PrepareLoadedRequest(request, aLoader, aChannelStatus, aString);
+ }
+
+ if (NS_FAILED(rv)) {
+ /*
+ * Handle script not loading error because source was a tracking URL.
+ * We make a note of this script node by including it in a dedicated
+ * array of blocked tracking nodes under its parent document.
+ */
+ if (rv == NS_ERROR_TRACKING_URI) {
+ nsCOMPtr<nsIContent> cont = do_QueryInterface(request->mElement);
+ mDocument->AddBlockedTrackingNode(cont);
+ }
+
+ if (request->mIsDefer) {
+ MOZ_ASSERT_IF(request->IsModuleRequest(),
+ request->AsModuleRequest()->IsTopLevel());
+ if (request->isInList()) {
+ RefPtr<nsScriptLoadRequest> req = mDeferRequests.Steal(request);
+ FireScriptAvailable(rv, req);
+ }
+ } else if (request->mIsAsync) {
+ MOZ_ASSERT_IF(request->IsModuleRequest(),
+ request->AsModuleRequest()->IsTopLevel());
+ if (request->isInList()) {
+ RefPtr<nsScriptLoadRequest> req = mLoadingAsyncRequests.Steal(request);
+ FireScriptAvailable(rv, req);
+ }
+ } else if (request->mIsNonAsyncScriptInserted) {
+ if (request->isInList()) {
+ RefPtr<nsScriptLoadRequest> req =
+ mNonAsyncExternalScriptInsertedRequests.Steal(request);
+ FireScriptAvailable(rv, req);
+ }
+ } else if (request->mIsXSLT) {
+ if (request->isInList()) {
+ RefPtr<nsScriptLoadRequest> req = mXSLTRequests.Steal(request);
+ FireScriptAvailable(rv, req);
+ }
+ } else if (request->IsModuleRequest()) {
+ nsModuleLoadRequest* modReq = request->AsModuleRequest();
+ MOZ_ASSERT(!modReq->IsTopLevel());
+ MOZ_ASSERT(!modReq->isInList());
+ modReq->Cancel();
+ FireScriptAvailable(rv, request);
+ } else if (mParserBlockingRequest == request) {
+ MOZ_ASSERT(!request->isInList());
+ mParserBlockingRequest = nullptr;
+ UnblockParser(request);
+
+ // Ensure that we treat request->mElement as our current parser-inserted
+ // script while firing onerror on it.
+ MOZ_ASSERT(request->mElement->GetParserCreated());
+ nsCOMPtr<nsIScriptElement> oldParserInsertedScript =
+ mCurrentParserInsertedScript;
+ mCurrentParserInsertedScript = request->mElement;
+ FireScriptAvailable(rv, request);
+ ContinueParserAsync(request);
+ mCurrentParserInsertedScript = oldParserInsertedScript;
+ } else {
+ mPreloads.RemoveElement(request, PreloadRequestComparator());
+ }
+ }
+
+ // Process our request and/or any pending ones
+ ProcessPendingRequests();
+
+ return NS_OK;
+}
+
+void
+nsScriptLoader::UnblockParser(nsScriptLoadRequest* aParserBlockingRequest)
+{
+ aParserBlockingRequest->mElement->UnblockParser();
+}
+
+void
+nsScriptLoader::ContinueParserAsync(nsScriptLoadRequest* aParserBlockingRequest)
+{
+ aParserBlockingRequest->mElement->ContinueParserAsync();
+}
+
+uint32_t
+nsScriptLoader::NumberOfProcessors()
+{
+ if (mNumberOfProcessors > 0)
+ return mNumberOfProcessors;
+
+ int32_t numProcs = PR_GetNumberOfProcessors();
+ if (numProcs > 0)
+ mNumberOfProcessors = numProcs;
+ return mNumberOfProcessors;
+}
+
+void
+nsScriptLoader::MaybeMoveToLoadedList(nsScriptLoadRequest* aRequest)
+{
+ MOZ_ASSERT(aRequest->IsReadyToRun());
+
+ // If it's async, move it to the loaded list. aRequest->mIsAsync really
+ // _should_ be in a list, but the consequences if it's not are bad enough we
+ // want to avoid trying to move it if it's not.
+ if (aRequest->mIsAsync) {
+ MOZ_ASSERT(aRequest->isInList());
+ if (aRequest->isInList()) {
+ RefPtr<nsScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
+ mLoadedAsyncRequests.AppendElement(req);
+ }
+ }
+}
+
+nsresult
+nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
+ nsIIncrementalStreamLoader* aLoader,
+ nsresult aStatus,
+ mozilla::Vector<char16_t> &aString)
+{
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ if (aRequest->IsCanceled()) {
+ return NS_BINDING_ABORTED;
+ }
+
+ // If we don't have a document, then we need to abort further
+ // evaluation.
+ if (!mDocument) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If the load returned an error page, then we need to abort
+ nsCOMPtr<nsIRequest> req;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
+ NS_ASSERTION(req, "StreamLoader's request went away prematurely");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
+ if (httpChannel) {
+ bool requestSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
+ if (NS_SUCCEEDED(rv) && !requestSucceeded) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString sourceMapURL;
+ rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL);
+ if (NS_SUCCEEDED(rv)) {
+ aRequest->mHasSourceMapURL = true;
+ aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL);
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
+ // If this load was subject to a CORS check; don't flag it with a
+ // separate origin principal, so that it will treat our document's
+ // principal as the origin principal
+ if (aRequest->mCORSMode == CORS_NONE) {
+ rv = nsContentUtils::GetSecurityManager()->
+ GetChannelResultPrincipal(channel, getter_AddRefs(aRequest->mOriginPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aString.empty()) {
+ aRequest->mScriptTextLength = aString.length();
+ aRequest->mScriptTextBuf = aString.extractOrCopyRawBuffer();
+ }
+
+ // This assertion could fire errorously if we ran out of memory when
+ // inserting the request in the array. However it's an unlikely case
+ // so if you see this assertion it is likely something else that is
+ // wrong, especially if you see it more than once.
+ NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
+ mLoadingAsyncRequests.Contains(aRequest) ||
+ mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
+ mXSLTRequests.Contains(aRequest) ||
+ (aRequest->IsModuleRequest() &&
+ !aRequest->AsModuleRequest()->IsTopLevel() &&
+ !aRequest->isInList()) ||
+ mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
+ mParserBlockingRequest,
+ "aRequest should be pending!");
+
+ if (aRequest->IsModuleRequest()) {
+ nsModuleLoadRequest* request = aRequest->AsModuleRequest();
+
+ // When loading a module, only responses with a JavaScript MIME type are
+ // acceptable.
+ nsAutoCString mimeType;
+ channel->GetContentType(mimeType);
+ NS_ConvertUTF8toUTF16 typeString(mimeType);
+ if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ channel->GetURI(getter_AddRefs(request->mBaseURL));
+
+ // Attempt to compile off main thread.
+ rv = AttemptAsyncScriptCompile(request);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ // Otherwise compile it right away and start fetching descendents.
+ return ProcessFetchedModuleSource(request);
+ }
+
+ // The script is now loaded and ready to run.
+ aRequest->SetReady();
+
+ // If this is currently blocking the parser, attempt to compile it off-main-thread.
+ if (aRequest == mParserBlockingRequest && (NumberOfProcessors() > 1)) {
+ MOZ_ASSERT(!aRequest->IsModuleRequest());
+ nsresult rv = AttemptAsyncScriptCompile(aRequest);
+ if (rv == NS_OK) {
+ MOZ_ASSERT(aRequest->mProgress == nsScriptLoadRequest::Progress::Compiling,
+ "Request should be off-thread compiling now.");
+ return NS_OK;
+ }
+
+ // If off-thread compile errored, return the error.
+ if (rv != NS_ERROR_FAILURE) {
+ return rv;
+ }
+
+ // If off-thread compile was rejected, continue with regular processing.
+ }
+
+ MaybeMoveToLoadedList(aRequest);
+
+ return NS_OK;
+}
+
+void
+nsScriptLoader::ParsingComplete(bool aTerminated)
+{
+ if (mDeferEnabled) {
+ // Have to check because we apparently get ParsingComplete
+ // without BeginDeferringScripts in some cases
+ mDocumentParsingDone = true;
+ }
+ mDeferEnabled = false;
+ if (aTerminated) {
+ mDeferRequests.Clear();
+ mLoadingAsyncRequests.Clear();
+ mLoadedAsyncRequests.Clear();
+ mNonAsyncExternalScriptInsertedRequests.Clear();
+ mXSLTRequests.Clear();
+ if (mParserBlockingRequest) {
+ mParserBlockingRequest->Cancel();
+ mParserBlockingRequest = nullptr;
+ }
+ }
+
+ // Have to call this even if aTerminated so we'll correctly unblock
+ // onload and all.
+ ProcessPendingRequests();
+}
+
+void
+nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
+ const nsAString &aType,
+ const nsAString &aCrossOrigin,
+ const nsAString& aIntegrity,
+ bool aScriptFromHead,
+ const mozilla::net::ReferrerPolicy aReferrerPolicy)
+{
+ NS_ENSURE_TRUE_VOID(mDocument);
+ // Check to see if scripts has been turned off.
+ if (!mEnabled || !mDocument->IsScriptEnabled()) {
+ return;
+ }
+
+ // TODO: Preload module scripts.
+ if (nsContentUtils::IsChromeDoc(mDocument) && aType.LowerCaseEqualsASCII("module")) {
+ return;
+ }
+
+ SRIMetadata sriMetadata;
+ if (!aIntegrity.IsEmpty()) {
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
+ ("nsScriptLoader::PreloadURI, integrity=%s",
+ NS_ConvertUTF16toUTF8(aIntegrity).get()));
+ nsAutoCString sourceUri;
+ if (mDocument->GetDocumentURI()) {
+ mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
+ }
+ SRICheck::IntegrityMetadata(aIntegrity, sourceUri, mReporter, &sriMetadata);
+ }
+
+ RefPtr<nsScriptLoadRequest> request =
+ CreateLoadRequest(nsScriptKind::Classic, nullptr, 0,
+ Element::StringToCORSMode(aCrossOrigin), sriMetadata);
+ request->mURI = aURI;
+ request->mIsInline = false;
+ request->mReferrerPolicy = aReferrerPolicy;
+
+ nsresult rv = StartLoad(request, aType, aScriptFromHead);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ PreloadInfo *pi = mPreloads.AppendElement();
+ pi->mRequest = request;
+ pi->mCharset = aCharset;
+}
+
+void
+nsScriptLoader::AddDeferRequest(nsScriptLoadRequest* aRequest)
+{
+ aRequest->mIsDefer = true;
+ mDeferRequests.AppendElement(aRequest);
+ if (mDeferEnabled && aRequest == mDeferRequests.getFirst() &&
+ mDocument && !mBlockingDOMContentLoaded) {
+ MOZ_ASSERT(mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING);
+ mBlockingDOMContentLoaded = true;
+ mDocument->BlockDOMContentLoaded();
+ }
+}
+
+bool
+nsScriptLoader::MaybeRemovedDeferRequests()
+{
+ if (mDeferRequests.isEmpty() && mDocument &&
+ mBlockingDOMContentLoaded) {
+ mBlockingDOMContentLoaded = false;
+ mDocument->UnblockDOMContentLoaded();
+ return true;
+ }
+ return false;
+}
+
+//////////////////////////////////////////////////////////////
+// nsScriptLoadHandler
+//////////////////////////////////////////////////////////////
+
+nsScriptLoadHandler::nsScriptLoadHandler(nsScriptLoader *aScriptLoader,
+ nsScriptLoadRequest *aRequest,
+ mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier)
+ : mScriptLoader(aScriptLoader),
+ mRequest(aRequest),
+ mSRIDataVerifier(aSRIDataVerifier),
+ mSRIStatus(NS_OK),
+ mDecoder(),
+ mBuffer()
+{}
+
+nsScriptLoadHandler::~nsScriptLoadHandler()
+{}
+
+NS_IMPL_ISUPPORTS(nsScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
+
+NS_IMETHODIMP
+nsScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ uint32_t aDataLength,
+ const uint8_t* aData,
+ uint32_t *aConsumedLength)
+{
+ if (mRequest->IsCanceled()) {
+ // If request cancelled, ignore any incoming data.
+ *aConsumedLength = aDataLength;
+ return NS_OK;
+ }
+
+ if (!EnsureDecoder(aLoader, aData, aDataLength,
+ /* aEndOfStream = */ false)) {
+ return NS_OK;
+ }
+
+ // Below we will/shall consume entire data chunk.
+ *aConsumedLength = aDataLength;
+
+ // Decoder has already been initialized. -- trying to decode all loaded bytes.
+ nsresult rv = TryDecodeRawData(aData, aDataLength,
+ /* aEndOfStream = */ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If SRI is required for this load, appending new bytes to the hash.
+ if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+ mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+ }
+
+ return rv;
+}
+
+nsresult
+nsScriptLoadHandler::TryDecodeRawData(const uint8_t* aData,
+ uint32_t aDataLength,
+ bool aEndOfStream)
+{
+ int32_t srcLen = aDataLength;
+ const char* src = reinterpret_cast<const char *>(aData);
+ int32_t dstLen;
+ nsresult rv =
+ mDecoder->GetMaxLength(src, srcLen, &dstLen);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t haveRead = mBuffer.length();
+
+ CheckedInt<uint32_t> capacity = haveRead;
+ capacity += dstLen;
+
+ if (!capacity.isValid() || !mBuffer.reserve(capacity.value())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = mDecoder->Convert(src,
+ &srcLen,
+ mBuffer.begin() + haveRead,
+ &dstLen);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ haveRead += dstLen;
+ MOZ_ASSERT(haveRead <= capacity.value(), "mDecoder produced more data than expected");
+ MOZ_ALWAYS_TRUE(mBuffer.resizeUninitialized(haveRead));
+
+ return NS_OK;
+}
+
+bool
+nsScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
+ const uint8_t* aData,
+ uint32_t aDataLength,
+ bool aEndOfStream)
+{
+ // Check if decoder has already been created.
+ if (mDecoder) {
+ return true;
+ }
+
+ nsAutoCString charset;
+
+ // JavaScript modules are always UTF-8.
+ if (mRequest->IsModuleRequest()) {
+ charset = "UTF-8";
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // Determine if BOM check should be done. This occurs either
+ // if end-of-stream has been reached, or at least 3 bytes have
+ // been read from input.
+ if (!aEndOfStream && (aDataLength < 3)) {
+ return false;
+ }
+
+ // Do BOM detection.
+ if (nsContentUtils::CheckForBOM(aData, aDataLength, charset)) {
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // BOM detection failed, check content stream for charset.
+ nsCOMPtr<nsIRequest> req;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
+ NS_ASSERTION(req, "StreamLoader's request went away prematurely");
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
+
+ if (channel &&
+ NS_SUCCEEDED(channel->GetContentCharset(charset)) &&
+ EncodingUtils::FindEncodingForLabel(charset, charset)) {
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // Check the hint charset from the script element or preload
+ // request.
+ nsAutoString hintCharset;
+ if (!mRequest->IsPreload()) {
+ mRequest->mElement->GetScriptCharset(hintCharset);
+ } else {
+ nsTArray<nsScriptLoader::PreloadInfo>::index_type i =
+ mScriptLoader->mPreloads.IndexOf(mRequest, 0,
+ nsScriptLoader::PreloadRequestComparator());
+
+ NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
+ "Incorrect preload bookkeeping");
+ hintCharset = mScriptLoader->mPreloads[i].mCharset;
+ }
+
+ if (EncodingUtils::FindEncodingForLabel(hintCharset, charset)) {
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // Get the charset from the charset of the document.
+ if (mScriptLoader->mDocument) {
+ charset = mScriptLoader->mDocument->GetDocumentCharacterSet();
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+ }
+
+ // Curiously, there are various callers that don't pass aDocument. The
+ // fallback in the old code was ISO-8859-1, which behaved like
+ // windows-1252. Saying windows-1252 for clarity and for compliance
+ // with the Encoding Standard.
+ charset = "windows-1252";
+ mDecoder = EncodingUtils::DecoderForEncoding(charset);
+ return true;
+}
+
+NS_IMETHODIMP
+nsScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aStatus,
+ uint32_t aDataLength,
+ const uint8_t* aData)
+{
+ if (!mRequest->IsCanceled()) {
+ DebugOnly<bool> encoderSet =
+ EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
+ MOZ_ASSERT(encoderSet);
+ DebugOnly<nsresult> rv = TryDecodeRawData(aData, aDataLength,
+ /* aEndOfStream = */ true);
+
+ // If SRI is required for this load, appending new bytes to the hash.
+ if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
+ mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
+ }
+ }
+
+ // we have to mediate and use mRequest.
+ return mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
+ mBuffer, mSRIDataVerifier);
+}
diff --git a/dom/base/nsScriptLoader.h b/dom/base/nsScriptLoader.h
new file mode 100644
index 000000000..d30a58441
--- /dev/null
+++ b/dom/base/nsScriptLoader.h
@@ -0,0 +1,715 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 that handles loading and evaluation of <script> elements.
+ */
+
+#ifndef __nsScriptLoader_h__
+#define __nsScriptLoader_h__
+
+#include "nsCOMPtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsIScriptElement.h"
+#include "nsCOMArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "nsIDocument.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsURIHashKey.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "mozilla/dom/SRICheck.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/Vector.h"
+
+class nsModuleLoadRequest;
+class nsModuleScript;
+class nsScriptLoadRequestList;
+class nsIURI;
+
+namespace JS {
+ class SourceBufferHolder;
+} // namespace JS
+
+namespace mozilla {
+namespace dom {
+class AutoJSAPI;
+} // namespace dom
+} // namespace mozilla
+
+//////////////////////////////////////////////////////////////
+// Per-request data structure
+//////////////////////////////////////////////////////////////
+
+enum class nsScriptKind {
+ Classic,
+ Module
+};
+
+class nsScriptLoadRequest : public nsISupports,
+ private mozilla::LinkedListElement<nsScriptLoadRequest>
+{
+ typedef LinkedListElement<nsScriptLoadRequest> super;
+
+ // Allow LinkedListElement<nsScriptLoadRequest> to cast us to itself as needed.
+ friend class mozilla::LinkedListElement<nsScriptLoadRequest>;
+ friend class nsScriptLoadRequestList;
+
+protected:
+ virtual ~nsScriptLoadRequest();
+
+public:
+ nsScriptLoadRequest(nsScriptKind aKind,
+ nsIScriptElement* aElement,
+ uint32_t aVersion,
+ mozilla::CORSMode aCORSMode,
+ const mozilla::dom::SRIMetadata &aIntegrity)
+ : mKind(aKind),
+ mElement(aElement),
+ mProgress(Progress::Loading),
+ mIsInline(true),
+ mHasSourceMapURL(false),
+ mIsDefer(false),
+ mIsAsync(false),
+ mIsNonAsyncScriptInserted(false),
+ mIsXSLT(false),
+ mIsCanceled(false),
+ mWasCompiledOMT(false),
+ mOffThreadToken(nullptr),
+ mScriptTextBuf(nullptr),
+ mScriptTextLength(0),
+ mJSVersion(aVersion),
+ mLineNo(1),
+ mCORSMode(aCORSMode),
+ mIntegrity(aIntegrity),
+ mReferrerPolicy(mozilla::net::RP_Default)
+ {
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsScriptLoadRequest)
+
+ bool IsModuleRequest() const
+ {
+ return mKind == nsScriptKind::Module;
+ }
+
+ nsModuleLoadRequest* AsModuleRequest();
+
+ void FireScriptAvailable(nsresult aResult)
+ {
+ mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
+ }
+ void FireScriptEvaluated(nsresult aResult)
+ {
+ mElement->ScriptEvaluated(aResult, mElement, mIsInline);
+ }
+
+ bool IsPreload()
+ {
+ return mElement == nullptr;
+ }
+
+ virtual void Cancel();
+
+ bool IsCanceled() const
+ {
+ return mIsCanceled;
+ }
+
+ virtual void SetReady();
+
+ void** OffThreadTokenPtr()
+ {
+ return mOffThreadToken ? &mOffThreadToken : nullptr;
+ }
+
+ enum class Progress {
+ Loading,
+ Compiling,
+ FetchingImports,
+ Ready
+ };
+ bool IsReadyToRun() const {
+ return mProgress == Progress::Ready;
+ }
+ bool IsLoading() const {
+ return mProgress == Progress::Loading;
+ }
+ bool InCompilingStage() const {
+ return mProgress == Progress::Compiling ||
+ (IsReadyToRun() && mWasCompiledOMT);
+ }
+
+ void MaybeCancelOffThreadScript();
+
+ using super::getNext;
+ using super::isInList;
+
+ const nsScriptKind mKind;
+ nsCOMPtr<nsIScriptElement> mElement;
+ Progress mProgress; // Are we still waiting for a load to complete?
+ bool mIsInline; // Is the script inline or loaded?
+ bool mHasSourceMapURL; // Does the HTTP header have a source map url?
+ bool mIsDefer; // True if we live in mDeferRequests.
+ bool mIsAsync; // True if we live in mLoadingAsyncRequests or mLoadedAsyncRequests.
+ bool mIsNonAsyncScriptInserted; // True if we live in mNonAsyncExternalScriptInsertedRequests
+ bool mIsXSLT; // True if we live in mXSLTRequests.
+ bool mIsCanceled; // True if we have been explicitly canceled.
+ bool mWasCompiledOMT; // True if the script has been compiled off main thread.
+ void* mOffThreadToken; // Off-thread parsing token.
+ nsString mSourceMapURL; // Holds source map url for loaded scripts
+ char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
+ size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
+ uint32_t mJSVersion;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mOriginPrincipal;
+ nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
+ int32_t mLineNo;
+ const mozilla::CORSMode mCORSMode;
+ const mozilla::dom::SRIMetadata mIntegrity;
+ mozilla::net::ReferrerPolicy mReferrerPolicy;
+};
+
+class nsScriptLoadRequestList : private mozilla::LinkedList<nsScriptLoadRequest>
+{
+ typedef mozilla::LinkedList<nsScriptLoadRequest> super;
+
+public:
+ ~nsScriptLoadRequestList();
+
+ void Clear();
+
+#ifdef DEBUG
+ bool Contains(nsScriptLoadRequest* aElem) const;
+#endif // DEBUG
+
+ using super::getFirst;
+ using super::isEmpty;
+
+ void AppendElement(nsScriptLoadRequest* aElem)
+ {
+ MOZ_ASSERT(!aElem->isInList());
+ NS_ADDREF(aElem);
+ insertBack(aElem);
+ }
+
+ MOZ_MUST_USE
+ already_AddRefed<nsScriptLoadRequest> Steal(nsScriptLoadRequest* aElem)
+ {
+ aElem->removeFrom(*this);
+ return dont_AddRef(aElem);
+ }
+
+ MOZ_MUST_USE
+ already_AddRefed<nsScriptLoadRequest> StealFirst()
+ {
+ MOZ_ASSERT(!isEmpty());
+ return Steal(getFirst());
+ }
+
+ void Remove(nsScriptLoadRequest* aElem)
+ {
+ aElem->removeFrom(*this);
+ NS_RELEASE(aElem);
+ }
+};
+
+//////////////////////////////////////////////////////////////
+// Script loader implementation
+//////////////////////////////////////////////////////////////
+
+class nsScriptLoader final : public nsISupports
+{
+ class MOZ_STACK_CLASS AutoCurrentScriptUpdater
+ {
+ public:
+ AutoCurrentScriptUpdater(nsScriptLoader* aScriptLoader,
+ nsIScriptElement* aCurrentScript)
+ : mOldScript(aScriptLoader->mCurrentScript)
+ , mScriptLoader(aScriptLoader)
+ {
+ mScriptLoader->mCurrentScript = aCurrentScript;
+ }
+ ~AutoCurrentScriptUpdater()
+ {
+ mScriptLoader->mCurrentScript.swap(mOldScript);
+ }
+ private:
+ nsCOMPtr<nsIScriptElement> mOldScript;
+ nsScriptLoader* mScriptLoader;
+ };
+
+ friend class nsModuleLoadRequest;
+ friend class nsScriptRequestProcessor;
+ friend class nsScriptLoadHandler;
+ friend class AutoCurrentScriptUpdater;
+
+public:
+ explicit nsScriptLoader(nsIDocument* aDocument);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsScriptLoader)
+
+ /**
+ * The loader maintains a weak reference to the document with
+ * which it is initialized. This call forces the reference to
+ * be dropped.
+ */
+ void DropDocumentReference()
+ {
+ mDocument = nullptr;
+ }
+
+ /**
+ * Add an observer for all scripts loaded through this loader.
+ *
+ * @param aObserver observer for all script processing.
+ */
+ nsresult AddObserver(nsIScriptLoaderObserver* aObserver)
+ {
+ return mObservers.AppendObject(aObserver) ? NS_OK :
+ NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ /**
+ * Remove an observer.
+ *
+ * @param aObserver observer to be removed
+ */
+ void RemoveObserver(nsIScriptLoaderObserver* aObserver)
+ {
+ mObservers.RemoveObject(aObserver);
+ }
+
+ /**
+ * Process a script element. This will include both loading the
+ * source of the element if it is not inline and evaluating
+ * the script itself.
+ *
+ * If the script is an inline script that can be executed immediately
+ * (i.e. there are no other scripts pending) then ScriptAvailable
+ * and ScriptEvaluated will be called before the function returns.
+ *
+ * If true is returned the script could not be executed immediately.
+ * In this case ScriptAvailable is guaranteed to be called at a later
+ * point (as well as possibly ScriptEvaluated).
+ *
+ * @param aElement The element representing the script to be loaded and
+ * evaluated.
+ */
+ bool ProcessScriptElement(nsIScriptElement* aElement);
+
+ /**
+ * Gets the currently executing script. This is useful if you want to
+ * generate a unique key based on the currently executing script.
+ */
+ nsIScriptElement* GetCurrentScript()
+ {
+ return mCurrentScript;
+ }
+
+ nsIScriptElement* GetCurrentParserInsertedScript()
+ {
+ return mCurrentParserInsertedScript;
+ }
+
+ /**
+ * Whether the loader is enabled or not.
+ * When disabled, processing of new script elements is disabled.
+ * Any call to ProcessScriptElement() will return false. Note that
+ * this DOES NOT disable currently loading or executing scripts.
+ */
+ bool GetEnabled()
+ {
+ return mEnabled;
+ }
+ void SetEnabled(bool aEnabled)
+ {
+ if (!mEnabled && aEnabled) {
+ ProcessPendingRequestsAsync();
+ }
+ mEnabled = aEnabled;
+ }
+
+ /**
+ * Add/remove a blocker for parser-blocking scripts (and XSLT
+ * scripts). Blockers will stop such scripts from executing, but not from
+ * loading.
+ */
+ void AddParserBlockingScriptExecutionBlocker()
+ {
+ ++mParserBlockingBlockerCount;
+ }
+ void RemoveParserBlockingScriptExecutionBlocker()
+ {
+ if (!--mParserBlockingBlockerCount && ReadyToExecuteScripts()) {
+ ProcessPendingRequestsAsync();
+ }
+ }
+
+ /**
+ * Add/remove a blocker for execution of all scripts. Blockers will stop
+ * scripts from executing, but not from loading.
+ */
+ void AddExecuteBlocker()
+ {
+ ++mBlockerCount;
+ }
+ void RemoveExecuteBlocker()
+ {
+ MOZ_ASSERT(mBlockerCount);
+ if (!--mBlockerCount) {
+ ProcessPendingRequestsAsync();
+ }
+ }
+
+ /**
+ * Convert the given buffer to a UTF-16 string.
+ * @param aChannel Channel corresponding to the data. May be null.
+ * @param aData The data to convert
+ * @param aLength Length of the data
+ * @param aHintCharset Hint for the character set (e.g., from a charset
+ * attribute). May be the empty string.
+ * @param aDocument Document which the data is loaded for. Must not be
+ * null.
+ * @param aBufOut [out] char16_t array allocated by ConvertToUTF16 and
+ * containing data converted to unicode. Caller must
+ * js_free() this data when no longer needed.
+ * @param aLengthOut [out] Length of array returned in aBufOut in number
+ * of char16_t code units.
+ */
+ static nsresult ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
+ uint32_t aLength,
+ const nsAString& aHintCharset,
+ nsIDocument* aDocument,
+ char16_t*& aBufOut, size_t& aLengthOut);
+
+ /**
+ * Handle the completion of a stream. This is called by the
+ * nsScriptLoadHandler object which observes the IncrementalStreamLoader
+ * loading the script.
+ */
+ nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+ nsISupports* aContext,
+ nsresult aChannelStatus,
+ nsresult aSRIStatus,
+ mozilla::Vector<char16_t> &aString,
+ mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier);
+
+ /**
+ * Processes any pending requests that are ready for processing.
+ */
+ void ProcessPendingRequests();
+
+ /**
+ * Starts deferring deferred scripts and puts them in the mDeferredRequests
+ * queue instead.
+ */
+ void BeginDeferringScripts()
+ {
+ mDeferEnabled = true;
+ if (mDocument) {
+ mDocument->BlockOnload();
+ }
+ }
+
+ /**
+ * Notifies the script loader that parsing is done. If aTerminated is true,
+ * this will drop any pending scripts that haven't run yet. Otherwise, it
+ * will stops deferring scripts and immediately processes the
+ * mDeferredRequests queue.
+ *
+ * WARNING: This function will synchronously execute content scripts, so be
+ * prepared that the world might change around you.
+ */
+ void ParsingComplete(bool aTerminated);
+
+ /**
+ * Returns the number of pending scripts, deferred or not.
+ */
+ uint32_t HasPendingOrCurrentScripts()
+ {
+ return mCurrentScript || mParserBlockingRequest;
+ }
+
+ /**
+ * Adds aURI to the preload list and starts loading it.
+ *
+ * @param aURI The URI of the external script.
+ * @param aCharset The charset parameter for the script.
+ * @param aType The type parameter for the script.
+ * @param aCrossOrigin The crossorigin attribute for the script.
+ * Void if not present.
+ * @param aIntegrity The expect hash url, if avail, of the request
+ * @param aScriptFromHead Whether or not the script was a child of head
+ */
+ virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
+ const nsAString &aType,
+ const nsAString &aCrossOrigin,
+ const nsAString& aIntegrity,
+ bool aScriptFromHead,
+ const mozilla::net::ReferrerPolicy aReferrerPolicy);
+
+ /**
+ * Process a request that was deferred so that the script could be compiled
+ * off thread.
+ */
+ nsresult ProcessOffThreadRequest(nsScriptLoadRequest *aRequest);
+
+ bool AddPendingChildLoader(nsScriptLoader* aChild) {
+ return mPendingChildLoaders.AppendElement(aChild) != nullptr;
+ }
+
+private:
+ virtual ~nsScriptLoader();
+
+ nsScriptLoadRequest* CreateLoadRequest(
+ nsScriptKind aKind,
+ nsIScriptElement* aElement,
+ uint32_t aVersion,
+ mozilla::CORSMode aCORSMode,
+ const mozilla::dom::SRIMetadata &aIntegrity);
+
+ /**
+ * Unblocks the creator parser of the parser-blocking scripts.
+ */
+ void UnblockParser(nsScriptLoadRequest* aParserBlockingRequest);
+
+ /**
+ * Asynchronously resumes the creator parser of the parser-blocking scripts.
+ */
+ void ContinueParserAsync(nsScriptLoadRequest* aParserBlockingRequest);
+
+
+ /**
+ * Helper function to check the content policy for a given request.
+ */
+ static nsresult CheckContentPolicy(nsIDocument* aDocument,
+ nsISupports *aContext,
+ nsIURI *aURI,
+ const nsAString &aType,
+ bool aIsPreLoad);
+
+ /**
+ * Start a load for aRequest's URI.
+ */
+ nsresult StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
+ bool aScriptFromHead);
+
+ /**
+ * Process any pending requests asynchronously (i.e. off an event) if there
+ * are any. Note that this is a no-op if there aren't any currently pending
+ * requests.
+ *
+ * This function is virtual to allow cross-library calls to SetEnabled()
+ */
+ virtual void ProcessPendingRequestsAsync();
+
+ /**
+ * If true, the loader is ready to execute parser-blocking scripts, and so are
+ * all its ancestors. If the loader itself is ready but some ancestor is not,
+ * this function will add an execute blocker and ask the ancestor to remove it
+ * once it becomes ready.
+ */
+ bool ReadyToExecuteParserBlockingScripts();
+
+ /**
+ * Return whether just this loader is ready to execute parser-blocking
+ * scripts.
+ */
+ bool SelfReadyToExecuteParserBlockingScripts()
+ {
+ return ReadyToExecuteScripts() && !mParserBlockingBlockerCount;
+ }
+
+ /**
+ * Return whether this loader is ready to execute scripts in general.
+ */
+ bool ReadyToExecuteScripts()
+ {
+ return mEnabled && !mBlockerCount;
+ }
+
+ nsresult AttemptAsyncScriptCompile(nsScriptLoadRequest* aRequest);
+ nsresult ProcessRequest(nsScriptLoadRequest* aRequest);
+ nsresult CompileOffThreadOrProcessRequest(nsScriptLoadRequest* aRequest);
+ void FireScriptAvailable(nsresult aResult,
+ nsScriptLoadRequest* aRequest);
+ void FireScriptEvaluated(nsresult aResult,
+ nsScriptLoadRequest* aRequest);
+ nsresult EvaluateScript(nsScriptLoadRequest* aRequest);
+
+ already_AddRefed<nsIScriptGlobalObject> GetScriptGlobalObject();
+ nsresult FillCompileOptionsForRequest(const mozilla::dom::AutoJSAPI& jsapi,
+ nsScriptLoadRequest* aRequest,
+ JS::Handle<JSObject*> aScopeChain,
+ JS::CompileOptions* aOptions);
+
+ uint32_t NumberOfProcessors();
+ nsresult PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
+ nsIIncrementalStreamLoader* aLoader,
+ nsresult aStatus,
+ mozilla::Vector<char16_t> &aString);
+
+ void AddDeferRequest(nsScriptLoadRequest* aRequest);
+ bool MaybeRemovedDeferRequests();
+
+ void MaybeMoveToLoadedList(nsScriptLoadRequest* aRequest);
+
+ JS::SourceBufferHolder GetScriptSource(nsScriptLoadRequest* aRequest,
+ nsAutoString& inlineData);
+
+ void SetModuleFetchStarted(nsModuleLoadRequest *aRequest);
+ void SetModuleFetchFinishedAndResumeWaitingRequests(nsModuleLoadRequest *aRequest,
+ nsresult aResult);
+
+ bool IsFetchingModule(nsModuleLoadRequest *aRequest) const;
+
+ bool ModuleMapContainsModule(nsModuleLoadRequest *aRequest) const;
+ RefPtr<mozilla::GenericPromise> WaitForModuleFetch(nsModuleLoadRequest *aRequest);
+ nsModuleScript* GetFetchedModule(nsIURI* aURL) const;
+
+ friend bool
+ HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp);
+
+ nsresult CreateModuleScript(nsModuleLoadRequest* aRequest);
+ nsresult ProcessFetchedModuleSource(nsModuleLoadRequest* aRequest);
+ void ProcessLoadedModuleTree(nsModuleLoadRequest* aRequest);
+ bool InstantiateModuleTree(nsModuleLoadRequest* aRequest);
+ void StartFetchingModuleDependencies(nsModuleLoadRequest* aRequest);
+
+ RefPtr<mozilla::GenericPromise>
+ StartFetchingModuleAndDependencies(nsModuleLoadRequest* aRequest, nsIURI* aURI);
+
+ nsIDocument* mDocument; // [WEAK]
+ nsCOMArray<nsIScriptLoaderObserver> mObservers;
+ nsScriptLoadRequestList mNonAsyncExternalScriptInsertedRequests;
+ // mLoadingAsyncRequests holds async requests while they're loading; when they
+ // have been loaded they are moved to mLoadedAsyncRequests.
+ nsScriptLoadRequestList mLoadingAsyncRequests;
+ nsScriptLoadRequestList mLoadedAsyncRequests;
+ nsScriptLoadRequestList mDeferRequests;
+ nsScriptLoadRequestList mXSLTRequests;
+ RefPtr<nsScriptLoadRequest> mParserBlockingRequest;
+
+ // In mRequests, the additional information here is stored by the element.
+ struct PreloadInfo {
+ RefPtr<nsScriptLoadRequest> mRequest;
+ nsString mCharset;
+ };
+
+ friend void ImplCycleCollectionUnlink(nsScriptLoader::PreloadInfo& aField);
+ friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ nsScriptLoader::PreloadInfo& aField,
+ const char* aName, uint32_t aFlags);
+
+ struct PreloadRequestComparator {
+ bool Equals(const PreloadInfo &aPi, nsScriptLoadRequest * const &aRequest)
+ const
+ {
+ return aRequest == aPi.mRequest;
+ }
+ };
+ struct PreloadURIComparator {
+ bool Equals(const PreloadInfo &aPi, nsIURI * const &aURI) const;
+ };
+ nsTArray<PreloadInfo> mPreloads;
+
+ nsCOMPtr<nsIScriptElement> mCurrentScript;
+ nsCOMPtr<nsIScriptElement> mCurrentParserInsertedScript;
+ nsTArray< RefPtr<nsScriptLoader> > mPendingChildLoaders;
+ uint32_t mParserBlockingBlockerCount;
+ uint32_t mBlockerCount;
+ uint32_t mNumberOfProcessors;
+ bool mEnabled;
+ bool mDeferEnabled;
+ bool mDocumentParsingDone;
+ bool mBlockingDOMContentLoaded;
+
+ // Module map
+ nsRefPtrHashtable<nsURIHashKey, mozilla::GenericPromise::Private> mFetchingModules;
+ nsRefPtrHashtable<nsURIHashKey, nsModuleScript> mFetchedModules;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReporter;
+};
+
+class nsScriptLoadHandler final : public nsIIncrementalStreamLoaderObserver
+{
+public:
+ explicit nsScriptLoadHandler(nsScriptLoader* aScriptLoader,
+ nsScriptLoadRequest *aRequest,
+ mozilla::dom::SRICheckDataVerifier *aSRIDataVerifier);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
+
+private:
+ virtual ~nsScriptLoadHandler();
+
+ /*
+ * Try to decode some raw data.
+ */
+ nsresult TryDecodeRawData(const uint8_t* aData, uint32_t aDataLength,
+ bool aEndOfStream);
+
+ /*
+ * Discover the charset by looking at the stream data, the script
+ * tag, and other indicators. Returns true if charset has been
+ * discovered.
+ */
+ bool EnsureDecoder(nsIIncrementalStreamLoader *aLoader,
+ const uint8_t* aData, uint32_t aDataLength,
+ bool aEndOfStream);
+
+ // ScriptLoader which will handle the parsed script.
+ RefPtr<nsScriptLoader> mScriptLoader;
+
+ // The nsScriptLoadRequest for this load.
+ RefPtr<nsScriptLoadRequest> mRequest;
+
+ // SRI data verifier.
+ nsAutoPtr<mozilla::dom::SRICheckDataVerifier> mSRIDataVerifier;
+
+ // Status of SRI data operations.
+ nsresult mSRIStatus;
+
+ // Unicode decoder for charset.
+ nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+
+ // Accumulated decoded char buffer.
+ mozilla::Vector<char16_t> mBuffer;
+};
+
+class nsAutoScriptLoaderDisabler
+{
+public:
+ explicit nsAutoScriptLoaderDisabler(nsIDocument* aDoc)
+ {
+ mLoader = aDoc->ScriptLoader();
+ mWasEnabled = mLoader->GetEnabled();
+ if (mWasEnabled) {
+ mLoader->SetEnabled(false);
+ }
+ }
+
+ ~nsAutoScriptLoaderDisabler()
+ {
+ if (mWasEnabled) {
+ mLoader->SetEnabled(true);
+ }
+ }
+
+ bool mWasEnabled;
+ RefPtr<nsScriptLoader> mLoader;
+};
+
+#endif //__nsScriptLoader_h__
diff --git a/dom/base/nsScriptNameSpaceManager.cpp b/dom/base/nsScriptNameSpaceManager.cpp
new file mode 100644
index 000000000..6003a1fc1
--- /dev/null
+++ b/dom/base/nsScriptNameSpaceManager.cpp
@@ -0,0 +1,436 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsScriptNameSpaceManager.h"
+#include "nsCOMPtr.h"
+#include "nsIComponentManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsICategoryManager.h"
+#include "nsIServiceManager.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIScriptNameSpaceManager.h"
+#include "nsIScriptContext.h"
+#include "nsIInterfaceInfoManager.h"
+#include "nsIInterfaceInfo.h"
+#include "xptinfo.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsHashKeys.h"
+#include "nsDOMClassInfo.h"
+#include "nsCRT.h"
+#include "nsIObserverService.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+
+#define NS_INTERFACE_PREFIX "nsI"
+#define NS_DOM_INTERFACE_PREFIX "nsIDOM"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static PLDHashNumber
+GlobalNameHashHashKey(const void *key)
+{
+ const nsAString *str = static_cast<const nsAString *>(key);
+ return HashString(*str);
+}
+
+static bool
+GlobalNameHashMatchEntry(const PLDHashEntryHdr *entry, const void *key)
+{
+ const GlobalNameMapEntry *e =
+ static_cast<const GlobalNameMapEntry *>(entry);
+ const nsAString *str = static_cast<const nsAString *>(key);
+
+ return str->Equals(e->mKey);
+}
+
+static void
+GlobalNameHashClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry)
+{
+ GlobalNameMapEntry *e = static_cast<GlobalNameMapEntry *>(entry);
+
+ // An entry is being cleared, let the key (nsString) do its own
+ // cleanup.
+ e->mKey.~nsString();
+
+ // This will set e->mGlobalName.mType to
+ // nsGlobalNameStruct::eTypeNotInitialized
+ memset(&e->mGlobalName, 0, sizeof(nsGlobalNameStruct));
+}
+
+static void
+GlobalNameHashInitEntry(PLDHashEntryHdr *entry, const void *key)
+{
+ GlobalNameMapEntry *e = static_cast<GlobalNameMapEntry *>(entry);
+ const nsAString *keyStr = static_cast<const nsAString *>(key);
+
+ // Initialize the key in the entry with placement new
+ new (&e->mKey) nsString(*keyStr);
+
+ // This will set e->mGlobalName.mType to
+ // nsGlobalNameStruct::eTypeNotInitialized
+ memset(&e->mGlobalName, 0, sizeof(nsGlobalNameStruct));
+}
+
+NS_IMPL_ISUPPORTS(
+ nsScriptNameSpaceManager,
+ nsIObserver,
+ nsISupportsWeakReference,
+ nsIMemoryReporter)
+
+static const PLDHashTableOps hash_table_ops =
+{
+ GlobalNameHashHashKey,
+ GlobalNameHashMatchEntry,
+ PLDHashTable::MoveEntryStub,
+ GlobalNameHashClearEntry,
+ GlobalNameHashInitEntry
+};
+
+#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH 32
+
+nsScriptNameSpaceManager::nsScriptNameSpaceManager()
+ : mGlobalNames(&hash_table_ops, sizeof(GlobalNameMapEntry),
+ GLOBALNAME_HASHTABLE_INITIAL_LENGTH)
+{
+ MOZ_COUNT_CTOR(nsScriptNameSpaceManager);
+}
+
+nsScriptNameSpaceManager::~nsScriptNameSpaceManager()
+{
+ UnregisterWeakMemoryReporter(this);
+ MOZ_COUNT_DTOR(nsScriptNameSpaceManager);
+}
+
+nsGlobalNameStruct *
+nsScriptNameSpaceManager::AddToHash(const char *aKey,
+ const char16_t **aClassName)
+{
+ NS_ConvertASCIItoUTF16 key(aKey);
+ auto entry = static_cast<GlobalNameMapEntry*>(mGlobalNames.Add(&key, fallible));
+ if (!entry) {
+ return nullptr;
+ }
+
+ WebIDLGlobalNameHash::Remove(aKey, key.Length());
+
+ if (aClassName) {
+ *aClassName = entry->mKey.get();
+ }
+
+ return &entry->mGlobalName;
+}
+
+void
+nsScriptNameSpaceManager::RemoveFromHash(const nsAString *aKey)
+{
+ mGlobalNames.Remove(aKey);
+}
+
+nsresult
+nsScriptNameSpaceManager::FillHash(nsICategoryManager *aCategoryManager,
+ const char *aCategory)
+{
+ nsCOMPtr<nsISimpleEnumerator> e;
+ nsresult rv = aCategoryManager->EnumerateCategory(aCategory,
+ getter_AddRefs(e));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> entry;
+ while (NS_SUCCEEDED(e->GetNext(getter_AddRefs(entry)))) {
+ rv = AddCategoryEntryToHash(aCategoryManager, aCategory, entry);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsScriptNameSpaceManager::Init()
+{
+ RegisterWeakMemoryReporter(this);
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsICategoryManager> cm =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = FillHash(cm, JAVASCRIPT_GLOBAL_CONSTRUCTOR_CATEGORY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = FillHash(cm, JAVASCRIPT_GLOBAL_PROPERTY_CATEGORY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = FillHash(cm, JAVASCRIPT_GLOBAL_PRIVILEGED_PROPERTY_CATEGORY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initial filling of the has table has been done.
+ // Now, listen for changes.
+ nsCOMPtr<nsIObserverService> serv =
+ mozilla::services::GetObserverService();
+
+ if (serv) {
+ serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, true);
+ serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, true);
+ }
+
+ return NS_OK;
+}
+
+const nsGlobalNameStruct*
+nsScriptNameSpaceManager::LookupName(const nsAString& aName,
+ const char16_t **aClassName)
+{
+ auto entry = static_cast<GlobalNameMapEntry*>(mGlobalNames.Search(&aName));
+
+ if (entry) {
+ if (aClassName) {
+ *aClassName = entry->mKey.get();
+ }
+ return &entry->mGlobalName;
+ }
+
+ if (aClassName) {
+ *aClassName = nullptr;
+ }
+ return nullptr;
+}
+
+nsresult
+nsScriptNameSpaceManager::RegisterClassName(const char *aClassName,
+ int32_t aDOMClassInfoID,
+ bool aPrivileged,
+ bool aXBLAllowed,
+ const char16_t **aResult)
+{
+ if (!nsCRT::IsAscii(aClassName)) {
+ NS_ERROR("Trying to register a non-ASCII class name");
+ return NS_OK;
+ }
+ nsGlobalNameStruct *s = AddToHash(aClassName, aResult);
+ NS_ENSURE_TRUE(s, NS_ERROR_OUT_OF_MEMORY);
+
+ if (s->mType == nsGlobalNameStruct::eTypeClassConstructor) {
+ return NS_OK;
+ }
+
+ // If a external constructor is already defined with aClassName we
+ // won't overwrite it.
+
+ if (s->mType == nsGlobalNameStruct::eTypeExternalConstructor) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(s->mType == nsGlobalNameStruct::eTypeNotInitialized,
+ "Whaaa, JS environment name clash!");
+
+ s->mType = nsGlobalNameStruct::eTypeClassConstructor;
+ s->mDOMClassInfoID = aDOMClassInfoID;
+ s->mChromeOnly = aPrivileged;
+ s->mAllowXBL = aXBLAllowed;
+
+ return NS_OK;
+}
+
+nsresult
+nsScriptNameSpaceManager::RegisterClassProto(const char *aClassName,
+ const nsIID *aConstructorProtoIID,
+ bool *aFoundOld)
+{
+ NS_ENSURE_ARG_POINTER(aConstructorProtoIID);
+
+ *aFoundOld = false;
+
+ nsGlobalNameStruct *s = AddToHash(aClassName);
+ NS_ENSURE_TRUE(s, NS_ERROR_OUT_OF_MEMORY);
+
+ if (s->mType != nsGlobalNameStruct::eTypeNotInitialized) {
+ *aFoundOld = true;
+
+ return NS_OK;
+ }
+
+ s->mType = nsGlobalNameStruct::eTypeClassProto;
+ s->mIID = *aConstructorProtoIID;
+
+ return NS_OK;
+}
+
+nsresult
+nsScriptNameSpaceManager::OperateCategoryEntryHash(nsICategoryManager* aCategoryManager,
+ const char* aCategory,
+ nsISupports* aEntry,
+ bool aRemove)
+{
+ MOZ_ASSERT(aCategoryManager);
+ // Get the type from the category name.
+ // NOTE: we could have passed the type in FillHash() and guessed it in
+ // Observe() but this way, we have only one place to update and this is
+ // not performance sensitive.
+ nsGlobalNameStruct::nametype type;
+ if (strcmp(aCategory, JAVASCRIPT_GLOBAL_CONSTRUCTOR_CATEGORY) == 0) {
+ type = nsGlobalNameStruct::eTypeExternalConstructor;
+ } else if (strcmp(aCategory, JAVASCRIPT_GLOBAL_PROPERTY_CATEGORY) == 0 ||
+ strcmp(aCategory, JAVASCRIPT_GLOBAL_PRIVILEGED_PROPERTY_CATEGORY) == 0) {
+ type = nsGlobalNameStruct::eTypeProperty;
+ } else {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupportsCString> strWrapper = do_QueryInterface(aEntry);
+
+ if (!strWrapper) {
+ NS_WARNING("Category entry not an nsISupportsCString!");
+ return NS_OK;
+ }
+
+ nsAutoCString categoryEntry;
+ nsresult rv = strWrapper->GetData(categoryEntry);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We need to handle removal before calling GetCategoryEntry
+ // because the category entry is already removed before we are
+ // notified.
+ if (aRemove) {
+ NS_ConvertASCIItoUTF16 entry(categoryEntry);
+ const nsGlobalNameStruct *s = LookupName(entry);
+ // Verify mType so that this API doesn't remove names
+ // registered by others.
+ if (!s || s->mType != type) {
+ return NS_OK;
+ }
+
+ RemoveFromHash(&entry);
+ return NS_OK;
+ }
+
+ nsXPIDLCString contractId;
+ rv = aCategoryManager->GetCategoryEntry(aCategory, categoryEntry.get(),
+ getter_Copies(contractId));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIComponentRegistrar> registrar;
+ rv = NS_GetComponentRegistrar(getter_AddRefs(registrar));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCID *cidPtr;
+ rv = registrar->ContractIDToCID(contractId, &cidPtr);
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Bad contract id registed with the script namespace manager");
+ return NS_OK;
+ }
+
+ // Copy CID onto the stack, so we can free it right away and avoid having
+ // to add cleanup code at every exit point from this function.
+ nsCID cid = *cidPtr;
+ free(cidPtr);
+
+ nsGlobalNameStruct *s = AddToHash(categoryEntry.get());
+ NS_ENSURE_TRUE(s, NS_ERROR_OUT_OF_MEMORY);
+
+ if (s->mType == nsGlobalNameStruct::eTypeNotInitialized) {
+ s->mType = type;
+ s->mCID = cid;
+ s->mChromeOnly =
+ strcmp(aCategory, JAVASCRIPT_GLOBAL_PRIVILEGED_PROPERTY_CATEGORY) == 0;
+ } else {
+ NS_WARNING("Global script name not overwritten!");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsScriptNameSpaceManager::AddCategoryEntryToHash(nsICategoryManager* aCategoryManager,
+ const char* aCategory,
+ nsISupports* aEntry)
+{
+ return OperateCategoryEntryHash(aCategoryManager, aCategory, aEntry,
+ /* aRemove = */ false);
+}
+
+nsresult
+nsScriptNameSpaceManager::RemoveCategoryEntryFromHash(nsICategoryManager* aCategoryManager,
+ const char* aCategory,
+ nsISupports* aEntry)
+{
+ return OperateCategoryEntryHash(aCategoryManager, aCategory, aEntry,
+ /* aRemove = */ true);
+}
+
+NS_IMETHODIMP
+nsScriptNameSpaceManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!aData) {
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID)) {
+ nsCOMPtr<nsICategoryManager> cm =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ if (!cm) {
+ return NS_OK;
+ }
+
+ return AddCategoryEntryToHash(cm, NS_ConvertUTF16toUTF8(aData).get(),
+ aSubject);
+ } else if (!strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID)) {
+ nsCOMPtr<nsICategoryManager> cm =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ if (!cm) {
+ return NS_OK;
+ }
+
+ return RemoveCategoryEntryFromHash(cm, NS_ConvertUTF16toUTF8(aData).get(),
+ aSubject);
+ }
+
+ // TODO: we could observe NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID
+ // but we are safe without it. See bug 600460.
+
+ return NS_OK;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(ScriptNameSpaceManagerMallocSizeOf)
+
+NS_IMETHODIMP
+nsScriptNameSpaceManager::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/script-namespace-manager", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(ScriptNameSpaceManagerMallocSizeOf),
+ "Memory used for the script namespace manager.");
+
+ return NS_OK;
+}
+
+size_t
+nsScriptNameSpaceManager::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+
+ n += mGlobalNames.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mGlobalNames.ConstIter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<GlobalNameMapEntry*>(iter.Get());
+ n += entry->SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
diff --git a/dom/base/nsScriptNameSpaceManager.h b/dom/base/nsScriptNameSpaceManager.h
new file mode 100644
index 000000000..3817e2c9c
--- /dev/null
+++ b/dom/base/nsScriptNameSpaceManager.h
@@ -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/.
+ *
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+#ifndef nsScriptNameSpaceManager_h__
+#define nsScriptNameSpaceManager_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "nsBaseHashtable.h"
+#include "nsIMemoryReporter.h"
+#include "nsIScriptNameSpaceManager.h"
+#include "nsString.h"
+#include "nsID.h"
+#include "PLDHashTable.h"
+#include "nsDOMClassInfo.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "xpcpublic.h"
+
+struct nsGlobalNameStruct
+{
+ enum nametype {
+ eTypeNotInitialized,
+ eTypeProperty,
+ eTypeExternalConstructor,
+ eTypeClassConstructor,
+ eTypeClassProto,
+ } mType;
+
+ bool mChromeOnly : 1;
+ bool mAllowXBL : 1;
+
+ union {
+ int32_t mDOMClassInfoID; // eTypeClassConstructor
+ nsIID mIID; // eTypeClassProto
+ nsCID mCID; // All other types
+ };
+};
+
+class GlobalNameMapEntry : public PLDHashEntryHdr
+{
+public:
+ // Our hash table ops don't care about the order of these members.
+ nsString mKey;
+ nsGlobalNameStruct mGlobalName;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mGlobalName
+ return mKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+};
+
+class nsICategoryManager;
+
+class nsScriptNameSpaceManager : public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsIMemoryReporter
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ nsScriptNameSpaceManager();
+
+ nsresult Init();
+
+ // Returns a nsGlobalNameStruct for aName, or null if one is not
+ // found. The returned nsGlobalNameStruct is only guaranteed to be
+ // valid until the next call to any of the methods in this class.
+ // It also returns a pointer to the string buffer of the classname
+ // in the nsGlobalNameStruct.
+ const nsGlobalNameStruct* LookupName(const nsAString& aName,
+ const char16_t **aClassName = nullptr);
+
+ nsresult RegisterClassName(const char *aClassName,
+ int32_t aDOMClassInfoID,
+ bool aPrivileged,
+ bool aXBLAllowed,
+ const char16_t **aResult);
+
+ nsresult RegisterClassProto(const char *aClassName,
+ const nsIID *aConstructorProtoIID,
+ bool *aFoundOld);
+
+ class NameIterator : public PLDHashTable::Iterator
+ {
+ public:
+ typedef PLDHashTable::Iterator Base;
+ explicit NameIterator(PLDHashTable* aTable) : Base(aTable) {}
+ NameIterator(NameIterator&& aOther) : Base(mozilla::Move(aOther.mTable)) {}
+
+ const GlobalNameMapEntry* Get() const
+ {
+ return static_cast<const GlobalNameMapEntry*>(Base::Get());
+ }
+
+ private:
+ NameIterator() = delete;
+ NameIterator(const NameIterator&) = delete;
+ NameIterator& operator=(const NameIterator&) = delete;
+ NameIterator& operator=(const NameIterator&&) = delete;
+ };
+
+ NameIterator GlobalNameIter() { return NameIterator(&mGlobalNames); }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+private:
+ virtual ~nsScriptNameSpaceManager();
+
+ // Adds a new entry to the hash and returns the nsGlobalNameStruct
+ // that aKey will be mapped to. If mType in the returned
+ // nsGlobalNameStruct is != eTypeNotInitialized, an entry for aKey
+ // already existed.
+ nsGlobalNameStruct *AddToHash(const char *aKey,
+ const char16_t **aClassName = nullptr);
+
+ // Removes an existing entry from the hash.
+ void RemoveFromHash(const nsAString *aKey);
+
+ nsresult FillHash(nsICategoryManager *aCategoryManager,
+ const char *aCategory);
+
+ /**
+ * Add a new category entry into the hash table.
+ * Only some categories can be added (see the beginning of the definition).
+ * The other ones will be ignored.
+ *
+ * @aCategoryManager Instance of the category manager service.
+ * @aCategory Category where the entry comes from.
+ * @aEntry The entry that should be added.
+ */
+ nsresult AddCategoryEntryToHash(nsICategoryManager* aCategoryManager,
+ const char* aCategory,
+ nsISupports* aEntry);
+
+ /**
+ * Remove an existing category entry from the hash table.
+ * Only some categories can be removed (see the beginning of the definition).
+ * The other ones will be ignored.
+ *
+ * @aCategory Category where the entry will be removed from.
+ * @aEntry The entry that should be removed.
+ */
+ nsresult RemoveCategoryEntryFromHash(nsICategoryManager* aCategoryManager,
+ const char* aCategory,
+ nsISupports* aEntry);
+
+ // common helper for AddCategoryEntryToHash and RemoveCategoryEntryFromHash
+ nsresult OperateCategoryEntryHash(nsICategoryManager* aCategoryManager,
+ const char* aCategory,
+ nsISupports* aEntry,
+ bool aRemove);
+
+ PLDHashTable mGlobalNames;
+};
+
+#endif /* nsScriptNameSpaceManager_h__ */
diff --git a/dom/base/nsStructuredCloneContainer.cpp b/dom/base/nsStructuredCloneContainer.cpp
new file mode 100644
index 000000000..8c2cdc091
--- /dev/null
+++ b/dom/base/nsStructuredCloneContainer.cpp
@@ -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/. */
+
+#include "nsStructuredCloneContainer.h"
+
+#include "nsCOMPtr.h"
+#include "nsIGlobalObject.h"
+#include "nsIVariant.h"
+#include "nsIXPConnect.h"
+#include "nsServiceManagerUtils.h"
+#include "nsContentUtils.h"
+#include "jsapi.h"
+#include "xpcpublic.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/dom/ScriptSettings.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()
+{
+}
+
+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())) {
+ return rv.StealNSResult();
+ }
+
+ 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())) {
+ return rv.StealNSResult();
+ }
+
+ aValue.set(jsStateObj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStructuredCloneContainer::DeserializeToVariant(JSContext* aCx,
+ nsIVariant** aData)
+{
+ NS_ENSURE_ARG_POINTER(aData);
+ *aData = nullptr;
+
+ if (!DataLength()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Deserialize to a JS::Value.
+ JS::Rooted<JS::Value> jsStateObj(aCx);
+ nsresult rv = DeserializeToJsval(aCx, &jsStateObj);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now wrap the JS::Value as an nsIVariant.
+ nsCOMPtr<nsIVariant> varStateObj;
+ nsCOMPtr<nsIXPConnect> xpconnect = do_GetService(nsIXPConnect::GetCID());
+ NS_ENSURE_STATE(xpconnect);
+ xpconnect->JSValToVariant(aCx, jsStateObj, getter_AddRefs(varStateObj));
+ NS_ENSURE_STATE(varStateObj);
+
+ varStateObj.forget(aData);
+ 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().Iter();
+ size_t size = Data().Size();
+ nsAutoCString binaryData;
+ binaryData.SetLength(size);
+ Data().ReadBytes(iter, binaryData.BeginWriting(), size);
+ nsAutoCString base64Data;
+ nsresult rv = Base64Encode(binaryData, base64Data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ CopyASCIItoUTF16(base64Data, aOut);
+ 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 000000000..a826c8c07
--- /dev/null
+++ b/dom/base/nsStructuredCloneContainer.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 nsStructuredCloneContainer_h__
+#define nsStructuredCloneContainer_h__
+
+#include "nsIStructuredCloneContainer.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+
+#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();
+
+ 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 000000000..3ded9b2d0
--- /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 000000000..0310fbcc4
--- /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 000000000..92f251165
--- /dev/null
+++ b/dom/base/nsStubDocumentObserver.cpp
@@ -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/. */
+
+/*
+ * 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)
+NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(nsStubDocumentObserver)
diff --git a/dom/base/nsStubDocumentObserver.h b/dom/base/nsStubDocumentObserver.h
new file mode 100644
index 000000000..1ca626888
--- /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 000000000..4f6b6fa63
--- /dev/null
+++ b/dom/base/nsStubMutationObserver.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/. */
+
+/*
+ * 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"
+
+NS_IMPL_NSIMUTATIONOBSERVER_CORE_STUB(nsStubMutationObserver)
+NS_IMPL_NSIMUTATIONOBSERVER_CONTENT(nsStubMutationObserver)
diff --git a/dom/base/nsStubMutationObserver.h b/dom/base/nsStubMutationObserver.h
new file mode 100644
index 000000000..438a7ae69
--- /dev/null
+++ b/dom/base/nsStubMutationObserver.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/. */
+
+/*
+ * 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 "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
+};
+
+#endif /* !defined(nsStubMutationObserver_h_) */
diff --git a/dom/base/nsStyleLinkElement.cpp b/dom/base/nsStyleLinkElement.cpp
new file mode 100644
index 000000000..8ab2dab0b
--- /dev/null
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -0,0 +1,526 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsStyleLinkElement.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 "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMComment.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMStyleSheet.h"
+#include "nsUnicharUtils.h"
+#include "nsCRT.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsUnicharInputStream.h"
+#include "nsContentUtils.h"
+#include "nsStyleUtil.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsStyleLinkElement::nsStyleLinkElement()
+ : mDontLoadStyle(false)
+ , mUpdatesEnabled(true)
+ , mLineNumber(1)
+{
+}
+
+nsStyleLinkElement::~nsStyleLinkElement()
+{
+ nsStyleLinkElement::SetStyleSheet(nullptr);
+}
+
+void
+nsStyleLinkElement::Unlink()
+{
+ nsStyleLinkElement::SetStyleSheet(nullptr);
+}
+
+void
+nsStyleLinkElement::Traverse(nsCycleCollectionTraversalCallback &cb)
+{
+ nsStyleLinkElement* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet);
+}
+
+NS_IMETHODIMP
+nsStyleLinkElement::SetStyleSheet(StyleSheet* aStyleSheet)
+{
+ if (mStyleSheet) {
+ mStyleSheet->SetOwningNode(nullptr);
+ }
+
+ mStyleSheet = aStyleSheet;
+ if (mStyleSheet) {
+ nsCOMPtr<nsINode> node = do_QueryObject(this);
+ if (node) {
+ mStyleSheet->SetOwningNode(node);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(StyleSheet*)
+nsStyleLinkElement::GetStyleSheet()
+{
+ return mStyleSheet;
+}
+
+NS_IMETHODIMP
+nsStyleLinkElement::InitStyleLinkElement(bool aDontLoadStyle)
+{
+ mDontLoadStyle = aDontLoadStyle;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleLinkElement::SetEnableUpdates(bool aEnableUpdates)
+{
+ mUpdatesEnabled = aEnableUpdates;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleLinkElement::GetCharset(nsAString& aCharset)
+{
+ // descendants have to implement this themselves
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* virtual */ void
+nsStyleLinkElement::OverrideBaseURI(nsIURI* aNewBaseURI)
+{
+ NS_NOTREACHED("Base URI can't be overriden in this implementation "
+ "of nsIStyleSheetLinkingElement.");
+}
+
+/* virtual */ void
+nsStyleLinkElement::SetLineNumber(uint32_t aLineNumber)
+{
+ mLineNumber = aLineNumber;
+}
+
+/* virtual */ uint32_t
+nsStyleLinkElement::GetLineNumber()
+{
+ return mLineNumber;
+}
+
+/* static */ bool
+nsStyleLinkElement::IsImportEnabled()
+{
+ static bool sAdded = false;
+ static bool sImportsEnabled;
+ if (!sAdded) {
+ // This part runs only once because of the static flag.
+ Preferences::AddBoolVarCache(&sImportsEnabled,
+ "dom.htmlimports.enabled",
+ false);
+ sAdded = true;
+ }
+
+ return sImportsEnabled;
+}
+
+static uint32_t ToLinkMask(const nsAString& aLink, nsIPrincipal* aPrincipal)
+{
+ // Keep this in sync with sRelValues in HTMLLinkElement.cpp
+ if (aLink.EqualsLiteral("prefetch"))
+ return nsStyleLinkElement::ePREFETCH;
+ else if (aLink.EqualsLiteral("dns-prefetch"))
+ return nsStyleLinkElement::eDNS_PREFETCH;
+ else if (aLink.EqualsLiteral("stylesheet"))
+ return nsStyleLinkElement::eSTYLESHEET;
+ else if (aLink.EqualsLiteral("next"))
+ return nsStyleLinkElement::eNEXT;
+ else if (aLink.EqualsLiteral("alternate"))
+ return nsStyleLinkElement::eALTERNATE;
+ else if (aLink.EqualsLiteral("import") &&
+ nsStyleLinkElement::IsImportEnabled())
+ return nsStyleLinkElement::eHTMLIMPORT;
+ else if (aLink.EqualsLiteral("preconnect"))
+ return nsStyleLinkElement::ePRECONNECT;
+ else
+ return 0;
+}
+
+uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes, nsIPrincipal* aPrincipal)
+{
+ 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, aPrincipal);
+ inString = false;
+ }
+ }
+ else {
+ if (!inString) {
+ start = current;
+ inString = true;
+ }
+ }
+ ++current;
+ }
+ if (inString) {
+ nsContentUtils::ASCIIToLower(Substring(start, current), subString);
+ linkMask |= ToLinkMask(subString, aPrincipal);
+ }
+ return linkMask;
+}
+
+NS_IMETHODIMP
+nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver* aObserver,
+ bool* aWillNotify,
+ bool* aIsAlternate,
+ bool aForceReload)
+{
+ if (aForceReload) {
+ // We remove this stylesheet from the cache to load a new version.
+ nsCOMPtr<nsIContent> thisContent;
+ CallQueryInterface(this, getter_AddRefs(thisContent));
+ nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ?
+ thisContent->OwnerDoc() : thisContent->GetUncomposedDoc();
+ if (doc && doc->CSSLoader()->GetEnabled() &&
+ mStyleSheet && !mStyleSheet->IsInline()) {
+ doc->CSSLoader()->ObsoleteSheet(mStyleSheet->GetOriginalURI());
+ }
+ }
+ return DoUpdateStyleSheet(nullptr, nullptr, aObserver, aWillNotify,
+ aIsAlternate, aForceReload);
+}
+
+nsresult
+nsStyleLinkElement::UpdateStyleSheetInternal(nsIDocument *aOldDocument,
+ ShadowRoot *aOldShadowRoot,
+ bool aForceUpdate)
+{
+ bool notify, alternate;
+ return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr, &notify,
+ &alternate, aForceUpdate);
+}
+
+static bool
+IsScopedStyleElement(nsIContent* aContent)
+{
+ // This is quicker than, say, QIing aContent to nsStyleLinkElement
+ // and then calling its virtual GetStyleSheetInfo method to find out
+ // if it is scoped.
+ return (aContent->IsHTMLElement(nsGkAtoms::style) ||
+ aContent->IsSVGElement(nsGkAtoms::style)) &&
+ aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scoped);
+}
+
+static bool
+HasScopedStyleSheetChild(nsIContent* aContent)
+{
+ for (nsIContent* n = aContent->GetFirstChild(); n; n = n->GetNextSibling()) {
+ if (IsScopedStyleElement(n)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Called when aElement has had a <style scoped> child removed.
+static void
+UpdateIsElementInStyleScopeFlagOnSubtree(Element* aElement)
+{
+ NS_ASSERTION(aElement->IsElementInStyleScope(),
+ "only call UpdateIsElementInStyleScopeFlagOnSubtree on a "
+ "subtree that has IsElementInStyleScope boolean flag set");
+
+ if (HasScopedStyleSheetChild(aElement)) {
+ return;
+ }
+
+ aElement->ClearIsElementInStyleScope();
+
+ nsIContent* n = aElement->GetNextNode(aElement);
+ while (n) {
+ if (HasScopedStyleSheetChild(n)) {
+ n = n->GetNextNonChildNode(aElement);
+ } else {
+ if (n->IsElement()) {
+ n->ClearIsElementInStyleScope();
+ }
+ n = n->GetNextNode(aElement);
+ }
+ }
+}
+
+nsresult
+nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument,
+ ShadowRoot* aOldShadowRoot,
+ nsICSSLoaderObserver* aObserver,
+ bool* aWillNotify,
+ bool* aIsAlternate,
+ bool aForceUpdate)
+{
+ *aWillNotify = false;
+
+ nsCOMPtr<nsIContent> thisContent;
+ CallQueryInterface(this, getter_AddRefs(thisContent));
+
+ // All instances of nsStyleLinkElement should implement nsIContent.
+ NS_ENSURE_TRUE(thisContent, NS_ERROR_FAILURE);
+
+ if (thisContent->IsInAnonymousSubtree() &&
+ thisContent->IsAnonymousContentInSVGUseSubtree()) {
+ // Stylesheets in <use>-cloned subtrees are disabled until we figure out
+ // how they should behave.
+ return NS_OK;
+ }
+
+ // Check for a ShadowRoot because link elements are inert in a
+ // ShadowRoot.
+ ShadowRoot* containingShadow = thisContent->GetContainingShadow();
+ if (thisContent->IsHTMLElement(nsGkAtoms::link) &&
+ (aOldShadowRoot || containingShadow)) {
+ return NS_OK;
+ }
+
+ // XXXheycam ServoStyleSheets do not support <style scoped>.
+ Element* oldScopeElement = nullptr;
+ if (mStyleSheet) {
+ if (mStyleSheet->IsServo()) {
+ NS_WARNING("stylo: ServoStyleSheets don't support <style scoped>");
+ } else {
+ oldScopeElement = mStyleSheet->AsGecko()->GetScopeElement();
+ }
+ }
+
+ 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 (aOldShadowRoot) {
+ aOldShadowRoot->RemoveSheet(mStyleSheet);
+ } else {
+ aOldDocument->BeginUpdate(UPDATE_STYLE);
+ aOldDocument->RemoveStyleSheet(mStyleSheet);
+ aOldDocument->EndUpdate(UPDATE_STYLE);
+ }
+
+ nsStyleLinkElement::SetStyleSheet(nullptr);
+ if (oldScopeElement) {
+ UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement);
+ }
+ }
+
+ // When static documents are created, stylesheets are cloned manually.
+ if (mDontLoadStyle || !mUpdatesEnabled ||
+ thisContent->OwnerDoc()->IsStaticDocument()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ?
+ thisContent->OwnerDoc() : thisContent->GetUncomposedDoc();
+ if (!doc || !doc->CSSLoader()->GetEnabled()) {
+ return NS_OK;
+ }
+
+ bool isInline;
+ nsCOMPtr<nsIURI> uri = GetStyleSheetURL(&isInline);
+
+ if (!aForceUpdate && mStyleSheet && !isInline && uri) {
+ nsIURI* oldURI = mStyleSheet->GetSheetURI();
+ if (oldURI) {
+ bool equal;
+ nsresult rv = oldURI->Equals(uri, &equal);
+ if (NS_SUCCEEDED(rv) && equal) {
+ return NS_OK; // We already loaded this stylesheet
+ }
+ }
+ }
+
+ if (mStyleSheet) {
+ if (thisContent->IsInShadowTree()) {
+ ShadowRoot* containingShadow = thisContent->GetContainingShadow();
+ containingShadow->RemoveSheet(mStyleSheet);
+ } else {
+ doc->BeginUpdate(UPDATE_STYLE);
+ doc->RemoveStyleSheet(mStyleSheet);
+ doc->EndUpdate(UPDATE_STYLE);
+ }
+
+ nsStyleLinkElement::SetStyleSheet(nullptr);
+ }
+
+ if (!uri && !isInline) {
+ return NS_OK; // If href is empty and this is not inline style then just bail
+ }
+
+ nsAutoString title, type, media;
+ bool isScoped;
+ bool isAlternate;
+
+ GetStyleSheetInfo(title, type, media, &isScoped, &isAlternate);
+
+ if (!type.LowerCaseEqualsLiteral("text/css")) {
+ return NS_OK;
+ }
+
+ Element* scopeElement = isScoped ? thisContent->GetParentElement() : nullptr;
+ if (scopeElement) {
+ NS_ASSERTION(isInline, "non-inline style must not have scope element");
+ scopeElement->SetIsElementInStyleScopeFlagOnSubtree(true);
+ }
+
+ bool doneLoading = false;
+ nsresult rv = NS_OK;
+ if (isInline) {
+ nsAutoString text;
+ if (!nsContentUtils::GetNodeTextContent(thisContent, false, text, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ MOZ_ASSERT(thisContent->NodeInfo()->NameAtom() != nsGkAtoms::link,
+ "<link> is not 'inline', and needs different CSP checks");
+ if (!nsStyleUtil::CSPAllowsInlineStyle(thisContent,
+ thisContent->NodePrincipal(),
+ doc->GetDocumentURI(),
+ mLineNumber, text, &rv))
+ return rv;
+
+ // Parse the style sheet.
+ rv = doc->CSSLoader()->
+ LoadInlineStyle(thisContent, text, mLineNumber, title, media,
+ scopeElement, aObserver, &doneLoading, &isAlternate);
+ }
+ else {
+ nsAutoString integrity;
+ thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
+ if (!integrity.IsEmpty()) {
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
+ ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
+ NS_ConvertUTF16toUTF8(integrity).get()));
+ }
+
+ // if referrer attributes are enabled in preferences, load the link's referrer
+ // attribute. If the link does not provide a referrer attribute, ignore this
+ // and use the document's referrer policy
+
+ net::ReferrerPolicy referrerPolicy = GetLinkReferrerPolicy();
+ if (referrerPolicy == net::RP_Unset) {
+ referrerPolicy = doc->GetReferrerPolicy();
+ }
+
+ // XXXbz clone the URI here to work around content policies modifying URIs.
+ nsCOMPtr<nsIURI> clonedURI;
+ uri->Clone(getter_AddRefs(clonedURI));
+ NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
+ rv = doc->CSSLoader()->
+ LoadStyleLink(thisContent, clonedURI, title, media, isAlternate,
+ GetCORSMode(), referrerPolicy, integrity,
+ aObserver, &isAlternate);
+ if (NS_FAILED(rv)) {
+ // 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.
+ doneLoading = true;
+ isAlternate = false;
+ rv = NS_OK;
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aWillNotify = !doneLoading;
+ *aIsAlternate = isAlternate;
+
+ return NS_OK;
+}
+
+void
+nsStyleLinkElement::UpdateStyleSheetScopedness(bool aIsNowScoped)
+{
+ if (!mStyleSheet) {
+ return;
+ }
+
+ if (mStyleSheet->IsServo()) {
+ // XXXheycam ServoStyleSheets don't support <style scoped>.
+ NS_ERROR("stylo: ServoStyleSheets don't support <style scoped>");
+ return;
+ }
+
+ CSSStyleSheet* sheet = mStyleSheet->AsGecko();
+
+ nsCOMPtr<nsIContent> thisContent;
+ CallQueryInterface(this, getter_AddRefs(thisContent));
+
+ Element* oldScopeElement = sheet->GetScopeElement();
+ Element* newScopeElement = aIsNowScoped ?
+ thisContent->GetParentElement() :
+ nullptr;
+
+ if (oldScopeElement == newScopeElement) {
+ return;
+ }
+
+ nsIDocument* document = thisContent->GetOwnerDocument();
+
+ if (thisContent->IsInShadowTree()) {
+ ShadowRoot* containingShadow = thisContent->GetContainingShadow();
+ containingShadow->RemoveSheet(mStyleSheet);
+
+ sheet->SetScopeElement(newScopeElement);
+
+ containingShadow->InsertSheet(mStyleSheet, thisContent);
+ } else {
+ document->BeginUpdate(UPDATE_STYLE);
+ document->RemoveStyleSheet(mStyleSheet);
+
+ sheet->SetScopeElement(newScopeElement);
+
+ document->AddStyleSheet(mStyleSheet);
+ document->EndUpdate(UPDATE_STYLE);
+ }
+
+ if (oldScopeElement) {
+ UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement);
+ }
+ if (newScopeElement) {
+ newScopeElement->SetIsElementInStyleScopeFlagOnSubtree(true);
+ }
+}
diff --git a/dom/base/nsStyleLinkElement.h b/dom/base/nsStyleLinkElement.h
new file mode 100644
index 000000000..a41ae5e1d
--- /dev/null
+++ b/dom/base/nsStyleLinkElement.h
@@ -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/. */
+
+/*
+ * A base class which implements nsIStyleSheetLinkingElement and can
+ * be subclassed by various content nodes that want to load
+ * stylesheets (<style>, <link>, processing instructions, etc).
+ */
+
+#ifndef nsStyleLinkElement_h___
+#define nsStyleLinkElement_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "nsCOMPtr.h"
+#include "nsIStyleSheetLinkingElement.h"
+#include "nsTArray.h"
+
+class nsIDocument;
+class nsIURI;
+
+namespace mozilla {
+class CSSStyleSheet;
+namespace dom {
+class ShadowRoot;
+} // namespace dom
+} // namespace mozilla
+
+class nsStyleLinkElement : public nsIStyleSheetLinkingElement
+{
+public:
+ nsStyleLinkElement();
+ virtual ~nsStyleLinkElement();
+
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override = 0;
+
+ mozilla::StyleSheet* GetSheet() const { return mStyleSheet; }
+
+ // nsIStyleSheetLinkingElement
+ NS_IMETHOD SetStyleSheet(mozilla::StyleSheet* aStyleSheet) override;
+ NS_IMETHOD_(mozilla::StyleSheet*) GetStyleSheet() override;
+ NS_IMETHOD InitStyleLinkElement(bool aDontLoadStyle) override;
+ NS_IMETHOD UpdateStyleSheet(nsICSSLoaderObserver* aObserver,
+ bool* aWillNotify,
+ bool* aIsAlternate,
+ bool aForceReload) override;
+ NS_IMETHOD SetEnableUpdates(bool aEnableUpdates) override;
+ NS_IMETHOD GetCharset(nsAString& aCharset) override;
+
+ virtual void OverrideBaseURI(nsIURI* aNewBaseURI) override;
+ virtual void SetLineNumber(uint32_t aLineNumber) override;
+ virtual uint32_t GetLineNumber() override;
+
+ enum RelValue {
+ ePREFETCH = 0x00000001,
+ eDNS_PREFETCH = 0x00000002,
+ eSTYLESHEET = 0x00000004,
+ eNEXT = 0x00000008,
+ eALTERNATE = 0x00000010,
+ eHTMLIMPORT = 0x00000020,
+ ePRECONNECT = 0x00000040
+ };
+
+ // The return value is a bitwise or of 0 or more RelValues.
+ // aPrincipal is used to check if HTML imports is enabled for the
+ // provided principal.
+ static uint32_t ParseLinkTypes(const nsAString& aTypes,
+ nsIPrincipal* aPrincipal);
+
+ static bool IsImportEnabled();
+
+ void UpdateStyleSheetInternal()
+ {
+ UpdateStyleSheetInternal(nullptr, nullptr);
+ }
+protected:
+ /**
+ * @param aOldDocument should be non-null only if we're updating because we
+ * removed the node from the document.
+ * @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.
+ */
+ nsresult UpdateStyleSheetInternal(nsIDocument *aOldDocument,
+ mozilla::dom::ShadowRoot *aOldShadowRoot,
+ bool aForceUpdate = false);
+
+ void UpdateStyleSheetScopedness(bool aIsNowScoped);
+
+ virtual already_AddRefed<nsIURI> GetStyleSheetURL(bool* aIsInline) = 0;
+ virtual void GetStyleSheetInfo(nsAString& aTitle,
+ nsAString& aType,
+ nsAString& aMedia,
+ bool* aIsScoped,
+ bool* aIsAlternate) = 0;
+
+ virtual mozilla::CORSMode GetCORSMode() const
+ {
+ // Default to no CORS
+ return mozilla::CORS_NONE;
+ }
+
+ virtual mozilla::net::ReferrerPolicy GetLinkReferrerPolicy()
+ {
+ return mozilla::net::RP_Unset;
+ }
+
+ // CC methods
+ void Unlink();
+ void Traverse(nsCycleCollectionTraversalCallback &cb);
+
+private:
+ /**
+ * @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.
+ */
+ nsresult DoUpdateStyleSheet(nsIDocument* aOldDocument,
+ mozilla::dom::ShadowRoot* aOldShadowRoot,
+ nsICSSLoaderObserver* aObserver,
+ bool* aWillNotify,
+ bool* aIsAlternate,
+ bool aForceUpdate);
+
+ RefPtr<mozilla::StyleSheet> mStyleSheet;
+protected:
+ bool mDontLoadStyle;
+ bool mUpdatesEnabled;
+ uint32_t mLineNumber;
+};
+
+#endif /* nsStyleLinkElement_h___ */
+
diff --git a/dom/base/nsStyledElement.cpp b/dom/base/nsStyledElement.cpp
new file mode 100644
index 000000000..03d1187ab
--- /dev/null
+++ b/dom/base/nsStyledElement.cpp
@@ -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/. */
+
+#include "nsStyledElement.h"
+#include "nsGkAtoms.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "nsDOMCSSDeclaration.h"
+#include "nsDOMCSSAttrDeclaration.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIDocument.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "nsCSSParser.h"
+#include "mozilla/css/Loader.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsXULElement.h"
+#include "nsContentUtils.h"
+#include "nsStyleUtil.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_QUERY_INTERFACE_INHERITED(nsStyledElement,
+ nsStyledElementBase,
+ nsStyledElement)
+
+//----------------------------------------------------------------------
+// nsIContent methods
+
+bool
+nsStyledElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aAttribute == nsGkAtoms::style && aNamespaceID == kNameSpaceID_None) {
+ SetMayHaveStyle();
+ ParseStyleAttribute(aValue, aResult, false);
+ return true;
+ }
+
+ return nsStyledElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+nsresult
+nsStyledElement::SetInlineStyleDeclaration(DeclarationBlock* aDeclaration,
+ const nsAString* aSerialized,
+ bool aNotify)
+{
+ SetMayHaveStyle();
+ bool modification = false;
+ nsAttrValue oldValue;
+
+ bool hasListeners = aNotify &&
+ nsContentUtils::HasMutationListeners(this,
+ NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
+ this);
+
+ // There's no point in comparing the stylerule pointers since we're always
+ // getting a new stylerule here. And we can't compare the stringvalues of
+ // the old and the new rules since both will point to the same declaration
+ // and thus will be the same.
+ if (hasListeners) {
+ // save the old attribute so we can set up the mutation event properly
+ // XXXbz if the old rule points to the same declaration as the new one,
+ // this is getting the new attr value, not the old one....
+ nsAutoString oldValueStr;
+ modification = GetAttr(kNameSpaceID_None, nsGkAtoms::style,
+ oldValueStr);
+ if (modification) {
+ oldValue.SetTo(oldValueStr);
+ }
+ }
+ else if (aNotify && IsInUncomposedDoc()) {
+ modification = !!mAttrsAndChildren.GetAttr(nsGkAtoms::style);
+ }
+
+ nsAttrValue attrValue(do_AddRef(aDeclaration), aSerialized);
+
+ // XXXbz do we ever end up with ADDITION here? I doubt it.
+ uint8_t modType = modification ?
+ static_cast<uint8_t>(nsIDOMMutationEvent::MODIFICATION) :
+ static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION);
+
+ return SetAttrAndNotify(kNameSpaceID_None, nsGkAtoms::style, nullptr,
+ oldValue, attrValue, modType, hasListeners,
+ aNotify, kDontCallAfterSetAttr);
+}
+
+DeclarationBlock*
+nsStyledElement::GetInlineStyleDeclaration()
+{
+ if (!MayHaveStyle()) {
+ return nullptr;
+ }
+ const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style);
+
+ if (attrVal && attrVal->Type() == nsAttrValue::eCSSDeclaration) {
+ return attrVal->GetCSSDeclarationValue();
+ }
+
+ return nullptr;
+}
+
+// ---------------------------------------------------------------
+// Others and helpers
+
+nsICSSDeclaration*
+nsStyledElement::Style()
+{
+ Element::nsDOMSlots *slots = DOMSlots();
+
+ if (!slots->mStyle) {
+ // Just in case...
+ ReparseStyleAttribute(true);
+
+ slots->mStyle = new nsDOMCSSAttributeDeclaration(this, false);
+ SetMayHaveStyle();
+ }
+
+ return slots->mStyle;
+}
+
+nsresult
+nsStyledElement::ReparseStyleAttribute(bool aForceInDataDoc)
+{
+ if (!MayHaveStyle()) {
+ return NS_OK;
+ }
+ const nsAttrValue* oldVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style);
+ if (oldVal && oldVal->Type() != nsAttrValue::eCSSDeclaration) {
+ nsAttrValue attrValue;
+ nsAutoString stringValue;
+ oldVal->ToString(stringValue);
+ ParseStyleAttribute(stringValue, attrValue, aForceInDataDoc);
+ // Don't bother going through SetInlineStyleDeclaration; we don't
+ // want to fire off mutation events or document notifications anyway
+ nsresult rv = mAttrsAndChildren.SetAndSwapAttr(nsGkAtoms::style, attrValue);
+ 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,
+ nsAttrValue& aResult,
+ bool aForceInDataDoc)
+{
+ nsIDocument* doc = OwnerDoc();
+ bool isNativeAnon = IsInNativeAnonymousSubtree();
+
+ if (!isNativeAnon &&
+ !nsStyleUtil::CSPAllowsInlineStyle(nullptr, NodePrincipal(),
+ doc->GetDocumentURI(), 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()) {
+ static const char textCssStr[] = "text/css";
+ isCSS = (styleType.EqualsIgnoreCase(textCssStr, sizeof(textCssStr) - 1));
+ }
+ }
+
+ if (isCSS && aResult.ParseStyleAttribute(aValue, this)) {
+ return;
+ }
+ }
+
+ aResult.SetTo(aValue);
+}
diff --git a/dom/base/nsStyledElement.h b/dom/base/nsStyledElement.h
new file mode 100644
index 000000000..c4894d87f
--- /dev/null
+++ b/dom/base/nsStyledElement.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/. */
+
+/**
+ * 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 "nsString.h"
+#include "mozilla/dom/Element.h"
+
+namespace mozilla {
+class DeclarationBlock;
+} // namespace mozilla
+
+// IID for nsStyledElement interface
+#define NS_STYLED_ELEMENT_IID \
+{ 0xacbd9ea6, 0x15aa, 0x4f37, \
+ { 0x8c, 0xe0, 0x35, 0x1e, 0xd7, 0x21, 0xca, 0xe9 } }
+
+typedef mozilla::dom::Element nsStyledElementBase;
+
+class nsStyledElement : public nsStyledElementBase
+{
+
+protected:
+
+ inline explicit nsStyledElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsStyledElementBase(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 mozilla::DeclarationBlock* GetInlineStyleDeclaration() override;
+ virtual nsresult SetInlineStyleDeclaration(mozilla::DeclarationBlock* aDeclaration,
+ const nsAString* aSerialized,
+ bool aNotify) override;
+
+ nsICSSDeclaration* Style();
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_STYLED_ELEMENT_IID)
+
+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 aResult the resulting HTMLValue [OUT]
+ */
+ void ParseStyleAttribute(const nsAString& aValue,
+ nsAttrValue& aResult,
+ bool aForceInDataDoc);
+
+ virtual bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
+ const nsAString& aValue, 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);
+};
+
+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 000000000..e0850d7b0
--- /dev/null
+++ b/dom/base/nsSyncLoadService.cpp
@@ -0,0 +1,394 @@
+/* -*- 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/. */
+
+/*
+ * 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 "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIPrincipal.h"
+#include "nsContentUtils.h" // for kLoadAsData
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include <algorithm>
+
+using mozilla::net::ReferrerPolicy;
+
+/**
+ * This class manages loading a single XML document
+ */
+
+class nsSyncLoader : public nsIStreamListener,
+ public nsIChannelEventSink,
+ public nsIInterfaceRequestor,
+ public nsSupportsWeakReference
+{
+public:
+ nsSyncLoader() : mLoading(false) {}
+
+ NS_DECL_ISUPPORTS
+
+ nsresult LoadDocument(nsIChannel* aChannel,
+ bool aChannelIsSync, bool aForceToXML,
+ ReferrerPolicy aReferrerPolicy,
+ nsIDOMDocument** 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()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsForceXMLListener,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+NS_IMETHODIMP
+nsForceXMLListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ nsresult status;
+ aRequest->GetStatus(&status);
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel && NS_SUCCEEDED(status)) {
+ channel->SetContentType(NS_LITERAL_CSTRING("text/xml"));
+ }
+
+ return mListener->OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP
+nsForceXMLListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ return mListener->OnStopRequest(aRequest, aContext, aStatusCode);
+}
+
+nsSyncLoader::~nsSyncLoader()
+{
+ if (mLoading && mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSyncLoader,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIChannelEventSink,
+ nsIInterfaceRequestor,
+ nsISupportsWeakReference)
+
+nsresult
+nsSyncLoader::LoadDocument(nsIChannel* aChannel,
+ bool aChannelIsSync,
+ bool aForceToXML,
+ ReferrerPolicy aReferrerPolicy,
+ nsIDOMDocument **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) {
+ http->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
+ NS_LITERAL_CSTRING("text/xml,application/xml,application/xhtml+xml,*/*;q=0.1"),
+ false);
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ if (loadInfo) {
+ nsCOMPtr<nsIURI> loaderUri;
+ loadInfo->TriggeringPrincipal()->GetURI(getter_AddRefs(loaderUri));
+ if (loaderUri) {
+ http->SetReferrerWithPolicy(loaderUri, aReferrerPolicy);
+ }
+ }
+ }
+
+ // 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<nsIDocument> 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);
+
+ return CallQueryInterface(document, aResult);
+}
+
+nsresult
+nsSyncLoader::PushAsyncStream(nsIStreamListener* aListener)
+{
+ mListener = aListener;
+
+ mAsyncLoadStatus = NS_OK;
+
+ // Start reading from the channel
+ nsresult rv = mChannel->AsyncOpen2(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->Open2(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLoading = true;
+ rv = nsSyncLoadService::PushSyncStreamToListener(in, aListener, mChannel);
+ mLoading = false;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSyncLoader::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ return mListener->OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP
+nsSyncLoader::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ if (NS_SUCCEEDED(mAsyncLoadStatus) && NS_FAILED(aStatusCode)) {
+ mAsyncLoadStatus = aStatusCode;
+ }
+ nsresult rv = mListener->OnStopRequest(aRequest, aContext, 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)
+{
+ NS_PRECONDITION(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,
+ bool aForceToXML,
+ ReferrerPolicy aReferrerPolicy,
+ nsIDOMDocument** aResult)
+{
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+ aURI,
+ aLoaderPrincipal,
+ aSecurityFlags,
+ aContentPolicyType,
+ aLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aForceToXML) {
+ channel->SetContentType(NS_LITERAL_CSTRING("text/xml"));
+ }
+
+ bool isChrome = false, isResource = false;
+ // if the load needs to enforce CORS, then force the load to be async
+ bool isSync =
+ !(aSecurityFlags & nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) &&
+ ((NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) ||
+ (NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)) && isResource));
+ RefPtr<nsSyncLoader> loader = new nsSyncLoader();
+ return loader->LoadDocument(channel, isSync, aForceToXML,
+ aReferrerPolicy, aResult);
+}
+
+/* static */
+nsresult
+nsSyncLoadService::PushSyncStreamToListener(nsIInputStream* aIn,
+ nsIStreamListener* aListener,
+ nsIChannel* aChannel)
+{
+ // Set up buffering stream
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ if (!NS_InputStreamIsBuffered(aIn)) {
+ 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), aIn,
+ chunkSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aIn = bufferedStream;
+ }
+
+ // Load
+ rv = aListener->OnStartRequest(aChannel, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ uint64_t sourceOffset = 0;
+ while (1) {
+ uint64_t readCount = 0;
+ rv = aIn->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, nullptr, aIn,
+ (uint32_t)std::min(sourceOffset, (uint64_t)UINT32_MAX),
+ (uint32_t)readCount);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ sourceOffset += readCount;
+ }
+ }
+ if (NS_FAILED(rv)) {
+ aChannel->Cancel(rv);
+ }
+ aListener->OnStopRequest(aChannel, nullptr, rv);
+
+ return rv;
+}
diff --git a/dom/base/nsSyncLoadService.h b/dom/base/nsSyncLoadService.h
new file mode 100644
index 000000000..b9297f8bc
--- /dev/null
+++ b/dom/base/nsSyncLoadService.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/. */
+
+/*
+ * A service that provides methods for synchronously loading a DOM in various ways.
+ */
+
+#ifndef nsSyncLoadService_h__
+#define nsSyncLoadService_h__
+
+#include "nscore.h"
+#include "mozilla/net/ReferrerPolicy.h"
+
+class nsIInputStream;
+class nsILoadGroup;
+class nsIStreamListener;
+class nsIURI;
+class nsIPrincipal;
+class nsIDOMDocument;
+class nsIChannel;
+
+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,
+ bool aForceToXML,
+ mozilla::net::ReferrerPolicy aReferrerPolicy,
+ nsIDOMDocument** aResult);
+
+ /**
+ * Read input stream aIn in chunks and deliver synchronously to aListener.
+ *
+ * @param aIn The stream to be read.
+ * @param aListener The listener that will receive
+ * OnStartRequest/OnDataAvailable/OnStopRequest
+ * notifications.
+ * @param aChannel The channel that aIn was opened from.
+ */
+ static nsresult PushSyncStreamToListener(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 000000000..260e254be
--- /dev/null
+++ b/dom/base/nsTextFragment.cpp
@@ -0,0 +1,470 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsMemory.h"
+#include "nsBidiUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsUTF8Utils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/SSE.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 mozilla::CheckedUint32;
+
+// 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.mLength && m1b && mState.mInHeap) {
+ free(m2b); // m1b == m2b as far as free is concerned
+ }
+
+ 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) {
+ m1b = aOther.m1b; // This will work even if aOther is using m2b
+ }
+ else {
+ CheckedUint32 m2bSize = aOther.mState.mLength;
+ m2bSize *= (aOther.mState.mIs2b ? sizeof(char16_t) : sizeof(char));
+ m2b = nullptr;
+ if (m2bSize.isValid()) {
+ m2b = static_cast<char16_t*>(malloc(m2bSize.value()));
+ }
+
+ if (m2b) {
+ memcpy(m2b, aOther.m2b, m2bSize.value());
+ } else {
+ // allocate a buffer for a single REPLACEMENT CHARACTER
+ m2b = static_cast<char16_t*>(moz_xmalloc(sizeof(char16_t)));
+ m2b[0] = 0xFFFD; // REPLACEMENT CHARACTER
+ mState.mIs2b = true;
+ mState.mInHeap = true;
+ mState.mLength = 1;
+ }
+ }
+
+ if (m1b) {
+ mAllBits = aOther.mAllBits;
+ }
+ }
+
+ return *this;
+}
+
+static inline int32_t
+FirstNon8BitUnvectorized(const char16_t *str, const char16_t *end)
+{
+ typedef Non8BitParameters<sizeof(size_t)> p;
+ 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;
+}
+
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+namespace mozilla {
+ namespace SSE2 {
+ int32_t FirstNon8Bit(const char16_t *str, const char16_t *end);
+ } // namespace SSE2
+} // 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::SSE2::FirstNon8Bit(str, end);
+ }
+#endif
+
+ return FirstNon8BitUnvectorized(str, end);
+}
+
+bool
+nsTextFragment::SetTo(const char16_t* aBuffer, int32_t aLength, bool aUpdateBidi)
+{
+ ReleaseText();
+
+ if (aLength == 0) {
+ return true;
+ }
+
+ char16_t firstChar = *aBuffer;
+ if (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 (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 = FirstNon8Bit(ucp, uend);
+
+ if (first16bit != -1) { // aBuffer contains no non-8bit character
+ // Use ucs2 storage because we have to
+ CheckedUint32 m2bSize = aLength;
+ m2bSize *= sizeof(char16_t);
+ if (!m2bSize.isValid()) {
+ return false;
+ }
+
+ m2b = static_cast<char16_t*>(malloc(m2bSize.value()));
+ if (!m2b) {
+ return false;
+ }
+ memcpy(m2b, aBuffer, m2bSize.value());
+
+ 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
+ LossyConvertEncoding16to8 converter(buff);
+ copy_string(aBuffer, aBuffer+aLength, converter);
+ m1b = buff;
+ mState.mIs2b = false;
+ }
+
+ // Setup our fields
+ mState.mInHeap = true;
+ mState.mLength = aLength;
+
+ return true;
+}
+
+void
+nsTextFragment::CopyTo(char16_t *aDest, int32_t aOffset, int32_t aCount)
+{
+ NS_ASSERTION(aOffset >= 0, "Bad offset passed to nsTextFragment::CopyTo()!");
+ NS_ASSERTION(aCount >= 0, "Bad count passed to nsTextFragment::CopyTo()!");
+
+ if (aOffset < 0) {
+ aOffset = 0;
+ }
+
+ if (uint32_t(aOffset + aCount) > GetLength()) {
+ aCount = mState.mLength - aOffset;
+ }
+
+ if (aCount != 0) {
+ if (mState.mIs2b) {
+ memcpy(aDest, m2b + aOffset, sizeof(char16_t) * aCount);
+ } else {
+ const char *cp = m1b + aOffset;
+ const char *end = cp + aCount;
+ LossyConvertEncoding8to16 converter(aDest);
+ copy_string(cp, end, converter);
+ }
+ }
+}
+
+bool
+nsTextFragment::Append(const char16_t* aBuffer, uint32_t aLength, bool aUpdateBidi)
+{
+ // 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);
+ }
+
+ // Should we optimize for aData.Length() == 0?
+
+ CheckedUint32 length = mState.mLength;
+ length += aLength;
+
+ if (!length.isValid()) {
+ return false;
+ }
+
+ if (mState.mIs2b) {
+ length *= sizeof(char16_t);
+ if (!length.isValid()) {
+ return false;
+ }
+
+ // Already a 2-byte string so the result will be too
+ char16_t* buff = static_cast<char16_t*>(realloc(m2b, length.value()));
+ if (!buff) {
+ return false;
+ }
+
+ memcpy(buff + mState.mLength, aBuffer, aLength * sizeof(char16_t));
+ mState.mLength += aLength;
+ m2b = buff;
+
+ 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 = FirstNon8Bit(aBuffer, aBuffer + aLength);
+
+ if (first16bit != -1) { // aBuffer contains no non-8bit character
+ length *= sizeof(char16_t);
+ if (!length.isValid()) {
+ return false;
+ }
+
+ // The old data was 1-byte, but the new is not so we have to expand it
+ // all to 2-byte
+ char16_t* buff = static_cast<char16_t*>(malloc(length.value()));
+ if (!buff) {
+ return false;
+ }
+
+ // Copy data into buff
+ LossyConvertEncoding8to16 converter(buff);
+ copy_string(m1b, m1b+mState.mLength, converter);
+
+ memcpy(buff + mState.mLength, aBuffer, aLength * sizeof(char16_t));
+ mState.mLength += aLength;
+ mState.mIs2b = true;
+
+ if (mState.mInHeap) {
+ free(m2b);
+ }
+ m2b = buff;
+
+ mState.mInHeap = true;
+
+ if (aUpdateBidi) {
+ UpdateBidiFlag(aBuffer + first16bit, aLength - first16bit);
+ }
+
+ return true;
+ }
+
+ // The new and the old data is all 1-byte
+ char* buff;
+ if (mState.mInHeap) {
+ buff = static_cast<char*>(realloc(const_cast<char*>(m1b), length.value()));
+ if (!buff) {
+ return false;
+ }
+ }
+ else {
+ buff = static_cast<char*>(malloc(length.value()));
+ if (!buff) {
+ return false;
+ }
+
+ memcpy(buff, m1b, mState.mLength);
+ mState.mInHeap = true;
+ }
+
+ // Copy aBuffer into buff.
+ LossyConvertEncoding16to8 converter(buff + mState.mLength);
+ copy_string(aBuffer, aBuffer + aLength, converter);
+
+ m1b = buff;
+ mState.mLength += aLength;
+
+ return true;
+}
+
+/* virtual */ size_t
+nsTextFragment::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ if (Is2b()) {
+ return aMallocSizeOf(m2b);
+ }
+
+ 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) {
+ const char16_t* cp = aBuffer;
+ const char16_t* end = cp + aLength;
+ while (cp < end) {
+ char16_t ch1 = *cp++;
+ uint32_t utf32Char = ch1;
+ if (NS_IS_HIGH_SURROGATE(ch1) &&
+ cp < end &&
+ NS_IS_LOW_SURROGATE(*cp)) {
+ char16_t ch2 = *cp++;
+ utf32Char = SURROGATE_TO_UCS4(ch1, ch2);
+ }
+ if (UTF32_CHAR_IS_BIDI(utf32Char) || IsBidiControlRTL(utf32Char)) {
+ mState.mIsBidi = true;
+ break;
+ }
+ }
+ }
+}
diff --git a/dom/base/nsTextFragment.h b/dom/base/nsTextFragment.h
new file mode 100644
index 000000000..47624d721
--- /dev/null
+++ b/dom/base/nsTextFragment.h
@@ -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/. */
+
+/*
+ * 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 "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsISupportsImpl.h"
+
+class nsString;
+
+// 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
+ {
+ NS_ASSERTION(Is2b(), "not 2b text");
+ return m2b;
+ }
+
+ /**
+ * 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;
+ }
+
+ 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.
+ */
+ bool SetTo(const char16_t* aBuffer, int32_t aLength, bool aUpdateBidi);
+
+ /**
+ * 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.
+ */
+ bool Append(const char16_t* aBuffer, uint32_t aLength, bool aUpdateBidi);
+
+ /**
+ * 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
+ */
+ MOZ_MUST_USE
+ bool AppendTo(nsAString& aString,
+ const mozilla::fallible_t& aFallible) const {
+ if (mState.mIs2b) {
+ bool ok = aString.Append(m2b, 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, int32_t aOffset, int32_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
+ */
+ MOZ_MUST_USE
+ bool AppendTo(nsAString& aString, int32_t aOffset, int32_t aLength,
+ const mozilla::fallible_t& aFallible) const
+ {
+ if (mState.mIs2b) {
+ bool ok = aString.Append(m2b + 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, int32_t aOffset, int32_t aCount);
+
+ /**
+ * Return the character in the text-fragment at the given
+ * index. This always returns a char16_t.
+ */
+ char16_t CharAt(int32_t aIndex) const
+ {
+ NS_ASSERTION(uint32_t(aIndex) < mState.mLength, "bad index");
+ return mState.mIs2b ? m2b[aIndex] : static_cast<unsigned char>(m1b[aIndex]);
+ }
+
+ 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;
+ uint32_t mLength : 29;
+ };
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) 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 {
+ char16_t *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/nsTextFragmentImpl.h b/dom/base/nsTextFragmentImpl.h
new file mode 100644
index 000000000..991c23e10
--- /dev/null
+++ b/dom/base/nsTextFragmentImpl.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 000000000..60f6cf6a1
--- /dev/null
+++ b/dom/base/nsTextFragmentSSE2.cpp
@@ -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/. */
+
+// This file should only be compiled if you're on x86 or x86_64. Additionally,
+// you'll need to compile this file with -msse2 if you're using gcc.
+
+#include <emmintrin.h>
+#include "nscore.h"
+#include "nsAlgorithm.h"
+#include "nsTextFragmentImpl.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace SSE2 {
+
+static inline bool
+is_zero (__m128i x)
+{
+ return
+ _mm_movemask_epi8(_mm_cmpeq_epi8(x, _mm_setzero_si128())) == 0xffff;
+}
+
+int32_t
+FirstNon8Bit(const char16_t *str, const char16_t *end)
+{
+ const uint32_t numUnicharsPerVector = 8;
+ typedef Non8BitParameters<sizeof(size_t)> p;
+ 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 a 16-byte boundary, as required by _mm_load_si128
+ // (i.e. MOVDQA).
+ int32_t alignLen =
+ std::min(len, int32_t(((-NS_PTR_TO_INT32(str)) & 0xf) / sizeof(char16_t)));
+ for (; i < alignLen; i++) {
+ if (str[i] > 255)
+ return i;
+ }
+
+ // Check one XMM register (16 bytes) at a time.
+ const int32_t vectWalkEnd = ((len - i) / numUnicharsPerVector) * numUnicharsPerVector;
+ const uint16_t shortMask = 0xff00;
+ __m128i vectmask = _mm_set1_epi16(static_cast<int16_t>(shortMask));
+ for(; i < vectWalkEnd; i += numUnicharsPerVector) {
+ const __m128i vect = *reinterpret_cast<const __m128i*>(str + i);
+ if (!is_zero(_mm_and_si128(vect, vectmask)))
+ 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 SSE2
+} // namespace mozilla
diff --git a/dom/base/nsTextNode.cpp b/dom/base/nsTextNode.cpp
new file mode 100644
index 000000000..25c2d3525
--- /dev/null
+++ b/dom/base/nsTextNode.cpp
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMText node.
+ */
+
+#include "nsTextNode.h"
+#include "mozilla/dom/TextBinding.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsIDocument.h"
+#include "nsThreadUtils.h"
+#include "nsStubMutationObserver.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#ifdef DEBUG
+#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,
+ nsIAtom* aAttrName) :
+ nsTextNode(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(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ virtual nsGenericDOMDataNode *CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo,
+ bool aCloneText) const override
+ {
+ already_AddRefed<mozilla::dom::NodeInfo> ni =
+ RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ nsAttributeTextNode *it = new nsAttributeTextNode(ni,
+ mNameSpaceID,
+ mAttrName);
+ if (it && aCloneText) {
+ it->mText = mText;
+ }
+
+ return it;
+ }
+
+ // 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.
+ nsIContent* mGrandparent;
+ // What attribute we're showing
+ int32_t mNameSpaceID;
+ nsCOMPtr<nsIAtom> mAttrName;
+};
+
+nsTextNode::~nsTextNode()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsTextNode, nsGenericDOMDataNode, nsIDOMNode,
+ nsIDOMText, nsIDOMCharacterData)
+
+JSObject*
+nsTextNode::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return TextBinding::Wrap(aCx, this, aGivenProto);
+}
+
+bool
+nsTextNode::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~(eCONTENT | eTEXT | eDATA_NODE));
+}
+
+nsGenericDOMDataNode*
+nsTextNode::CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo, bool aCloneText) const
+{
+ already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ nsTextNode *it = new nsTextNode(ni);
+ if (aCloneText) {
+ it->mText = mText;
+ }
+
+ return it;
+}
+
+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(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent, bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericDOMDataNode::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetDirectionFromNewTextNode(this);
+
+ return NS_OK;
+}
+
+void nsTextNode::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ ResetDirectionSetByTextNode(this);
+
+ nsGenericDOMDataNode::UnbindFromTree(aDeep, aNullParent);
+}
+
+#ifdef DEBUG
+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 (IsCommonAncestorForRangeInSelection()) {
+ typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable;
+ RangeHashTable* ranges =
+ static_cast<RangeHashTable*>(GetProperty(nsGkAtoms::range));
+ fprintf(out, " ranges:%d", ranges ? ranges->Count() : 0);
+ }
+ 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, nsIAtom* aAttrName,
+ nsIContent** aResult)
+{
+ NS_PRECONDITION(aNodeInfoManager, "Missing nodeInfoManager");
+ NS_PRECONDITION(aAttrName, "Must have an attr name");
+ NS_PRECONDITION(aNameSpaceID != kNameSpaceID_Unknown, "Must know namespace");
+
+ *aResult = nullptr;
+
+ already_AddRefed<mozilla::dom::NodeInfo> ni = aNodeInfoManager->GetTextNodeInfo();
+
+ nsAttributeTextNode* textNode = new nsAttributeTextNode(ni,
+ aNameSpaceID,
+ aAttrName);
+ NS_ADDREF(*aResult = textNode);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAttributeTextNode, nsTextNode,
+ nsIMutationObserver)
+
+nsresult
+nsAttributeTextNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ NS_PRECONDITION(aParent && aParent->GetParent(),
+ "This node can't be a child of the document or of the document root");
+
+ nsresult rv = nsTextNode::BindToTree(aDocument, aParent,
+ aBindingParent, aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!mGrandparent, "We were already bound!");
+ mGrandparent = aParent->GetParent();
+ 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 aDeep, 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(aDeep, aNullParent);
+}
+
+void
+nsAttributeTextNode::AttributeChanged(nsIDocument* aDocument,
+ Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* 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(this, update));
+ }
+}
+
+void
+nsAttributeTextNode::NodeWillBeDestroyed(const 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 000000000..488540a82
--- /dev/null
+++ b/dom/base/nsTextNode.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 nsTextNode_h
+#define nsTextNode_h
+
+/*
+ * Implementation of DOM Core's nsIDOMText node.
+ */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Text.h"
+#include "nsIDOMText.h"
+#include "nsDebug.h"
+
+class nsNodeInfoManager;
+
+/**
+ * Class used to implement DOM text nodes
+ */
+class nsTextNode : public mozilla::dom::Text,
+ public nsIDOMText
+{
+private:
+ void Init()
+ {
+ MOZ_ASSERT(mNodeInfo->NodeType() == nsIDOMNode::TEXT_NODE,
+ "Bad NodeType in aNodeInfo");
+ }
+
+public:
+ explicit nsTextNode(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : mozilla::dom::Text(aNodeInfo)
+ {
+ Init();
+ }
+
+ explicit nsTextNode(nsNodeInfoManager* aNodeInfoManager)
+ : mozilla::dom::Text(aNodeInfoManager->GetTextNodeInfo())
+ {
+ Init();
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIDOMNode
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+ using mozilla::dom::Text::GetParentElement;
+
+ // nsIDOMCharacterData
+ NS_FORWARD_NSIDOMCHARACTERDATA(nsGenericDOMDataNode::)
+ using nsGenericDOMDataNode::SetData; // Prevent hiding overloaded virtual function.
+
+ // nsIDOMText
+ NS_FORWARD_NSIDOMTEXT(nsGenericDOMDataNode::)
+
+ // nsINode
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+
+ virtual nsGenericDOMDataNode* CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo,
+ bool aCloneText) const override;
+
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep = true,
+ bool aNullParent = true) override;
+
+ nsresult AppendTextForNormalize(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify, nsIContent* aNextSibling);
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+#ifdef DEBUG
+ 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 000000000..2f7594e8e
--- /dev/null
+++ b/dom/base/nsTraversal.cpp
@@ -0,0 +1,80 @@
+/* -*- 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 "nsTraversal.h"
+
+#include "nsIDOMNode.h"
+#include "nsError.h"
+#include "nsINode.h"
+#include "mozilla/AutoRestore.h"
+
+#include "nsGkAtoms.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsTraversal::nsTraversal(nsINode *aRoot,
+ uint32_t aWhatToShow,
+ NodeFilterHolder aFilter) :
+ mRoot(aRoot),
+ mWhatToShow(aWhatToShow),
+ mFilter(Move(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 nsIDOMNodeFilter.idl
+ */
+int16_t
+nsTraversal::TestNode(nsINode* aNode, mozilla::ErrorResult& aResult)
+{
+ 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 nsIDOMNodeFilter::FILTER_SKIP;
+ }
+
+ if (!mFilter.GetISupports()) {
+ // No filter, just accept
+ return nsIDOMNodeFilter::FILTER_ACCEPT;
+ }
+
+ if (mFilter.HasWebIDLCallback()) {
+ 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.GetWebIDLCallback()->
+ AcceptNode(*aNode, aResult, nullptr,
+ CallbackObject::eRethrowExceptions);
+ }
+
+ nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aNode);
+ AutoRestore<bool> inAcceptNode(mInAcceptNode);
+ mInAcceptNode = true;
+ int16_t filtered;
+ nsresult rv = mFilter.GetXPCOMCallback()->AcceptNode(domNode, &filtered);
+ if (NS_FAILED(rv)) {
+ aResult.Throw(rv);
+ }
+ return filtered;
+}
diff --git a/dom/base/nsTraversal.h b/dom/base/nsTraversal.h
new file mode 100644
index 000000000..8b0b55300
--- /dev/null
+++ b/dom/base/nsTraversal.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/. */
+
+/*
+ * Implementation of DOM Traversal's nsIDOMTreeWalker
+ */
+
+#ifndef nsTraversal_h___
+#define nsTraversal_h___
+
+#include "nsCOMPtr.h"
+#include "nsIDocument.h" // for NodeFilterHolder
+#include "mozilla/dom/CallbackObject.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+#include "nsIDOMNodeFilter.h"
+
+class nsINode;
+
+class nsTraversal
+{
+public:
+ nsTraversal(nsINode *aRoot,
+ uint32_t aWhatToShow,
+ mozilla::dom::NodeFilterHolder aFilter);
+ virtual ~nsTraversal();
+
+protected:
+ nsCOMPtr<nsINode> mRoot;
+ uint32_t mWhatToShow;
+ mozilla::dom::NodeFilterHolder 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
+ * @returns Filtervalue. See nsIDOMNodeFilter.idl
+ */
+ int16_t TestNode(nsINode* aNode, mozilla::ErrorResult& aResult);
+};
+
+#endif
+
diff --git a/dom/base/nsTreeSanitizer.cpp b/dom/base/nsTreeSanitizer.cpp
new file mode 100644
index 000000000..dc53ea5fa
--- /dev/null
+++ b/dom/base/nsTreeSanitizer.cpp
@@ -0,0 +1,1550 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ArrayUtils.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Declaration.h"
+#include "mozilla/css/StyleRule.h"
+#include "mozilla/css/Rule.h"
+#include "nsCSSParser.h"
+#include "nsCSSPropertyID.h"
+#include "nsUnicharInputStream.h"
+#include "nsIDOMCSSRule.h"
+#include "nsAttrName.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNullPrincipal.h"
+#include "nsContentUtils.h"
+#include "nsIParserUtils.h"
+#include "nsIDocument.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+
+//
+// Thanks to Mark Pilgrim and Sam Ruby for the initial whitelist
+//
+nsIAtom** const kElementsHTML[] = {
+ &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::datalist,
+ &nsGkAtoms::dd,
+ &nsGkAtoms::del,
+ &nsGkAtoms::details,
+ &nsGkAtoms::dfn,
+ &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::label,
+ &nsGkAtoms::legend,
+ &nsGkAtoms::li,
+ &nsGkAtoms::link,
+ &nsGkAtoms::listing,
+ &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::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,
+ &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
+};
+
+nsIAtom** const kAttributesHTML[] = {
+ &nsGkAtoms::abbr,
+ &nsGkAtoms::accept,
+ &nsGkAtoms::acceptcharset,
+ &nsGkAtoms::accesskey,
+ &nsGkAtoms::action,
+ &nsGkAtoms::alt,
+ &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::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::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
+};
+
+nsIAtom** const kPresAttributesHTML[] = {
+ &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
+};
+
+nsIAtom** const kURLAttributesHTML[] = {
+ &nsGkAtoms::action,
+ &nsGkAtoms::href,
+ &nsGkAtoms::src,
+ &nsGkAtoms::longdesc,
+ &nsGkAtoms::cite,
+ &nsGkAtoms::background,
+ nullptr
+};
+
+nsIAtom** 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
+};
+
+nsIAtom** 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
+ // 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::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
+};
+
+nsIAtom** const kURLAttributesSVG[] = {
+ &nsGkAtoms::href,
+ nullptr
+};
+
+nsIAtom** 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::mfenced_, // mfenced
+ &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
+};
+
+nsIAtom** 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
+};
+
+nsIAtom** const kURLAttributesMathML[] = {
+ &nsGkAtoms::href,
+ &nsGkAtoms::src,
+ &nsGkAtoms::cdgroup_,
+ &nsGkAtoms::altimg_,
+ &nsGkAtoms::definitionURL_,
+ nullptr
+};
+
+nsTHashtable<nsISupportsHashKey>* nsTreeSanitizer::sElementsHTML = nullptr;
+nsTHashtable<nsISupportsHashKey>* nsTreeSanitizer::sAttributesHTML = nullptr;
+nsTHashtable<nsISupportsHashKey>* nsTreeSanitizer::sPresAttributesHTML = nullptr;
+nsTHashtable<nsISupportsHashKey>* nsTreeSanitizer::sElementsSVG = nullptr;
+nsTHashtable<nsISupportsHashKey>* nsTreeSanitizer::sAttributesSVG = nullptr;
+nsTHashtable<nsISupportsHashKey>* nsTreeSanitizer::sElementsMathML = nullptr;
+nsTHashtable<nsISupportsHashKey>* nsTreeSanitizer::sAttributesMathML = 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)
+{
+ 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, nsIAtom* aLocal)
+{
+ if (aNamespace == kNameSpaceID_XHTML) {
+ if (mDropNonCSSPresentation && (nsGkAtoms::font == aLocal ||
+ nsGkAtoms::center == aLocal)) {
+ return true;
+ }
+ if (mDropForms && (nsGkAtoms::form == aLocal ||
+ nsGkAtoms::input == aLocal ||
+ nsGkAtoms::keygen == 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;
+ }
+ return !sElementsHTML->GetEntry(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->GetEntry(aLocal);
+ }
+ if (aNamespace == kNameSpaceID_MathML) {
+ return !sElementsMathML->GetEntry(aLocal);
+ }
+ return true;
+}
+
+bool
+nsTreeSanitizer::IsURL(nsIAtom*** aURLs, nsIAtom* aLocalName)
+{
+ nsIAtom** atomPtrPtr;
+ while ((atomPtrPtr = *aURLs)) {
+ if (*atomPtrPtr == aLocalName) {
+ return true;
+ }
+ ++aURLs;
+ }
+ return false;
+}
+
+bool
+nsTreeSanitizer::MustPrune(int32_t aNamespace,
+ nsIAtom* aLocal,
+ mozilla::dom::Element* 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) {
+ if (nsGkAtoms::style == aLocal && !(aNamespace == kNameSpaceID_XHTML
+ || aNamespace == kNameSpaceID_SVG)) {
+ return true;
+ }
+ return false;
+ }
+ if (nsGkAtoms::style == aLocal) {
+ return true;
+ }
+ return false;
+}
+
+bool
+nsTreeSanitizer::SanitizeStyleDeclaration(mozilla::css::Declaration* aDeclaration,
+ nsAutoString& aRuleText)
+{
+ bool didSanitize = aDeclaration->HasProperty(eCSSProperty_binding);
+ aDeclaration->RemovePropertyByID(eCSSProperty_binding);
+ aDeclaration->ToString(aRuleText);
+ return didSanitize;
+}
+
+bool
+nsTreeSanitizer::SanitizeStyleSheet(const nsAString& aOriginal,
+ nsAString& aSanitized,
+ nsIDocument* aDocument,
+ nsIURI* aBaseURI)
+{
+ nsresult rv;
+ aSanitized.Truncate();
+ // aSanitized will hold the permitted CSS text.
+ // -moz-binding is blacklisted.
+ bool didSanitize = false;
+ // Create a sheet to hold the parsed CSS
+ RefPtr<CSSStyleSheet> sheet =
+ new CSSStyleSheet(mozilla::css::eAuthorSheetFeatures,
+ CORS_NONE, aDocument->GetReferrerPolicy());
+ sheet->SetURIs(aDocument->GetDocumentURI(), nullptr, aBaseURI);
+ sheet->SetPrincipal(aDocument->NodePrincipal());
+ // Create the CSS parser, and parse the CSS text.
+ nsCSSParser parser(nullptr, sheet);
+ rv = parser.ParseSheet(aOriginal, aDocument->GetDocumentURI(), aBaseURI,
+ aDocument->NodePrincipal(), 0);
+ NS_ENSURE_SUCCESS(rv, true);
+ // Mark the sheet as complete.
+ MOZ_ASSERT(!sheet->IsModified(),
+ "should not get marked modified during parsing");
+ sheet->SetComplete();
+ // Loop through all the rules found in the CSS text
+ int32_t ruleCount = sheet->StyleRuleCount();
+ for (int32_t i = 0; i < ruleCount; ++i) {
+ mozilla::css::Rule* rule = sheet->GetStyleRuleAt(i);
+ if (!rule)
+ continue;
+ switch (rule->GetType()) {
+ default:
+ didSanitize = true;
+ // Ignore these rule types.
+ break;
+ case mozilla::css::Rule::NAMESPACE_RULE:
+ case mozilla::css::Rule::FONT_FACE_RULE: {
+ // Append @namespace and @font-face rules verbatim.
+ nsAutoString cssText;
+ nsCOMPtr<nsIDOMCSSRule> styleRule = do_QueryInterface(rule);
+ if (styleRule) {
+ rv = styleRule->GetCssText(cssText);
+ if (NS_SUCCEEDED(rv)) {
+ aSanitized.Append(cssText);
+ }
+ }
+ break;
+ }
+ case mozilla::css::Rule::STYLE_RULE: {
+ // For style rules, we will just look for and remove the
+ // -moz-binding properties.
+ RefPtr<mozilla::css::StyleRule> styleRule = do_QueryObject(rule);
+ NS_ASSERTION(styleRule, "Must be a style rule");
+ nsAutoString decl;
+ bool sanitized =
+ SanitizeStyleDeclaration(styleRule->GetDeclaration(), decl);
+ didSanitize = sanitized || didSanitize;
+ if (!sanitized) {
+ styleRule->GetCssText(decl);
+ }
+ aSanitized.Append(decl);
+ }
+ }
+ }
+ return didSanitize;
+}
+
+void
+nsTreeSanitizer::SanitizeAttributes(mozilla::dom::Element* aElement,
+ nsTHashtable<nsISupportsHashKey>* aAllowed,
+ nsIAtom*** aURLs,
+ bool aAllowXLink,
+ bool aAllowStyle,
+ bool aAllowDangerousSrc)
+{
+ uint32_t ac = aElement->GetAttrCount();
+
+ for (int32_t i = ac - 1; i >= 0; --i) {
+ const nsAttrName* attrName = aElement->GetAttrNameAt(i);
+ int32_t attrNs = attrName->NamespaceID();
+ nsCOMPtr<nsIAtom> attrLocal = attrName->LocalName();
+
+ if (kNameSpaceID_None == attrNs) {
+ if (aAllowStyle && nsGkAtoms::style == attrLocal) {
+ nsCOMPtr<nsIURI> baseURI = aElement->GetBaseURI();
+ nsIDocument* document = aElement->OwnerDoc();
+ // Pass the CSS Loader object to the parser, to allow parser error
+ // reports to include the outer window ID.
+ nsCSSParser parser(document->CSSLoader());
+ nsAutoString value;
+ aElement->GetAttr(attrNs, attrLocal, value);
+ RefPtr<mozilla::css::Declaration> decl =
+ parser.ParseStyleAttribute(value, document->GetDocumentURI(),
+ baseURI, document->NodePrincipal());
+ if (decl) {
+ nsAutoString cleanValue;
+ if (SanitizeStyleDeclaration(decl, cleanValue)) {
+ aElement->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::style,
+ cleanValue,
+ false);
+ }
+ }
+ continue;
+ }
+ if (aAllowDangerousSrc && nsGkAtoms::src == attrLocal) {
+ continue;
+ }
+ if (IsURL(aURLs, attrLocal)) {
+ if (SanitizeURL(aElement, attrNs, 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;
+ }
+ // 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 == sAttributesHTML) && // element is HTML
+ sPresAttributesHTML->GetEntry(attrLocal)) {
+ continue;
+ }
+ if (aAllowed->GetEntry(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();
+ // Allow underscore to cater to the MCE editor library.
+ // Allow data-* on SVG and MathML, too, as a forward-compat measure.
+ if (*localStr == '_' || (attrLocal->GetLength() > 5 && localStr[0] == 'd'
+ && localStr[1] == 'a' && localStr[2] == 't' && localStr[3] == 'a'
+ && localStr[4] == '-')) {
+ continue;
+ }
+ // else not allowed
+ } else if (kNameSpaceID_XML == attrNs) {
+ if (nsGkAtoms::base == attrLocal) {
+ if (SanitizeURL(aElement, attrNs, 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 (nsGkAtoms::lang == attrLocal || nsGkAtoms::space == attrLocal) {
+ continue;
+ }
+ // else not allowed
+ } else if (aAllowXLink && kNameSpaceID_XLink == attrNs) {
+ if (nsGkAtoms::href == attrLocal) {
+ if (SanitizeURL(aElement, attrNs, 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 (nsGkAtoms::type == attrLocal || nsGkAtoms::title == attrLocal
+ || nsGkAtoms::show == attrLocal || nsGkAtoms::actuate == attrLocal) {
+ continue;
+ }
+ // else not allowed
+ }
+ aElement->UnsetAttr(kNameSpaceID_None, attrLocal, false);
+ // 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,
+ EmptyString(),
+ false);
+ }
+}
+
+bool
+nsTreeSanitizer::SanitizeURL(mozilla::dom::Element* aElement,
+ int32_t aNamespace,
+ nsIAtom* aLocalName)
+{
+ 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);
+
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ uint32_t flags = nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL;
+
+ nsCOMPtr<nsIURI> baseURI = aElement->GetBaseURI();
+ nsCOMPtr<nsIURI> attrURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(attrURI), v, nullptr, baseURI);
+ 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);
+ }
+ } else {
+ rv = secMan->CheckLoadURIWithPrincipal(sNullPrincipal, attrURI, flags);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ aElement->UnsetAttr(aNamespace, aLocalName, false);
+ return true;
+ }
+ return false;
+}
+
+void
+nsTreeSanitizer::Sanitize(nsIContent* 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.
+ NS_PRECONDITION(aFragment->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT),
+ "Argument was not DOM fragment.");
+ NS_PRECONDITION(!aFragment->IsInUncomposedDoc(), "The fragment is in doc?");
+
+ mFullDocument = false;
+ SanitizeChildren(aFragment);
+}
+
+void
+nsTreeSanitizer::Sanitize(nsIDocument* 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
+ NS_PRECONDITION(!aDocument->GetContainer(), "The document is in a shell.");
+ RefPtr<mozilla::dom::Element> root = aDocument->GetRootElement();
+ NS_PRECONDITION(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();
+ nsIAtom* localName = nodeInfo->NameAtom();
+ int32_t ns = nodeInfo->NamespaceID();
+
+ if (MustPrune(ns, localName, elt)) {
+ RemoveAllAttributes(node);
+ nsIContent* descendant = node;
+ while ((descendant = descendant->GetNextNode(node))) {
+ RemoveAllAttributes(descendant);
+ }
+ nsIContent* next = node->GetNextNonChildNode(aRoot);
+ node->RemoveFromParent();
+ node = next;
+ continue;
+ }
+ if (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!");
+ nsAutoString styleText;
+ nsContentUtils::GetNodeTextContent(node, false, styleText);
+
+ nsAutoString sanitizedStyle;
+ nsCOMPtr<nsIURI> baseURI = node->GetBaseURI();
+ if (SanitizeStyleSheet(styleText,
+ sanitizedStyle,
+ aRoot->OwnerDoc(),
+ baseURI)) {
+ nsContentUtils::SetNodeTextContent(node, sanitizedStyle, true);
+ } else {
+ // If the node had non-text child nodes, this operation zaps those.
+ nsContentUtils::SetNodeTextContent(node, styleText, true);
+ }
+ if (ns == kNameSpaceID_XHTML) {
+ SanitizeAttributes(elt,
+ sAttributesHTML,
+ (nsIAtom***)kURLAttributesHTML,
+ false,
+ mAllowStyles,
+ false);
+ } else {
+ SanitizeAttributes(elt,
+ sAttributesSVG,
+ (nsIAtom***)kURLAttributesSVG,
+ true,
+ mAllowStyles,
+ false);
+ }
+ node = node->GetNextNonChildNode(aRoot);
+ continue;
+ }
+ if (MustFlatten(ns, localName)) {
+ RemoveAllAttributes(node);
+ 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!");
+ if (ns == kNameSpaceID_XHTML) {
+ SanitizeAttributes(elt,
+ sAttributesHTML,
+ (nsIAtom***)kURLAttributesHTML,
+ false, mAllowStyles,
+ (nsGkAtoms::img == localName) &&
+ !mCidEmbedsOnly);
+ } else if (ns == kNameSpaceID_SVG) {
+ SanitizeAttributes(elt,
+ sAttributesSVG,
+ (nsIAtom***)kURLAttributesSVG,
+ true,
+ mAllowStyles,
+ false);
+ } else {
+ SanitizeAttributes(elt,
+ sAttributesMathML,
+ (nsIAtom***)kURLAttributesMathML,
+ true,
+ false,
+ false);
+ }
+ node = node->GetNextNode(aRoot);
+ continue;
+ }
+ NS_ASSERTION(!node->GetFirstChild(), "How come non-element node had kids?");
+ nsIContent* next = node->GetNextNonChildNode(aRoot);
+ if (!mAllowComments && node->IsNodeOfType(nsINode::eCOMMENT)) {
+ node->RemoveFromParent();
+ }
+ node = next;
+ }
+}
+
+void
+nsTreeSanitizer::RemoveAllAttributes(nsIContent* aElement)
+{
+ const nsAttrName* attrName;
+ while ((attrName = aElement->GetAttrNameAt(0))) {
+ int32_t attrNs = attrName->NamespaceID();
+ nsCOMPtr<nsIAtom> attrLocal = attrName->LocalName();
+ aElement->UnsetAttr(attrNs, attrLocal, false);
+ }
+}
+
+void
+nsTreeSanitizer::InitializeStatics()
+{
+ NS_PRECONDITION(!sElementsHTML, "Initializing a second time.");
+
+ sElementsHTML = new nsTHashtable<nsISupportsHashKey>(ArrayLength(kElementsHTML));
+ for (uint32_t i = 0; kElementsHTML[i]; i++) {
+ sElementsHTML->PutEntry(*kElementsHTML[i]);
+ }
+
+ sAttributesHTML = new nsTHashtable<nsISupportsHashKey>(ArrayLength(kAttributesHTML));
+ for (uint32_t i = 0; kAttributesHTML[i]; i++) {
+ sAttributesHTML->PutEntry(*kAttributesHTML[i]);
+ }
+
+ sPresAttributesHTML = new nsTHashtable<nsISupportsHashKey>(ArrayLength(kPresAttributesHTML));
+ for (uint32_t i = 0; kPresAttributesHTML[i]; i++) {
+ sPresAttributesHTML->PutEntry(*kPresAttributesHTML[i]);
+ }
+
+ sElementsSVG = new nsTHashtable<nsISupportsHashKey>(ArrayLength(kElementsSVG));
+ for (uint32_t i = 0; kElementsSVG[i]; i++) {
+ sElementsSVG->PutEntry(*kElementsSVG[i]);
+ }
+
+ sAttributesSVG = new nsTHashtable<nsISupportsHashKey>(ArrayLength(kAttributesSVG));
+ for (uint32_t i = 0; kAttributesSVG[i]; i++) {
+ sAttributesSVG->PutEntry(*kAttributesSVG[i]);
+ }
+
+ sElementsMathML = new nsTHashtable<nsISupportsHashKey>(ArrayLength(kElementsMathML));
+ for (uint32_t i = 0; kElementsMathML[i]; i++) {
+ sElementsMathML->PutEntry(*kElementsMathML[i]);
+ }
+
+ sAttributesMathML = new nsTHashtable<nsISupportsHashKey>(ArrayLength(kAttributesMathML));
+ for (uint32_t i = 0; kAttributesMathML[i]; i++) {
+ sAttributesMathML->PutEntry(*kAttributesMathML[i]);
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::Create();
+ 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;
+
+ NS_IF_RELEASE(sNullPrincipal);
+}
diff --git a/dom/base/nsTreeSanitizer.h b/dom/base/nsTreeSanitizer.h
new file mode 100644
index 000000000..b8700d775
--- /dev/null
+++ b/dom/base/nsTreeSanitizer.h
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/css/StyleRule.h"
+#include "nsIPrincipal.h"
+#include "mozilla/dom/Element.h"
+
+class nsIContent;
+
+/**
+ * See the documentation of nsIParserUtils::sanitize for documentation
+ * about the default behavior and the configuration options of this sanitizer.
+ */
+class MOZ_STACK_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 argument must be of type nsINode::eDOCUMENT_FRAGMENT and,
+ * consequently, must not be in the document. Furthermore, the fragment
+ * must have just come from a parser so that it can't have mutation
+ * event listeners set on it.
+ */
+ void Sanitize(nsIContent* 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(nsIDocument* aDocument);
+
+ 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;
+
+ 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, nsIAtom* 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,
+ nsIAtom* 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(nsIAtom*** aURLs, nsIAtom* aLocalName);
+
+ /**
+ * 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 the whitelist of permitted local names to use
+ * @param aURLs the local names of URL-valued attributes
+ * @param aAllowXLink whether XLink attributes are allowed
+ * @param aAllowStyle whether the style attribute is allowed
+ * @param aAllowDangerousSrc whether to leave the value of the src
+ * attribute unsanitized
+ */
+ void SanitizeAttributes(mozilla::dom::Element* aElement,
+ nsTHashtable<nsISupportsHashKey>* aAllowed,
+ nsIAtom*** aURLs,
+ bool aAllowXLink,
+ bool aAllowStyle,
+ bool aAllowDangerousSrc);
+
+ /**
+ * 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
+ * @return true if the attribute was removed and false otherwise
+ */
+ bool SanitizeURL(mozilla::dom::Element* aElement,
+ int32_t aNamespace,
+ nsIAtom* aLocalName);
+
+ /**
+ * Checks a style rule for the presence of the 'binding' CSS property and
+ * removes that property from the rule and reserializes in case the
+ * property was found.
+ *
+ * @param aDeclaration The style declaration to check
+ * @param aRuleText the serialized mutated rule if the method returns true
+ * @return true if the rule was modified and false otherwise
+ */
+ bool SanitizeStyleDeclaration(mozilla::css::Declaration* aDeclaration,
+ nsAutoString& aRuleText);
+
+ /**
+ * Parses a style sheet and reserializes it with the 'binding' property
+ * removed if it was present.
+ *
+ * @param aOrigin the original style sheet source
+ * @param aSanitized the reserialization without 'binding'; only valid if
+ * this method return true
+ * @param aDocument the document the style sheet belongs to
+ * @param aBaseURI the base URI to use
+ * @return true if the 'binding' property was encountered and false
+ * otherwise
+ */
+ bool SanitizeStyleSheet(const nsAString& aOriginal,
+ nsAString& aSanitized,
+ nsIDocument* aDocument,
+ nsIURI* aBaseURI);
+
+ /**
+ * Removes all attributes from an element node.
+ */
+ void RemoveAllAttributes(nsIContent* aElement);
+
+ /**
+ * The whitelist of HTML elements.
+ */
+ static nsTHashtable<nsISupportsHashKey>* sElementsHTML;
+
+ /**
+ * The whitelist of non-presentational HTML attributes.
+ */
+ static nsTHashtable<nsISupportsHashKey>* sAttributesHTML;
+
+ /**
+ * The whitelist of presentational HTML attributes.
+ */
+ static nsTHashtable<nsISupportsHashKey>* sPresAttributesHTML;
+
+ /**
+ * The whitelist of SVG elements.
+ */
+ static nsTHashtable<nsISupportsHashKey>* sElementsSVG;
+
+ /**
+ * The whitelist of SVG attributes.
+ */
+ static nsTHashtable<nsISupportsHashKey>* sAttributesSVG;
+
+ /**
+ * The whitelist of SVG elements.
+ */
+ static nsTHashtable<nsISupportsHashKey>* sElementsMathML;
+
+ /**
+ * The whitelist of MathML attributes.
+ */
+ static nsTHashtable<nsISupportsHashKey>* sAttributesMathML;
+
+ /**
+ * Reusable null principal for URL checks.
+ */
+ static nsIPrincipal* sNullPrincipal;
+};
+
+#endif // nsTreeSanitizer_h_
diff --git a/dom/base/nsViewportInfo.cpp b/dom/base/nsViewportInfo.cpp
new file mode 100644
index 000000000..5a69faa92
--- /dev/null
+++ b/dom/base/nsViewportInfo.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 "nsViewportInfo.h"
+#include "mozilla/Assertions.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+void
+nsViewportInfo::ConstrainViewportValues()
+{
+ // Constrain the min/max zoom as specified at:
+ // dev.w3.org/csswg/css-device-adapt section 6.2
+ mMaxZoom = std::max(mMinZoom, mMaxZoom);
+
+ if (mDefaultZoom > mMaxZoom) {
+ mDefaultZoomValid = false;
+ mDefaultZoom = mMaxZoom;
+ }
+ if (mDefaultZoom < mMinZoom) {
+ mDefaultZoomValid = false;
+ mDefaultZoom = mMinZoom;
+ }
+}
diff --git a/dom/base/nsViewportInfo.h b/dom/base/nsViewportInfo.h
new file mode 100644
index 000000000..eff264444
--- /dev/null
+++ b/dom/base/nsViewportInfo.h
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <stdint.h>
+#include "mozilla/Attributes.h"
+#include "Units.h"
+
+/**
+ * Default values for the nsViewportInfo class.
+ */
+static const mozilla::LayoutDeviceToScreenScale kViewportMinScale(0.1f);
+static const mozilla::LayoutDeviceToScreenScale kViewportMaxScale(10.0f);
+static const mozilla::CSSIntSize kViewportMinSize(200, 40);
+static const mozilla::CSSIntSize kViewportMaxSize(10000, 10000);
+
+/**
+ * Information retrieved from the <meta name="viewport"> tag. See
+ * nsIDocument::GetViewportInfo for more information on this functionality.
+ */
+class MOZ_STACK_CLASS nsViewportInfo
+{
+ public:
+ nsViewportInfo(const mozilla::ScreenIntSize& aDisplaySize,
+ const mozilla::CSSToScreenScale& aDefaultZoom,
+ bool aAllowZoom) :
+ mDefaultZoomValid(true),
+ mDefaultZoom(aDefaultZoom),
+ mAutoSize(true),
+ mAllowZoom(aAllowZoom)
+ {
+ mSize = mozilla::ScreenSize(aDisplaySize) / mDefaultZoom;
+ mozilla::CSSToLayoutDeviceScale pixelRatio(1.0f);
+ mMinZoom = pixelRatio * kViewportMinScale;
+ mMaxZoom = pixelRatio * kViewportMaxScale;
+ ConstrainViewportValues();
+ }
+
+ nsViewportInfo(const mozilla::CSSToScreenScale& aDefaultZoom,
+ const mozilla::CSSToScreenScale& aMinZoom,
+ const mozilla::CSSToScreenScale& aMaxZoom,
+ const mozilla::CSSSize& aSize,
+ bool aAutoSize,
+ bool aAllowZoom) :
+ mDefaultZoomValid(true),
+ mDefaultZoom(aDefaultZoom),
+ mMinZoom(aMinZoom),
+ mMaxZoom(aMaxZoom),
+ mSize(aSize),
+ mAutoSize(aAutoSize),
+ mAllowZoom(aAllowZoom)
+ {
+ 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; }
+
+ private:
+
+ /**
+ * Constrain the viewport calculations from the
+ * nsIDocument::GetViewportInfo() function in order to always return
+ * sane minimum/maximum values.
+ */
+ void ConstrainViewportValues();
+
+ // If the default zoom was specified and was between the min and max
+ // zoom values.
+ bool mDefaultZoomValid;
+
+ // 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;
+
+ // 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 000000000..acec4acfb
--- /dev/null
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -0,0 +1,861 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "amIAddonManager.h"
+#include "nsWindowMemoryReporter.h"
+#include "nsGlobalWindow.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindowCollection.h"
+#include "nsIEffectiveTLDService.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "XPCJSMemoryReporter.h"
+#include "js/MemoryMetrics.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+
+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)
+{
+}
+
+nsWindowMemoryReporter::~nsWindowMemoryReporter()
+{
+ KillCheckTimer();
+}
+
+NS_IMPL_ISUPPORTS(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
+ nsISupportsWeakReference)
+
+static nsresult
+AddNonJSSizeOfWindowAndItsDescendents(nsGlobalWindow* aWindow,
+ nsTabSizes* aSizes)
+{
+ // Measure the window.
+ nsWindowSizes windowSizes(moz_malloc_size_of);
+ aWindow->AddSizeOfIncludingThis(&windowSizes);
+ windowSizes.addToTabSizes(aSizes);
+
+ // Measure the inner window, if there is one.
+ nsWindowSizes innerWindowSizes(moz_malloc_size_of);
+ nsGlobalWindow* inner = aWindow->IsOuterWindow() ? aWindow->GetCurrentInnerWindowInternal()
+ : nullptr;
+ if (inner) {
+ inner->AddSizeOfIncludingThis(&innerWindowSizes);
+ innerWindowSizes.addToTabSizes(aSizes);
+ }
+
+ nsCOMPtr<nsIDOMWindowCollection> frames = aWindow->GetFrames();
+
+ uint32_t length;
+ nsresult rv = frames->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Measure this window's descendents.
+ for (uint32_t i = 0; i < length; i++) {
+ nsCOMPtr<mozIDOMWindowProxy> child;
+ rv = frames->Item(i, getter_AddRefs(child));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(child);
+
+ nsGlobalWindow* childWin = nsGlobalWindow::Cast(child);
+
+ rv = AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+static nsresult
+NonJSSizeOfTab(nsPIDOMWindowOuter* aWindow, size_t* aDomSize, size_t* aStyleSize, size_t* aOtherSize)
+{
+ nsGlobalWindow* window = nsGlobalWindow::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);
+ }
+
+ RegisterStrongMemoryReporter(new GhostWindowsReporter());
+ RegisterGhostWindowsDistinguishedAmount(GhostWindowsReporter::DistinguishedAmount);
+}
+
+/* static */ nsWindowMemoryReporter*
+nsWindowMemoryReporter::Get()
+{
+ return sWindowReporter;
+}
+
+static already_AddRefed<nsIURI>
+GetWindowURI(nsGlobalWindow* aWindow)
+{
+ NS_ENSURE_TRUE(aWindow, nullptr);
+
+ nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+ nsCOMPtr<nsIURI> uri;
+
+ if (doc) {
+ uri = doc->GetDocumentURI();
+ }
+
+ if (!uri) {
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrincipal =
+ do_QueryObject(aWindow);
+ NS_ENSURE_TRUE(scriptObjPrincipal, nullptr);
+
+ // 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()) {
+ nsIPrincipal* principal = scriptObjPrincipal->GetPrincipal();
+ if (principal) {
+ principal->GetURI(getter_AddRefs(uri));
+ }
+ }
+ }
+
+ return uri.forget();
+}
+
+static void
+AppendWindowURI(nsGlobalWindow *aWindow, nsACString& aStr, bool aAnonymize)
+{
+ nsCOMPtr<nsIURI> uri = GetWindowURI(aWindow);
+
+ if (uri) {
+ if (aAnonymize && !aWindow->IsChromeWindow()) {
+ aStr.AppendPrintf("<anonymized-%llu>", aWindow->WindowID());
+ } else {
+ nsCString spec = uri->GetSpecOrDefault();
+
+ // 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;
+ }
+ } else {
+ // 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 += NS_LITERAL_CSTRING("[system]");
+ }
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf)
+
+// The key is the window ID.
+typedef nsDataHashtable<nsUint64HashKey, nsCString> WindowPaths;
+
+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(
+ EmptyCString(), 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
+CollectWindowReports(nsGlobalWindow *aWindow,
+ amIAddonManager *addonManager,
+ nsWindowSizes *aWindowTotalSizes,
+ nsTHashtable<nsUint64HashKey> *aGhostWindowIDs,
+ WindowPaths *aWindowPaths,
+ WindowPaths *aTopWindowPaths,
+ nsIHandleReportCallback *aHandleReport,
+ nsISupports *aData,
+ bool aAnonymize)
+{
+ nsAutoCString windowPath("explicit/");
+
+ // Avoid calling aWindow->GetTop() if there's no outer window. It will work
+ // just fine, but will spew a lot of warnings.
+ nsGlobalWindow *top = nullptr;
+ nsCOMPtr<nsIURI> location;
+ if (aWindow->GetOuterWindow()) {
+ // Our window should have a null top iff it has a null docshell.
+ MOZ_ASSERT(!!aWindow->GetTopInternal() == !!aWindow->GetDocShell());
+ top = aWindow->GetTopInternal();
+ if (top) {
+ location = GetWindowURI(top);
+ }
+ }
+ if (!location) {
+ location = GetWindowURI(aWindow);
+ }
+
+ if (addonManager && location) {
+ bool ok;
+ nsAutoCString id;
+ if (NS_SUCCEEDED(addonManager->MapURIToAddonID(location, id, &ok)) && ok) {
+ // Add-on names are not privacy-sensitive, so we can use them with
+ // impunity.
+ windowPath += NS_LITERAL_CSTRING("add-ons/") + id +
+ NS_LITERAL_CSTRING("/");
+ }
+ }
+
+ windowPath += NS_LITERAL_CSTRING("window-objects/");
+
+ if (top) {
+ windowPath += NS_LITERAL_CSTRING("top(");
+ AppendWindowURI(top, windowPath, aAnonymize);
+ windowPath.AppendPrintf(", id=%llu)", top->WindowID());
+
+ aTopWindowPaths->Put(aWindow->WindowID(), windowPath);
+
+ windowPath += aWindow->IsFrozen() ? NS_LITERAL_CSTRING("/cached/")
+ : NS_LITERAL_CSTRING("/active/");
+ } else {
+ if (aGhostWindowIDs->Contains(aWindow->WindowID())) {
+ windowPath += NS_LITERAL_CSTRING("top(none)/ghost/");
+ } else {
+ windowPath += NS_LITERAL_CSTRING("top(none)/detached/");
+ }
+ }
+
+ windowPath += NS_LITERAL_CSTRING("window(");
+ AppendWindowURI(aWindow, windowPath, aAnonymize);
+ windowPath += NS_LITERAL_CSTRING(")");
+
+ // Use |windowPath|, but replace "explicit/" with "event-counts/".
+ nsCString censusWindowPath(windowPath);
+ censusWindowPath.Replace(0, strlen("explicit"), "event-counts");
+
+ // Remember the path for later.
+ aWindowPaths->Put(aWindow->WindowID(), windowPath);
+
+#define REPORT_SIZE(_pathTail, _amount, _desc) \
+ ReportSize(windowPath, _pathTail, _amount, NS_LITERAL_CSTRING(_desc), \
+ aHandleReport, aData);
+
+#define REPORT_COUNT(_pathTail, _amount, _desc) \
+ ReportCount(censusWindowPath, _pathTail, _amount, NS_LITERAL_CSTRING(_desc), \
+ aHandleReport, aData);
+
+ nsWindowSizes windowSizes(WindowsMallocSizeOf);
+ aWindow->AddSizeOfIncludingThis(&windowSizes);
+
+ REPORT_SIZE("/dom/element-nodes", windowSizes.mDOMElementNodesSize,
+ "Memory used by the element nodes in a window's DOM.");
+ aWindowTotalSizes->mDOMElementNodesSize += windowSizes.mDOMElementNodesSize;
+
+ REPORT_SIZE("/dom/text-nodes", windowSizes.mDOMTextNodesSize,
+ "Memory used by the text nodes in a window's DOM.");
+ aWindowTotalSizes->mDOMTextNodesSize += windowSizes.mDOMTextNodesSize;
+
+ REPORT_SIZE("/dom/cdata-nodes", windowSizes.mDOMCDATANodesSize,
+ "Memory used by the CDATA nodes in a window's DOM.");
+ aWindowTotalSizes->mDOMCDATANodesSize += windowSizes.mDOMCDATANodesSize;
+
+ REPORT_SIZE("/dom/comment-nodes", windowSizes.mDOMCommentNodesSize,
+ "Memory used by the comment nodes in a window's DOM.");
+ aWindowTotalSizes->mDOMCommentNodesSize += windowSizes.mDOMCommentNodesSize;
+
+ REPORT_SIZE("/dom/event-targets", windowSizes.mDOMEventTargetsSize,
+ "Memory used by the event targets table in a window's DOM, and "
+ "the objects it points to, which include XHRs.");
+ aWindowTotalSizes->mDOMEventTargetsSize += windowSizes.mDOMEventTargetsSize;
+
+ REPORT_COUNT("/dom/event-targets", windowSizes.mDOMEventTargetsCount,
+ "Number of non-node event targets in the event targets table "
+ "in a window's DOM, such as XHRs.");
+ aWindowTotalSizes->mDOMEventTargetsCount +=
+ windowSizes.mDOMEventTargetsCount;
+
+ REPORT_COUNT("/dom/event-listeners", windowSizes.mDOMEventListenersCount,
+ "Number of event listeners in a window, including event "
+ "listeners on nodes and other event targets.");
+ aWindowTotalSizes->mDOMEventListenersCount +=
+ windowSizes.mDOMEventListenersCount;
+
+ REPORT_SIZE("/dom/other", windowSizes.mDOMOtherSize,
+ "Memory used by a window's DOM that isn't measured by the "
+ "other 'dom/' numbers.");
+ aWindowTotalSizes->mDOMOtherSize += windowSizes.mDOMOtherSize;
+
+ REPORT_SIZE("/property-tables",
+ windowSizes.mPropertyTablesSize,
+ "Memory used for the property tables within a window.");
+ aWindowTotalSizes->mPropertyTablesSize += windowSizes.mPropertyTablesSize;
+
+ REPORT_SIZE("/style-sheets", windowSizes.mStyleSheetsSize,
+ "Memory used by style sheets within a window.");
+ aWindowTotalSizes->mStyleSheetsSize += windowSizes.mStyleSheetsSize;
+
+ REPORT_SIZE("/layout/pres-shell", windowSizes.mLayoutPresShellSize,
+ "Memory used by layout's PresShell, along with any structures "
+ "allocated in its arena and not measured elsewhere, "
+ "within a window.");
+ aWindowTotalSizes->mLayoutPresShellSize += windowSizes.mLayoutPresShellSize;
+
+ REPORT_SIZE("/layout/line-boxes", windowSizes.mArenaStats.mLineBoxes,
+ "Memory used by line boxes within a window.");
+ aWindowTotalSizes->mArenaStats.mLineBoxes
+ += windowSizes.mArenaStats.mLineBoxes;
+
+ REPORT_SIZE("/layout/rule-nodes", windowSizes.mArenaStats.mRuleNodes,
+ "Memory used by CSS rule nodes within a window.");
+ aWindowTotalSizes->mArenaStats.mRuleNodes
+ += windowSizes.mArenaStats.mRuleNodes;
+
+ REPORT_SIZE("/layout/style-contexts", windowSizes.mArenaStats.mStyleContexts,
+ "Memory used by style contexts within a window.");
+ aWindowTotalSizes->mArenaStats.mStyleContexts
+ += windowSizes.mArenaStats.mStyleContexts;
+
+ REPORT_SIZE("/layout/style-structs", windowSizes.mArenaStats.mStyleStructs,
+ "Memory used by style structs within a window.");
+ aWindowTotalSizes->mArenaStats.mStyleStructs
+ += windowSizes.mArenaStats.mStyleStructs;
+
+ REPORT_SIZE("/layout/style-sets", windowSizes.mLayoutStyleSetsSize,
+ "Memory used by style sets within a window.");
+ aWindowTotalSizes->mLayoutStyleSetsSize += windowSizes.mLayoutStyleSetsSize;
+
+ REPORT_SIZE("/layout/text-runs", windowSizes.mLayoutTextRunsSize,
+ "Memory used for text-runs (glyph layout) in the PresShell's "
+ "frame tree, within a window.");
+ aWindowTotalSizes->mLayoutTextRunsSize += windowSizes.mLayoutTextRunsSize;
+
+ REPORT_SIZE("/layout/pres-contexts", windowSizes.mLayoutPresContextSize,
+ "Memory used for the PresContext in the PresShell's frame "
+ "within a window.");
+ aWindowTotalSizes->mLayoutPresContextSize +=
+ windowSizes.mLayoutPresContextSize;
+
+ // 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 FRAME_SUNDRIES_THRESHOLD =
+ js::MemoryReportingSundriesThreshold();
+
+ size_t frameSundriesSize = 0;
+#define FRAME_ID(classname) \
+ { \
+ size_t frameSize \
+ = windowSizes.mArenaStats.FRAME_ID_STAT_FIELD(classname); \
+ if (frameSize < FRAME_SUNDRIES_THRESHOLD) { \
+ frameSundriesSize += frameSize; \
+ } else { \
+ REPORT_SIZE("/layout/frames/" # classname, frameSize, \
+ "Memory used by frames of " \
+ "type " #classname " within a window."); \
+ } \
+ aWindowTotalSizes->mArenaStats.FRAME_ID_STAT_FIELD(classname) \
+ += frameSize; \
+ }
+#include "nsFrameIdList.h"
+#undef FRAME_ID
+
+ if (frameSundriesSize > 0) {
+ REPORT_SIZE("/layout/frames/sundries", frameSundriesSize,
+ "The sum of all memory used by frames which were too small "
+ "to be shown individually.");
+ }
+
+#undef REPORT_SIZE
+#undef REPORT_COUNT
+}
+
+typedef nsTArray< RefPtr<nsGlobalWindow> > WindowArray;
+
+NS_IMETHODIMP
+nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ nsGlobalWindow::WindowByIdTable* windowsById =
+ nsGlobalWindow::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.
+ WindowArray windows;
+ for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
+ windows.AppendElement(iter.Data());
+ }
+
+ // Get the IDs of all the "ghost" windows, and call aHandleReport->Callback()
+ // for each one.
+ nsTHashtable<nsUint64HashKey> ghostWindows;
+ CheckForGhostWindows(&ghostWindows);
+ for (auto iter = ghostWindows.ConstIter(); !iter.Done(); iter.Next()) {
+ nsGlobalWindow::WindowByIdTable* windowsById =
+ nsGlobalWindow::GetWindowsTable();
+ if (!windowsById) {
+ NS_WARNING("Couldn't get window-by-id hashtable?");
+ continue;
+ }
+
+ nsGlobalWindow* window = windowsById->Get(iter.Get()->GetKey());
+ if (!window) {
+ NS_WARNING("Could not look up window?");
+ continue;
+ }
+
+ nsAutoCString path;
+ path.AppendLiteral("ghost-windows/");
+ AppendWindowURI(window, path, aAnonymize);
+
+ aHandleReport->Callback(
+ /* process = */ EmptyCString(),
+ path,
+ nsIMemoryReporter::KIND_OTHER,
+ nsIMemoryReporter::UNITS_COUNT,
+ /* amount = */ 1,
+ /* description = */ NS_LITERAL_CSTRING("A ghost window."),
+ aData);
+ }
+
+ WindowPaths windowPaths;
+ WindowPaths topWindowPaths;
+
+ // Collect window memory usage.
+ nsWindowSizes windowTotalSizes(nullptr);
+ nsCOMPtr<amIAddonManager> addonManager;
+ if (XRE_IsParentProcess()) {
+ // Only try to access the service from the main process.
+ addonManager = do_GetService("@mozilla.org/addons/integration;1");
+ }
+ for (uint32_t i = 0; i < windows.Length(); i++) {
+ CollectWindowReports(windows[i], addonManager,
+ &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);
+
+#define REPORT(_path, _amount, _desc) \
+ aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
+ KIND_OTHER, UNITS_BYTES, _amount, \
+ NS_LITERAL_CSTRING(_desc), aData);
+
+ REPORT("window-objects/dom/element-nodes", windowTotalSizes.mDOMElementNodesSize,
+ "This is the sum of all windows' 'dom/element-nodes' numbers.");
+
+ REPORT("window-objects/dom/text-nodes", windowTotalSizes.mDOMTextNodesSize,
+ "This is the sum of all windows' 'dom/text-nodes' numbers.");
+
+ REPORT("window-objects/dom/cdata-nodes", windowTotalSizes.mDOMCDATANodesSize,
+ "This is the sum of all windows' 'dom/cdata-nodes' numbers.");
+
+ REPORT("window-objects/dom/comment-nodes", windowTotalSizes.mDOMCommentNodesSize,
+ "This is the sum of all windows' 'dom/comment-nodes' numbers.");
+
+ REPORT("window-objects/dom/event-targets", windowTotalSizes.mDOMEventTargetsSize,
+ "This is the sum of all windows' 'dom/event-targets' numbers.");
+
+ REPORT("window-objects/dom/other", windowTotalSizes.mDOMOtherSize,
+ "This is the sum of all windows' 'dom/other' numbers.");
+
+ REPORT("window-objects/property-tables",
+ windowTotalSizes.mPropertyTablesSize,
+ "This is the sum of all windows' 'property-tables' numbers.");
+
+ REPORT("window-objects/style-sheets", windowTotalSizes.mStyleSheetsSize,
+ "This is the sum of all windows' '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/line-boxes",
+ windowTotalSizes.mArenaStats.mLineBoxes,
+ "This is the sum of all windows' 'layout/line-boxes' numbers.");
+
+ REPORT("window-objects/layout/rule-nodes",
+ windowTotalSizes.mArenaStats.mRuleNodes,
+ "This is the sum of all windows' 'layout/rule-nodes' numbers.");
+
+ REPORT("window-objects/layout/style-contexts",
+ windowTotalSizes.mArenaStats.mStyleContexts,
+ "This is the sum of all windows' 'layout/style-contexts' numbers.");
+
+ REPORT("window-objects/layout/style-structs",
+ windowTotalSizes.mArenaStats.mStyleStructs,
+ "This is the sum of all windows' 'layout/style-structs' numbers.");
+
+ REPORT("window-objects/layout/style-sets", windowTotalSizes.mLayoutStyleSetsSize,
+ "This is the sum of all windows' 'layout/style-sets' 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.");
+
+ size_t frameTotal = 0;
+#define FRAME_ID(classname) \
+ frameTotal += windowTotalSizes.mArenaStats.FRAME_ID_STAT_FIELD(classname);
+#include "nsFrameIdList.h"
+#undef FRAME_ID
+
+ REPORT("window-objects/layout/frames", frameTotal,
+ "Memory used for layout frames within windows. "
+ "This is the sum of all windows' 'layout/frames/' 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(nsGlobalWindow* aWindow)
+{
+ nsWeakPtr weakWindow = do_GetWeakReference(static_cast<nsIDOMEventTarget*>(aWindow));
+ if (!weakWindow) {
+ NS_WARNING("Couldn't take weak reference to a window?");
+ return;
+ }
+
+ mDetachedWindows.Put(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;
+
+ mCheckTimer = do_CreateInstance("@mozilla.org/timer;1");
+
+ if (mCheckTimer) {
+ mCheckTimer->InitWithFuncCallback(CheckTimerFired, nullptr,
+ timerDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+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(
+ nsTHashtable<nsUint64HashKey> *aOutGhostIDs /* = nullptr */)
+{
+ nsCOMPtr<nsIEffectiveTLDService> tldService = do_GetService(
+ NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ NS_WARNING("Couldn't get TLDService.");
+ return;
+ }
+
+ nsGlobalWindow::WindowByIdTable *windowsById =
+ nsGlobalWindow::GetWindowsTable();
+ if (!windowsById) {
+ NS_WARNING("GetWindowsTable returned null");
+ return;
+ }
+
+ mLastCheckForGhostWindows = TimeStamp::NowLoRes();
+ KillCheckTimer();
+
+ nsTHashtable<nsCStringHashKey> nonDetachedWindowDomains;
+
+ // Populate nonDetachedWindowDomains.
+ for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
+ // Null outer window implies null top, but calling GetTop() when there's no
+ // outer window causes us to spew debug warnings.
+ nsGlobalWindow* window = iter.UserData();
+ if (!window->GetOuterWindow() || !window->GetTopInternal()) {
+ // This window is detached, so we don't care about its domain.
+ continue;
+ }
+
+ nsCOMPtr<nsIURI> uri = GetWindowURI(window);
+ nsAutoCString domain;
+ if (uri) {
+ tldService->GetBaseDomain(uri, 0, domain);
+ }
+ nonDetachedWindowDomains.PutEntry(domain);
+ }
+
+ // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
+ // if it's not null.
+ uint32_t ghostTimeout = GetGhostTimeout();
+ TimeStamp now = mLastCheckForGhostWindows;
+ 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 GetTop() 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()->GetTop();
+ }
+
+ if (top) {
+ // The window is no longer detached, so we no longer want to track it.
+ iter.Remove();
+ continue;
+ }
+
+ nsCOMPtr<nsIURI> uri = GetWindowURI(nsGlobalWindow::Cast(window));
+
+ nsAutoCString domain;
+ if (uri) {
+ // GetBaseDomain works fine if |uri| is null, but it outputs a warning
+ // which ends up overrunning the mochitest logs.
+ tldService->GetBaseDomain(uri, 0, domain);
+ }
+
+ TimeStamp& timeStamp = iter.Data();
+
+ if (nonDetachedWindowDomains.Contains(domain)) {
+ // This window shares a domain with a non-detached window, so reset its
+ // clock.
+ timeStamp = TimeStamp();
+ } else {
+ // This window does not share a domain with 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.
+ if (aOutGhostIDs && window) {
+ aOutGhostIDs->PutEntry(window->WindowID());
+ }
+ }
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsWindowMemoryReporter::GhostWindowsReporter,
+ nsIMemoryReporter)
+
+/* static */ int64_t
+nsWindowMemoryReporter::GhostWindowsReporter::DistinguishedAmount()
+{
+ nsTHashtable<nsUint64HashKey> ghostWindows;
+ sWindowReporter->CheckForGhostWindows(&ghostWindows);
+ return ghostWindows.Count();
+}
+
+void
+nsWindowMemoryReporter::KillCheckTimer()
+{
+ if (mCheckTimer) {
+ mCheckTimer->Cancel();
+ mCheckTimer = nullptr;
+ }
+}
+
+#ifdef DEBUG
+/* static */ void
+nsWindowMemoryReporter::UnlinkGhostWindows()
+{
+ if (!sWindowReporter) {
+ return;
+ }
+
+ nsGlobalWindow::WindowByIdTable* windowsById =
+ nsGlobalWindow::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.
+ WindowArray windows;
+ for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
+ windows.AppendElement(iter.Data());
+ }
+
+ // Get the IDs of all the "ghost" windows, and unlink them all.
+ nsTHashtable<nsUint64HashKey> ghostWindows;
+ sWindowReporter->CheckForGhostWindows(&ghostWindows);
+ for (auto iter = ghostWindows.ConstIter(); !iter.Done(); iter.Next()) {
+ nsGlobalWindow::WindowByIdTable* windowsById =
+ nsGlobalWindow::GetWindowsTable();
+ if (!windowsById) {
+ continue;
+ }
+
+ RefPtr<nsGlobalWindow> window = windowsById->Get(iter.Get()->GetKey());
+ if (window) {
+ window->RiskyUnlink();
+ }
+ }
+}
+#endif
diff --git a/dom/base/nsWindowMemoryReporter.h b/dom/base/nsWindowMemoryReporter.h
new file mode 100644
index 000000000..b9e986959
--- /dev/null
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -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/. */
+
+#ifndef nsWindowMemoryReporter_h__
+#define nsWindowMemoryReporter_h__
+
+#include "nsGlobalWindow.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsDataHashtable.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/TimeStamp.h"
+#include "nsArenaMemoryStats.h"
+
+class nsWindowSizes {
+#define FOR_EACH_SIZE(macro) \
+ macro(DOM, mDOMElementNodesSize) \
+ macro(DOM, mDOMTextNodesSize) \
+ macro(DOM, mDOMCDATANodesSize) \
+ macro(DOM, mDOMCommentNodesSize) \
+ macro(DOM, mDOMEventTargetsSize) \
+ macro(DOM, mDOMOtherSize) \
+ macro(Style, mStyleSheetsSize) \
+ macro(Other, mLayoutPresShellSize) \
+ macro(Style, mLayoutStyleSetsSize) \
+ macro(Other, mLayoutTextRunsSize) \
+ macro(Other, mLayoutPresContextSize) \
+ macro(Other, mPropertyTablesSize) \
+
+public:
+ explicit nsWindowSizes(mozilla::MallocSizeOf aMallocSizeOf)
+ :
+ #define ZERO_SIZE(kind, mSize) mSize(0),
+ FOR_EACH_SIZE(ZERO_SIZE)
+ #undef ZERO_SIZE
+ mDOMEventTargetsCount(0),
+ mDOMEventListenersCount(0),
+ mArenaStats(),
+ mMallocSizeOf(aMallocSizeOf)
+ {}
+
+ void addToTabSizes(nsTabSizes *sizes) const {
+ #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize);
+ FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
+ #undef ADD_TO_TAB_SIZES
+ mArenaStats.addToTabSizes(sizes);
+ }
+
+ size_t getTotalSize() const
+ {
+ size_t total = 0;
+ #define ADD_TO_TOTAL_SIZE(kind, mSize) total += mSize;
+ FOR_EACH_SIZE(ADD_TO_TOTAL_SIZE)
+ #undef ADD_TO_TOTAL_SIZE
+ total += mArenaStats.getTotalSize();
+ return total;
+ }
+
+ #define DECL_SIZE(kind, mSize) size_t mSize;
+ FOR_EACH_SIZE(DECL_SIZE);
+ #undef DECL_SIZE
+
+ uint32_t mDOMEventTargetsCount;
+ uint32_t mDOMEventListenersCount;
+
+ nsArenaMemoryStats mArenaStats;
+ mozilla::MallocSizeOf mMallocSizeOf;
+
+#undef FOR_EACH_SIZE
+};
+
+/**
+ * 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.xul 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(nsGlobalWindow* aWindow);
+
+private:
+ ~nsWindowMemoryReporter();
+
+ /**
+ * nsGhostWindowReporter generates the "ghost-windows" report, which counts
+ * the number of ghost windows present.
+ */
+ class GhostWindowsReporter final : public nsIMemoryReporter
+ {
+ ~GhostWindowsReporter() {}
+ public:
+ NS_DECL_ISUPPORTS
+
+ static int64_t DistinguishedAmount();
+
+ NS_IMETHOD
+ CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+ bool aAnonymize) override
+ {
+ MOZ_COLLECT_REPORT(
+ "ghost-windows", KIND_OTHER, UNITS_COUNT, DistinguishedAmount(),
+"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, does not share a domain 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.");
+
+ return NS_OK;
+ }
+ };
+
+ // 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(nsTHashtable<nsUint64HashKey> *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.)
+ */
+ nsDataHashtable<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;
+};
+
+#endif // nsWindowMemoryReporter_h__
+
diff --git a/dom/base/nsWindowRoot.cpp b/dom/base/nsWindowRoot.cpp
new file mode 100644
index 000000000..b839a0ee5
--- /dev/null
+++ b/dom/base/nsWindowRoot.cpp
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/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 "nsGlobalWindow.h"
+#include "nsFocusManager.h"
+#include "nsIContent.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsIControllers.h"
+#include "nsIController.h"
+#include "xpcpublic.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/TabParent.h"
+
+#ifdef MOZ_XUL
+#include "nsIDOMXULElement.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsWindowRoot::nsWindowRoot(nsPIDOMWindowOuter* aWindow)
+{
+ mWindow = aWindow;
+ MOZ_ASSERT(mWindow->IsOuterWindow());
+
+ // Keyboard indicators are not shown on Mac by default.
+#if defined(XP_MACOSX)
+ mShowAccelerators = false;
+ mShowFocusRings = false;
+#else
+ mShowAccelerators = true;
+ mShowFocusRings = true;
+#endif
+}
+
+nsWindowRoot::~nsWindowRoot()
+{
+ if (mListenerManager) {
+ mListenerManager->Disconnect();
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsWindowRoot,
+ mWindow,
+ mListenerManager,
+ mParent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsWindowRoot)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventTarget)
+ NS_INTERFACE_MAP_ENTRY(nsPIWindowRoot)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsWindowRoot)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsWindowRoot)
+
+NS_IMPL_DOMTARGET_DEFAULTS(nsWindowRoot)
+
+NS_IMETHODIMP
+nsWindowRoot::RemoveEventListener(const nsAString& aType, nsIDOMEventListener* aListener, bool aUseCapture)
+{
+ if (RefPtr<EventListenerManager> elm = GetExistingListenerManager()) {
+ elm->RemoveEventListener(aType, aListener, aUseCapture);
+ }
+ return NS_OK;
+}
+
+NS_IMPL_REMOVE_SYSTEM_EVENT_LISTENER(nsWindowRoot)
+
+NS_IMETHODIMP
+nsWindowRoot::DispatchEvent(nsIDOMEvent* aEvt, bool *aRetVal)
+{
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = EventDispatcher::DispatchDOMEvent(
+ static_cast<EventTarget*>(this), nullptr, aEvt, nullptr, &status);
+ *aRetVal = (status != nsEventStatus_eConsumeNoDefault);
+ return rv;
+}
+
+nsresult
+nsWindowRoot::DispatchDOMEvent(WidgetEvent* aEvent,
+ nsIDOMEvent* aDOMEvent,
+ nsPresContext* aPresContext,
+ nsEventStatus* aEventStatus)
+{
+ return EventDispatcher::DispatchDOMEvent(static_cast<EventTarget*>(this),
+ aEvent, aDOMEvent,
+ aPresContext, aEventStatus);
+}
+
+NS_IMETHODIMP
+nsWindowRoot::AddEventListener(const nsAString& aType,
+ nsIDOMEventListener *aListener,
+ bool aUseCapture, bool aWantsUntrusted,
+ uint8_t aOptionalArgc)
+{
+ NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1,
+ "Won't check if this is chrome, you want to set "
+ "aWantsUntrusted to false or make the aWantsUntrusted "
+ "explicit by making optional_argc non-zero.");
+
+ EventListenerManager* elm = GetOrCreateListenerManager();
+ NS_ENSURE_STATE(elm);
+ elm->AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted);
+ return NS_OK;
+}
+
+void
+nsWindowRoot::AddEventListener(const nsAString& aType,
+ EventListener* aListener,
+ const AddEventListenerOptionsOrBoolean& aOptions,
+ const Nullable<bool>& aWantsUntrusted,
+ ErrorResult& aRv)
+{
+ bool wantsUntrusted = !aWantsUntrusted.IsNull() && aWantsUntrusted.Value();
+ EventListenerManager* elm = GetOrCreateListenerManager();
+ if (!elm) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ elm->AddEventListener(aType, aListener, aOptions, wantsUntrusted);
+}
+
+
+NS_IMETHODIMP
+nsWindowRoot::AddSystemEventListener(const nsAString& aType,
+ nsIDOMEventListener *aListener,
+ bool aUseCapture,
+ bool aWantsUntrusted,
+ uint8_t aOptionalArgc)
+{
+ NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1,
+ "Won't check if this is chrome, you want to set "
+ "aWantsUntrusted to false or make the aWantsUntrusted "
+ "explicit by making optional_argc non-zero.");
+
+ return NS_AddSystemEventListener(this, aType, aListener, aUseCapture,
+ aWantsUntrusted);
+}
+
+EventListenerManager*
+nsWindowRoot::GetOrCreateListenerManager()
+{
+ if (!mListenerManager) {
+ mListenerManager =
+ new EventListenerManager(static_cast<EventTarget*>(this));
+ }
+
+ return mListenerManager;
+}
+
+EventListenerManager*
+nsWindowRoot::GetExistingListenerManager() const
+{
+ return mListenerManager;
+}
+
+nsIScriptContext*
+nsWindowRoot::GetContextForEventHandlers(nsresult* aRv)
+{
+ *aRv = NS_OK;
+ return nullptr;
+}
+
+nsresult
+nsWindowRoot::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mCanHandle = true;
+ aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
+ // To keep mWindow alive
+ aVisitor.mItemData = static_cast<nsISupports *>(mWindow);
+ aVisitor.mParentTarget = mParent;
+ return NS_OK;
+}
+
+nsresult
+nsWindowRoot::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ return NS_OK;
+}
+
+nsPIDOMWindowOuter*
+nsWindowRoot::GetOwnerGlobalForBindings()
+{
+ return GetWindow();
+}
+
+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(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.
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent =
+ nsFocusManager::GetFocusedDescendant(mWindow, true, getter_AddRefs(focusedWindow));
+ if (focusedContent) {
+#ifdef MOZ_XUL
+ nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(focusedContent));
+ if (xulElement)
+ return xulElement->GetControllers(aResult);
+#endif
+
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> htmlTextArea =
+ do_QueryInterface(focusedContent);
+ if (htmlTextArea)
+ return htmlTextArea->GetControllers(aResult);
+
+ nsCOMPtr<nsIDOMHTMLInputElement> htmlInputElement =
+ do_QueryInterface(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,
+ nsIController** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ {
+ nsCOMPtr<nsIControllers> controllers;
+ GetControllers(getter_AddRefs(controllers));
+ if (controllers) {
+ nsCOMPtr<nsIController> controller;
+ controllers->GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (controller) {
+ controller.forget(_retval);
+ return NS_OK;
+ }
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(mWindow, true, 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?
+ nsGlobalWindow *win = nsGlobalWindow::Cast(focusedWindow);
+ focusedWindow = win->GetPrivateParent();
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindowRoot::GetEnabledDisabledCommandsForControllers(nsIControllers* aControllers,
+ nsTHashtable<nsCharPtrHashKey>& 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) {
+ uint32_t commandsCount;
+ char** commands;
+ if (NS_SUCCEEDED(commandController->GetSupportedCommands(&commandsCount, &commands))) {
+ for (uint32_t e = 0; e < commandsCount; e++) {
+ // 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.Contains(commands[e])) {
+ aCommandsHandled.PutEntry(commands[e]);
+
+ bool enabled = false;
+ controller->IsCommandEnabled(commands[e], &enabled);
+
+ const nsDependentCSubstring commandStr(commands[e], strlen(commands[e]));
+ if (enabled) {
+ aEnabledCommands.AppendElement(commandStr);
+ } else {
+ aDisabledCommands.AppendElement(commandStr);
+ }
+ }
+ }
+
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(commandsCount, commands);
+ }
+ }
+ }
+}
+
+void
+nsWindowRoot::GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
+ nsTArray<nsCString>& aDisabledCommands)
+{
+ nsTHashtable<nsCharPtrHashKey> commandsHandled;
+
+ nsCOMPtr<nsIControllers> controllers;
+ GetControllers(getter_AddRefs(controllers));
+ if (controllers) {
+ GetEnabledDisabledCommandsForControllers(controllers, commandsHandled,
+ aEnabledCommands, aDisabledCommands);
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(mWindow, true, getter_AddRefs(focusedWindow));
+ while (focusedWindow) {
+ focusedWindow->GetControllers(getter_AddRefs(controllers));
+ if (controllers) {
+ GetEnabledDisabledCommandsForControllers(controllers, commandsHandled,
+ aEnabledCommands, aDisabledCommands);
+ }
+
+ nsGlobalWindow* win = nsGlobalWindow::Cast(focusedWindow);
+ focusedWindow = win->GetPrivateParent();
+ }
+}
+
+nsIDOMNode*
+nsWindowRoot::GetPopupNode()
+{
+ nsCOMPtr<nsIDOMNode> popupNode = do_QueryReferent(mPopupNode);
+ return popupNode;
+}
+
+void
+nsWindowRoot::SetPopupNode(nsIDOMNode* 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::WindowRootBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+nsWindowRoot::AddBrowser(mozilla::dom::TabParent* aBrowser)
+{
+ nsWeakPtr weakBrowser = do_GetWeakReference(static_cast<nsITabParent*>(aBrowser));
+ mWeakBrowsers.PutEntry(weakBrowser);
+}
+
+void
+nsWindowRoot::RemoveBrowser(mozilla::dom::TabParent* aBrowser)
+{
+ nsWeakPtr weakBrowser = do_GetWeakReference(static_cast<nsITabParent*>(aBrowser));
+ mWeakBrowsers.RemoveEntry(weakBrowser);
+}
+
+void
+nsWindowRoot::EnumerateBrowsers(BrowserEnumerator aEnumFunc, void* aArg)
+{
+ // Collect strong references to all browsers in a separate array in
+ // case aEnumFunc alters mWeakBrowsers.
+ nsTArray<RefPtr<TabParent>> tabParents;
+ for (auto iter = mWeakBrowsers.ConstIter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsITabParent> tabParent(do_QueryReferent(iter.Get()->GetKey()));
+ if (TabParent* tab = TabParent::GetFrom(tabParent)) {
+ tabParents.AppendElement(tab);
+ }
+ }
+
+ for (uint32_t i = 0; i < tabParents.Length(); ++i) {
+ aEnumFunc(tabParents[i], aArg);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+
+already_AddRefed<EventTarget>
+NS_NewWindowRoot(nsPIDOMWindowOuter* aWindow)
+{
+ nsCOMPtr<EventTarget> result = new nsWindowRoot(aWindow);
+ return result.forget();
+}
diff --git a/dom/base/nsWindowRoot.h b/dom/base/nsWindowRoot.h
new file mode 100644
index 000000000..214f49d52
--- /dev/null
+++ b/dom/base/nsWindowRoot.h
@@ -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/. */
+
+#ifndef nsWindowRoot_h__
+#define nsWindowRoot_h__
+
+class nsIDOMEvent;
+class nsIGlobalObject;
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventListenerManager.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsPIWindowRoot.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+
+class nsWindowRoot final : public nsPIWindowRoot
+{
+public:
+ explicit nsWindowRoot(nsPIDOMWindowOuter* aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIDOMEVENTTARGET
+
+ virtual mozilla::EventListenerManager*
+ GetExistingListenerManager() const override;
+ virtual mozilla::EventListenerManager*
+ GetOrCreateListenerManager() override;
+
+ using mozilla::dom::EventTarget::RemoveEventListener;
+ virtual void AddEventListener(const nsAString& aType,
+ mozilla::dom::EventListener* aListener,
+ const mozilla::dom::AddEventListenerOptionsOrBoolean& aOptions,
+ const mozilla::dom::Nullable<bool>& aWantsUntrusted,
+ mozilla::ErrorResult& aRv) override;
+
+ // nsPIWindowRoot
+
+ virtual nsPIDOMWindowOuter* GetWindow() override;
+
+ virtual nsresult GetControllers(nsIControllers** aResult) override;
+ virtual nsresult GetControllerForCommand(const char * aCommand,
+ nsIController** _retval) override;
+
+ virtual void GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
+ nsTArray<nsCString>& aDisabledCommands) override;
+
+ virtual nsIDOMNode* GetPopupNode() override;
+ virtual void SetPopupNode(nsIDOMNode* aNode) override;
+
+ virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) override
+ {
+ mParent = aTarget;
+ }
+ virtual mozilla::dom::EventTarget* GetParentTarget() override { return mParent; }
+ virtual nsPIDOMWindowOuter* GetOwnerGlobalForBindings() override;
+ virtual nsIGlobalObject* GetOwnerGlobal() const override;
+
+ nsIGlobalObject* GetParentObject();
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsWindowRoot,
+ nsIDOMEventTarget)
+
+ virtual void AddBrowser(mozilla::dom::TabParent* aBrowser) override;
+ virtual void RemoveBrowser(mozilla::dom::TabParent* aBrowser) override;
+ virtual void EnumerateBrowsers(BrowserEnumerator aEnumFunc, void *aArg) override;
+
+ virtual bool ShowAccelerators() override
+ {
+ return mShowAccelerators;
+ }
+
+ virtual bool ShowFocusRings() override
+ {
+ return mShowFocusRings;
+ }
+
+ virtual void SetShowAccelerators(bool aEnable) override
+ {
+ mShowAccelerators = aEnable;
+ }
+
+ virtual void SetShowFocusRings(bool aEnable) override
+ {
+ mShowFocusRings = aEnable;
+ }
+
+protected:
+ virtual ~nsWindowRoot();
+
+ void GetEnabledDisabledCommandsForControllers(nsIControllers* aControllers,
+ nsTHashtable<nsCharPtrHashKey>& 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;
+
+ // True if focus rings and accelerators are enabled for this
+ // window hierarchy.
+ bool mShowAccelerators;
+ bool mShowFocusRings;
+
+ nsCOMPtr<mozilla::dom::EventTarget> mParent;
+
+ // The TabParents that are currently registered with this top-level window.
+ typedef nsTHashtable<nsRefPtrHashKey<nsIWeakReference>> WeakBrowserTable;
+ WeakBrowserTable mWeakBrowsers;
+};
+
+extern already_AddRefed<mozilla::dom::EventTarget>
+NS_NewWindowRoot(nsPIDOMWindowOuter* aWindow);
+
+#endif
diff --git a/dom/base/nsWrapperCache.cpp b/dom/base/nsWrapperCache.cpp
new file mode 100644
index 000000000..b91d86598
--- /dev/null
+++ b/dom/base/nsWrapperCache.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "js/Class.h"
+#include "js/Proxy.h"
+#include "mozilla/dom/DOMJSProxyHandler.h"
+#include "mozilla/CycleCollectedJSContext.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)
+{
+ cyclecollector::HoldJSObjectsImpl(aScriptObjectHolder, aTracer);
+ if (mWrapper && !JS::ObjectIsTenured(mWrapper)) {
+ CycleCollectedJSContext::Get()->NurseryWrapperPreserved(mWrapper);
+ }
+}
+
+void
+nsWrapperCache::SetWrapperJSObject(JSObject* aWrapper)
+{
+ mWrapper = aWrapper;
+ UnsetWrapperFlags(kWrapperFlagsMask & ~WRAPPER_IS_NOT_DOM_BINDING);
+
+ if (aWrapper && !JS::ObjectIsTenured(aWrapper)) {
+ CycleCollectedJSContext::Get()->NurseryWrapperAdded(this);
+ }
+}
+
+void
+nsWrapperCache::ReleaseWrapper(void* aScriptObjectHolder)
+{
+ if (PreservingWrapper()) {
+ // PreserveWrapper puts new DOM bindings in the JS holders hash, but they
+ // can also be in the DOM expando hash, so we need to try to remove them
+ // from both here.
+ JSObject* obj = GetWrapperPreserveColor();
+ if (IsDOMBinding() && obj && js::IsProxy(obj)) {
+ DOMProxyHandler::ClearExternalRefsForWrapperRelease(obj);
+ }
+ SetPreservingWrapper(false);
+ cyclecollector::DropJSObjectsImpl(aScriptObjectHolder);
+ }
+}
+
+#ifdef DEBUG
+
+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)
+ {
+ }
+ NS_IMETHOD_(void) DescribeGCedNode(bool aIsMarked,
+ const char* aObjName,
+ uint64_t aCompartmentAddress)
+ {
+ }
+
+ NS_IMETHOD_(void) NoteJSChild(const JS::GCCellPtr& aChild)
+ {
+ if (aChild == mWrapper) {
+ mFound = true;
+ }
+ }
+ NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild)
+ {
+ }
+ NS_IMETHOD_(void) NoteNativeChild(void* aChild,
+ nsCycleCollectionParticipant* aHelper)
+ {
+ }
+
+ NS_IMETHOD_(void) NoteNextEdgeName(const char* aName)
+ {
+ }
+
+ 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 = GetWrapper();
+ if (!wrapper) {
+ return;
+ }
+
+ 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->Traverse(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.");
+}
+
+#endif // DEBUG
diff --git a/dom/base/nsWrapperCache.h b/dom/base/nsWrapperCache.h
new file mode 100644
index 000000000..3c69a7ec4
--- /dev/null
+++ b/dom/base/nsWrapperCache.h
@@ -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/. */
+
+#ifndef nsWrapperCache_h___
+#define nsWrapperCache_h___
+
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Assertions.h"
+#include "js/Id.h" // must come before js/RootingAPI.h
+#include "js/Value.h" // must come before js/RootingAPI.h
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+
+namespace mozilla {
+namespace dom {
+class TabChildGlobal;
+class ProcessGlobal;
+} // namespace dom
+} // namespace mozilla
+class SandboxPrivate;
+class nsInProcessTabChildGlobal;
+class nsWindowRoot;
+
+#define NS_WRAPPERCACHE_IID \
+{ 0x6f3179a1, 0x36f7, 0x4a5c, \
+ { 0x8c, 0xf1, 0xad, 0xc8, 0x7c, 0xde, 0x3e, 0x87 } }
+
+/**
+ * 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 2 types of objects:
+ *
+ * If WRAPPER_IS_NOT_DOM_BINDING is set (IsDOMBinding() returns false):
+ * - the JSObject of an XPCWrappedNative wrapper
+ *
+ * If WRAPPER_IS_NOT_DOM_BINDING is not set (IsDOMBinding() returns true):
+ * - a DOM binding object (regular JS object or proxy)
+ *
+ * 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 nsWrapperCache
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_WRAPPERCACHE_IID)
+
+ nsWrapperCache() : mWrapper(nullptr), mFlags(0)
+ {
+ }
+ ~nsWrapperCache()
+ {
+ MOZ_ASSERT(!PreservingWrapper(),
+ "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 JS API function and that it won't be stored without being
+ * rooted (or otherwise signaling the stored value to the CC).
+ */
+ JSObject* GetWrapperPreserveColor() const
+ {
+ return GetWrapperJSObject();
+ }
+
+#ifdef DEBUG
+private:
+ static bool HasJSObjectMovedOp(JSObject* aWrapper);
+
+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 wrapper. This should be called from the finalizer for the
+ * wrapper.
+ */
+ void ClearWrapper()
+ {
+ MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
+
+ SetWrapperJSObject(nullptr);
+ }
+
+ /**
+ * 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)
+ {
+ if (mWrapper) {
+ MOZ_ASSERT(mWrapper == aOldObject);
+ mWrapper = aNewObject;
+ }
+ }
+
+ bool PreservingWrapper()
+ {
+ return HasWrapperFlag(WRAPPER_BIT_PRESERVED);
+ }
+
+ bool IsDOMBinding() const
+ {
+ return !HasWrapperFlag(WRAPPER_IS_NOT_DOM_BINDING);
+ }
+
+ /**
+ * 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 non-gray wrapper.
+ */
+ bool IsBlack();
+
+ /**
+ * Returns true if the object has a black wrapper,
+ * and all the GC things it is keeping alive are black too.
+ */
+ bool IsBlackAndDoesNotNeedTracing(nsISupports* aThis);
+
+ bool HasNothingToTrace(nsISupports* aThis);
+
+ // 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(&mWrapper, "Preserved wrapper", aClosure);
+ }
+ }
+
+ /*
+ * The following methods for getting and manipulating flags allow the unused
+ * bits of mFlags to be used by derived classes.
+ */
+
+ typedef uint32_t FlagsType;
+
+ FlagsType GetFlags() const
+ {
+ return mFlags & ~kWrapperFlagsMask;
+ }
+
+ bool HasFlag(FlagsType aFlag) const
+ {
+ MOZ_ASSERT((aFlag & kWrapperFlagsMask) == 0, "Bad flag mask");
+ return !!(mFlags & aFlag);
+ }
+
+ void SetFlags(FlagsType aFlagsToSet)
+ {
+ MOZ_ASSERT((aFlagsToSet & kWrapperFlagsMask) == 0, "Bad flag mask");
+ mFlags |= aFlagsToSet;
+ }
+
+ void UnsetFlags(FlagsType aFlagsToUnset)
+ {
+ MOZ_ASSERT((aFlagsToUnset & kWrapperFlagsMask) == 0, "Bad flag mask");
+ mFlags &= ~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;
+ }
+
+ HoldJSObjects(aScriptObjectHolder, aTracer);
+ 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);
+
+protected:
+ void TraceWrapper(JSTracer* aTrc, const char* name)
+ {
+ if (mWrapper) {
+ js::UnsafeTraceManuallyBarrieredEdge(aTrc, &mWrapper, name);
+ }
+ }
+
+ 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:
+ friend class mozilla::dom::TabChildGlobal;
+ friend class mozilla::dom::ProcessGlobal;
+ friend class SandboxPrivate;
+ friend class nsInProcessTabChildGlobal;
+ friend class nsWindowRoot;
+ void SetIsNotDOMBinding()
+ {
+ MOZ_ASSERT(!mWrapper && !(GetWrapperFlags() & ~WRAPPER_IS_NOT_DOM_BINDING),
+ "This flag should be set before creating any wrappers.");
+ SetWrapperFlags(WRAPPER_IS_NOT_DOM_BINDING);
+ }
+
+ JSObject *GetWrapperJSObject() const
+ {
+ return mWrapper;
+ }
+
+ void SetWrapperJSObject(JSObject* aWrapper);
+
+ FlagsType GetWrapperFlags() const
+ {
+ return mFlags & kWrapperFlagsMask;
+ }
+
+ bool HasWrapperFlag(FlagsType aFlag) const
+ {
+ MOZ_ASSERT((aFlag & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
+ return !!(mFlags & aFlag);
+ }
+
+ void SetWrapperFlags(FlagsType aFlagsToSet)
+ {
+ MOZ_ASSERT((aFlagsToSet & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
+ mFlags |= aFlagsToSet;
+ }
+
+ void UnsetWrapperFlags(FlagsType aFlagsToUnset)
+ {
+ MOZ_ASSERT((aFlagsToUnset & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
+ mFlags &= ~aFlagsToUnset;
+ }
+
+ void HoldJSObjects(void* aScriptObjectHolder,
+ nsScriptObjectTracer* aTracer);
+
+#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,
+ * NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS and
+ * NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER).
+ */
+ enum { WRAPPER_BIT_PRESERVED = 1 << 0 };
+
+ /**
+ * If this bit is set then the wrapper for the native object is not a DOM
+ * binding.
+ */
+ enum { WRAPPER_IS_NOT_DOM_BINDING = 1 << 1 };
+
+ enum { kWrapperFlagsMask = (WRAPPER_BIT_PRESERVED | WRAPPER_IS_NOT_DOM_BINDING) };
+
+ JSObject* mWrapper;
+ FlagsType mFlags;
+};
+
+enum { WRAPPER_CACHE_FLAGS_BITS_USED = 2 };
+
+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.
+
+#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_TRACE_WRAPPERCACHE(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(_class) \
+ NS_IMPL_CYCLE_COLLECTION_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_SCRIPT_OBJECTS \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(_class)
+
+#define NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(_class, ...) \
+ NS_IMPL_CYCLE_COLLECTION_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_SCRIPT_OBJECTS \
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \
+ NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(_class)
+
+#endif /* nsWrapperCache_h___ */
diff --git a/dom/base/nsWrapperCacheInlines.h b/dom/base/nsWrapperCacheInlines.h
new file mode 100644
index 000000000..f91e5db59
--- /dev/null
+++ b/dom/base/nsWrapperCacheInlines.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 nsWrapperCacheInline_h___
+#define nsWrapperCacheInline_h___
+
+#include "nsWrapperCache.h"
+#include "js/GCAPI.h"
+#include "js/TracingAPI.h"
+
+inline JSObject*
+nsWrapperCache::GetWrapper() const
+{
+ JSObject* obj = GetWrapperPreserveColor();
+ if (obj) {
+ JS::ExposeObjectToActiveJS(obj);
+ }
+ return obj;
+}
+
+inline bool
+nsWrapperCache::IsBlack()
+{
+ 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::IsBlackAndDoesNotNeedTracing(nsISupports* aThis)
+{
+ return IsBlack() && HasNothingToTrace(aThis);
+}
+
+#endif /* nsWrapperCache_h___ */
diff --git a/dom/base/nsXHTMLContentSerializer.cpp b/dom/base/nsXHTMLContentSerializer.cpp
new file mode 100644
index 000000000..0dc31d7ae
--- /dev/null
+++ b/dom/base/nsXHTMLContentSerializer.cpp
@@ -0,0 +1,947 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsIContentSerializer implementation that can be used with an
+ * nsIDocumentEncoder to convert an XHTML (not HTML!) DOM to an XHTML
+ * string that could be parsed into more or less the original DOM.
+ */
+
+#include "nsXHTMLContentSerializer.h"
+
+#include "nsIDOMElement.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsNameSpaceManager.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsXPIDLString.h"
+#include "nsIServiceManager.h"
+#include "nsIDocumentEncoder.h"
+#include "nsGkAtoms.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsEscape.h"
+#include "nsITextToSubURI.h"
+#include "nsCRT.h"
+#include "nsIParserService.h"
+#include "nsContentUtils.h"
+#include "nsLWBrkCIID.h"
+#include "nsIScriptElement.h"
+#include "nsStubMutationObserver.h"
+#include "nsAttrName.h"
+#include "nsParserConstants.h"
+#include "nsComputedDOMStyle.h"
+#include "mozilla/dom/Element.h"
+
+static const int32_t kLongLineLen = 128;
+
+#define kXMLNS "xmlns"
+
+nsresult
+NS_NewXHTMLContentSerializer(nsIContentSerializer** aSerializer)
+{
+ RefPtr<nsXHTMLContentSerializer> it = new nsXHTMLContentSerializer();
+ it.forget(aSerializer);
+ return NS_OK;
+}
+
+nsXHTMLContentSerializer::nsXHTMLContentSerializer()
+ : mIsHTMLSerializer(false)
+{
+}
+
+nsXHTMLContentSerializer::~nsXHTMLContentSerializer()
+{
+ NS_ASSERTION(mOLStateStack.IsEmpty(), "Expected OL State stack to be empty");
+}
+
+NS_IMETHODIMP
+nsXHTMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
+ const char* aCharSet, bool aIsCopying,
+ bool aRewriteEncodingDeclaration)
+{
+ // The previous version of the HTML serializer did implicit wrapping
+ // when there is no flags, so we keep wrapping in order to keep
+ // compatibility with the existing calling code
+ // XXXLJ perhaps should we remove this default settings later ?
+ if (aFlags & nsIDocumentEncoder::OutputFormatted ) {
+ aFlags = aFlags | nsIDocumentEncoder::OutputWrap;
+ }
+
+ nsresult rv;
+ rv = nsXMLContentSerializer::Init(aFlags, aWrapColumn, aCharSet, aIsCopying, aRewriteEncodingDeclaration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRewriteEncodingDeclaration = aRewriteEncodingDeclaration;
+ mIsCopying = aIsCopying;
+ mIsFirstChildOfOL = false;
+ mInBody = 0;
+ mDisableEntityEncoding = 0;
+ mBodyOnly = (mFlags & nsIDocumentEncoder::OutputBodyOnly) ? true
+ : false;
+
+ // set up entity converter if we are going to need it
+ if (mFlags & nsIDocumentEncoder::OutputEncodeW3CEntities) {
+ mEntityConverter = do_CreateInstance(NS_ENTITYCONVERTER_CONTRACTID);
+ }
+ return NS_OK;
+}
+
+
+// See if the string has any lines longer than longLineLen:
+// if so, we presume formatting is wonky (e.g. the node has been edited)
+// and we'd better rewrap the whole text node.
+bool
+nsXHTMLContentSerializer::HasLongLines(const nsString& text, int32_t& aLastNewlineOffset)
+{
+ uint32_t start=0;
+ uint32_t theLen = text.Length();
+ bool rv = false;
+ aLastNewlineOffset = kNotFound;
+ for (start = 0; start < theLen; ) {
+ int32_t eol = text.FindChar('\n', start);
+ if (eol < 0) {
+ eol = text.Length();
+ }
+ else {
+ aLastNewlineOffset = eol;
+ }
+ if (int32_t(eol - start) > kLongLineLen)
+ rv = true;
+ start = eol + 1;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsXHTMLContentSerializer::AppendText(nsIContent* aText,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aText);
+
+ nsAutoString data;
+ nsresult rv;
+
+ rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ if (mDoRaw || PreLevel() > 0) {
+ NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoFormat) {
+ NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoWrap) {
+ NS_ENSURE_TRUE(AppendToStringWrapped(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ int32_t lastNewlineOffset = kNotFound;
+ if (HasLongLines(data, lastNewlineOffset)) {
+ // We have long lines, rewrap
+ mDoWrap = true;
+ bool result = AppendToStringWrapped(data, aStr);
+ mDoWrap = false;
+ NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXHTMLContentSerializer::EscapeURI(nsIContent* aContent, const nsAString& aURI, nsAString& aEscapedURI)
+{
+ // URL escape %xx cannot be used in JS.
+ // No escaping if the scheme is 'javascript'.
+ if (IsJavaScript(aContent, nsGkAtoms::href, kNameSpaceID_None, aURI)) {
+ aEscapedURI = aURI;
+ return NS_OK;
+ }
+
+ // nsITextToSubURI does charset convert plus uri escape
+ // This is needed to convert to a document charset which is needed to support existing browsers.
+ // But we eventually want to use UTF-8 instead of a document charset, then the code would be much simpler.
+ // See HTML 4.01 spec, "Appendix B.2.1 Non-ASCII characters in URI attribute values"
+ nsCOMPtr<nsITextToSubURI> textToSubURI;
+ nsAutoString uri(aURI); // in order to use FindCharInSet()
+ nsresult rv = NS_OK;
+
+ if (!mCharset.IsEmpty() && !IsASCII(uri)) {
+ textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int32_t start = 0;
+ int32_t end;
+ nsAutoString part;
+ nsXPIDLCString escapedURI;
+ aEscapedURI.Truncate(0);
+
+ // Loop and escape parts by avoiding escaping reserved characters
+ // (and '%', '#', as well as '[' and ']' for IPv6 address literals).
+ while ((end = uri.FindCharInSet("%#;/?:@&=+$,[]", start)) != -1) {
+ part = Substring(aURI, start, (end-start));
+ if (textToSubURI && !IsASCII(part)) {
+ rv = textToSubURI->ConvertAndEscape(mCharset.get(), part.get(), getter_Copies(escapedURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(part), escapedURI,
+ url_Path))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ AppendASCIItoUTF16(escapedURI, aEscapedURI);
+
+ // Append a reserved character without escaping.
+ part = Substring(aURI, end, 1);
+ aEscapedURI.Append(part);
+ start = end + 1;
+ }
+
+ if (start < (int32_t) aURI.Length()) {
+ // Escape the remaining part.
+ part = Substring(aURI, start, aURI.Length()-start);
+ if (textToSubURI) {
+ rv = textToSubURI->ConvertAndEscape(mCharset.get(), part.get(), getter_Copies(escapedURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(part), escapedURI,
+ url_Path))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ AppendASCIItoUTF16(escapedURI, aEscapedURI);
+ }
+
+ return rv;
+}
+
+bool
+nsXHTMLContentSerializer::SerializeAttributes(nsIContent* aContent,
+ nsIContent *aOriginalElement,
+ nsAString& aTagPrefix,
+ const nsAString& aTagNamespaceURI,
+ nsIAtom* aTagName,
+ nsAString& aStr,
+ uint32_t aSkipAttr,
+ bool aAddNSAttr)
+{
+ nsresult rv;
+ uint32_t index, count;
+ nsAutoString prefixStr, uriStr, valueStr;
+ nsAutoString xmlnsStr;
+ xmlnsStr.AssignLiteral(kXMLNS);
+
+ int32_t contentNamespaceID = aContent->GetNameSpaceID();
+
+ // this method is not called by nsHTMLContentSerializer
+ // so we don't have to check HTML element, just XHTML
+
+ if (mIsCopying && kNameSpaceID_XHTML == contentNamespaceID) {
+
+ // Need to keep track of OL and LI elements in order to get ordinal number
+ // for the LI.
+ if (aTagName == nsGkAtoms::ol) {
+ // We are copying and current node is an OL;
+ // Store its start attribute value in olState->startVal.
+ nsAutoString start;
+ int32_t startAttrVal = 0;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::start, start);
+ if (!start.IsEmpty()) {
+ nsresult rv = NS_OK;
+ startAttrVal = start.ToInteger(&rv);
+ //If OL has "start" attribute, first LI element has to start with that value
+ //Therefore subtracting 1 as all the LI elements are incrementing it before using it;
+ //In failure of ToInteger(), default StartAttrValue to 0.
+ if (NS_SUCCEEDED(rv))
+ --startAttrVal;
+ else
+ startAttrVal = 0;
+ }
+ olState state (startAttrVal, true);
+ mOLStateStack.AppendElement(state);
+ }
+ else if (aTagName == nsGkAtoms::li) {
+ mIsFirstChildOfOL = IsFirstChildOfOL(aOriginalElement);
+ if (mIsFirstChildOfOL) {
+ // If OL is parent of this LI, serialize attributes in different manner.
+ NS_ENSURE_TRUE(SerializeLIValueAttribute(aContent, aStr), false);
+ }
+ }
+ }
+
+ // If we had to add a new namespace declaration, serialize
+ // and push it on the namespace stack
+ if (aAddNSAttr) {
+ if (aTagPrefix.IsEmpty()) {
+ // Serialize default namespace decl
+ NS_ENSURE_TRUE(SerializeAttr(EmptyString(), xmlnsStr,
+ aTagNamespaceURI,
+ aStr, true), false);
+ } else {
+ // Serialize namespace decl
+ NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, aTagPrefix,
+ aTagNamespaceURI,
+ aStr, true), false);
+ }
+ PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement);
+ }
+
+ NS_NAMED_LITERAL_STRING(_mozStr, "_moz");
+
+ count = aContent->GetAttrCount();
+
+ // Now serialize each of the attributes
+ // XXX Unfortunately we need a namespace manager to get
+ // attribute URIs.
+ for (index = 0; index < count; index++) {
+
+ if (aSkipAttr == index) {
+ continue;
+ }
+
+ BorrowedAttrInfo info = aContent->GetAttrInfoAt(index);
+ const nsAttrName* name = info.mName;
+
+ int32_t namespaceID = name->NamespaceID();
+ nsIAtom* attrName = name->LocalName();
+ nsIAtom* attrPrefix = name->GetPrefix();
+
+ // Filter out any attribute starting with [-|_]moz
+ nsDependentAtomString attrNameStr(attrName);
+ if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
+ StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
+ continue;
+ }
+
+ if (attrPrefix) {
+ attrPrefix->ToString(prefixStr);
+ }
+ else {
+ prefixStr.Truncate();
+ }
+
+ bool addNSAttr = false;
+ if (kNameSpaceID_XMLNS != namespaceID) {
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr);
+ addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true);
+ }
+
+ info.mValue->ToString(valueStr);
+
+ nsDependentAtomString nameStr(attrName);
+ bool isJS = false;
+
+ if (kNameSpaceID_XHTML == contentNamespaceID) {
+ //
+ // Filter out special case of <br type="_moz"> or <br _moz*>,
+ // used by the editor. Bug 16988. Yuck.
+ //
+ if (namespaceID == kNameSpaceID_None && aTagName == nsGkAtoms::br && attrName == nsGkAtoms::type
+ && StringBeginsWith(valueStr, _mozStr)) {
+ continue;
+ }
+
+ if (mIsCopying && mIsFirstChildOfOL && (aTagName == nsGkAtoms::li)
+ && (attrName == nsGkAtoms::value)) {
+ // This is handled separately in SerializeLIValueAttribute()
+ continue;
+ }
+
+ isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr);
+
+ if (namespaceID == kNameSpaceID_None &&
+ ((attrName == nsGkAtoms::href) ||
+ (attrName == nsGkAtoms::src))) {
+ // Make all links absolute when converting only the selection:
+ if (mFlags & nsIDocumentEncoder::OutputAbsoluteLinks) {
+ // Would be nice to handle OBJECT and APPLET tags,
+ // but that gets more complicated since we have to
+ // search the tag list for CODEBASE as well.
+ // For now, just leave them relative.
+ nsCOMPtr<nsIURI> uri = aContent->GetBaseURI();
+ if (uri) {
+ nsAutoString absURI;
+ rv = NS_MakeAbsoluteURI(absURI, valueStr, uri);
+ if (NS_SUCCEEDED(rv)) {
+ valueStr = absURI;
+ }
+ }
+ }
+ // Need to escape URI.
+ nsAutoString tempURI(valueStr);
+ if (!isJS && NS_FAILED(EscapeURI(aContent, tempURI, valueStr)))
+ valueStr = tempURI;
+ }
+
+ if (mRewriteEncodingDeclaration && aTagName == nsGkAtoms::meta &&
+ attrName == nsGkAtoms::content) {
+ // If we're serializing a <meta http-equiv="content-type">,
+ // use the proper value, rather than what's in the document.
+ nsAutoString header;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header);
+ if (header.LowerCaseEqualsLiteral("content-type")) {
+ valueStr = NS_LITERAL_STRING("text/html; charset=") +
+ NS_ConvertASCIItoUTF16(mCharset);
+ }
+ }
+
+ // Expand shorthand attribute.
+ if (namespaceID == kNameSpaceID_None && IsShorthandAttr(attrName, aTagName) && valueStr.IsEmpty()) {
+ valueStr = nameStr;
+ }
+ }
+ else {
+ isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr);
+ }
+
+ NS_ENSURE_TRUE(SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS), false);
+
+ if (addNSAttr) {
+ NS_ASSERTION(!prefixStr.IsEmpty(),
+ "Namespaced attributes must have a prefix");
+ NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true), false);
+ PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement);
+ }
+ }
+
+ return true;
+}
+
+bool
+nsXHTMLContentSerializer::AfterElementStart(nsIContent* aContent,
+ nsIContent* aOriginalElement,
+ nsAString& aStr)
+{
+ if (mRewriteEncodingDeclaration &&
+ aContent->IsHTMLElement(nsGkAtoms::head)) {
+
+ // Check if there already are any content-type meta children.
+ // If there are, they will be modified to use the correct charset.
+ // If there aren't, we'll insert one here.
+ bool hasMeta = false;
+ for (nsIContent* child = aContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::meta) &&
+ child->HasAttr(kNameSpaceID_None, nsGkAtoms::content)) {
+ nsAutoString header;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header);
+
+ if (header.LowerCaseEqualsLiteral("content-type")) {
+ hasMeta = true;
+ break;
+ }
+ }
+ }
+
+ if (!hasMeta) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), false);
+ if (mDoFormat) {
+ NS_ENSURE_TRUE(AppendIndentation(aStr), false);
+ }
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("<meta http-equiv=\"content-type\""), aStr), false);
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" content=\"text/html; charset="), aStr), false);
+ NS_ENSURE_TRUE(AppendToString(NS_ConvertASCIItoUTF16(mCharset), aStr), false);
+ if (mIsHTMLSerializer) {
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("\">"), aStr), false);
+ } else {
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("\" />"), aStr), false);
+ }
+ }
+ }
+
+ return true;
+}
+
+void
+nsXHTMLContentSerializer::AfterElementEnd(nsIContent * aContent,
+ nsAString& aStr)
+{
+ NS_ASSERTION(!mIsHTMLSerializer, "nsHTMLContentSerializer shouldn't call this method !");
+
+ // this method is not called by nsHTMLContentSerializer
+ // so we don't have to check HTML element, just XHTML
+ if (aContent->IsHTMLElement(nsGkAtoms::body)) {
+ --mInBody;
+ }
+}
+
+
+NS_IMETHODIMP
+nsXHTMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr)
+{
+ if (!mBodyOnly)
+ return nsXMLContentSerializer::AppendDocumentStart(aDocument, aStr);
+
+ return NS_OK;
+}
+
+bool
+nsXHTMLContentSerializer::CheckElementStart(nsIContent * aContent,
+ bool & aForceFormat,
+ nsAString& aStr,
+ nsresult& aResult)
+{
+ aResult = NS_OK;
+
+ // The _moz_dirty attribute is emitted by the editor to
+ // indicate that this element should be pretty printed
+ // even if we're not in pretty printing mode
+ aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) &&
+ aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty);
+
+ if (aContent->IsHTMLElement(nsGkAtoms::br) &&
+ (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre) &&
+ PreLevel() > 0) {
+ aResult = AppendNewLineToString(aStr) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ return false;
+ }
+
+ if (aContent->IsHTMLElement(nsGkAtoms::body)) {
+ ++mInBody;
+ }
+
+ return true;
+}
+
+bool
+nsXHTMLContentSerializer::CheckElementEnd(mozilla::dom::Element* aElement,
+ bool& aForceFormat,
+ nsAString& aStr)
+{
+ NS_ASSERTION(!mIsHTMLSerializer, "nsHTMLContentSerializer shouldn't call this method !");
+
+ aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) &&
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty);
+
+ if (mIsCopying && aElement->IsHTMLElement(nsGkAtoms::ol)) {
+ NS_ASSERTION((!mOLStateStack.IsEmpty()), "Cannot have an empty OL Stack");
+ /* Though at this point we must always have an state to be deleted as all
+ the OL opening tags are supposed to push an olState object to the stack*/
+ if (!mOLStateStack.IsEmpty()) {
+ mOLStateStack.RemoveElementAt(mOLStateStack.Length() -1);
+ }
+ }
+
+ bool dummyFormat;
+ return nsXMLContentSerializer::CheckElementEnd(aElement, dummyFormat, aStr);
+}
+
+bool
+nsXHTMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
+ nsAString& aOutputStr)
+{
+ if (mBodyOnly && !mInBody) {
+ return true;
+ }
+
+ if (mDisableEntityEncoding) {
+ return aOutputStr.Append(aStr, mozilla::fallible);
+ }
+
+ return nsXMLContentSerializer::AppendAndTranslateEntities(aStr, aOutputStr);
+}
+
+bool
+nsXHTMLContentSerializer::IsShorthandAttr(const nsIAtom* aAttrName,
+ const nsIAtom* aElementName)
+{
+ // checked
+ if ((aAttrName == nsGkAtoms::checked) &&
+ (aElementName == nsGkAtoms::input)) {
+ return true;
+ }
+
+ // compact
+ if ((aAttrName == nsGkAtoms::compact) &&
+ (aElementName == nsGkAtoms::dir ||
+ aElementName == nsGkAtoms::dl ||
+ aElementName == nsGkAtoms::menu ||
+ aElementName == nsGkAtoms::ol ||
+ aElementName == nsGkAtoms::ul)) {
+ return true;
+ }
+
+ // declare
+ if ((aAttrName == nsGkAtoms::declare) &&
+ (aElementName == nsGkAtoms::object)) {
+ return true;
+ }
+
+ // defer
+ if ((aAttrName == nsGkAtoms::defer) &&
+ (aElementName == nsGkAtoms::script)) {
+ return true;
+ }
+
+ // disabled
+ if ((aAttrName == nsGkAtoms::disabled) &&
+ (aElementName == nsGkAtoms::button ||
+ aElementName == nsGkAtoms::input ||
+ aElementName == nsGkAtoms::optgroup ||
+ aElementName == nsGkAtoms::option ||
+ aElementName == nsGkAtoms::select ||
+ aElementName == nsGkAtoms::textarea)) {
+ return true;
+ }
+
+ // ismap
+ if ((aAttrName == nsGkAtoms::ismap) &&
+ (aElementName == nsGkAtoms::img ||
+ aElementName == nsGkAtoms::input)) {
+ return true;
+ }
+
+ // multiple
+ if ((aAttrName == nsGkAtoms::multiple) &&
+ (aElementName == nsGkAtoms::select)) {
+ return true;
+ }
+
+ // noresize
+ if ((aAttrName == nsGkAtoms::noresize) &&
+ (aElementName == nsGkAtoms::frame)) {
+ return true;
+ }
+
+ // noshade
+ if ((aAttrName == nsGkAtoms::noshade) &&
+ (aElementName == nsGkAtoms::hr)) {
+ return true;
+ }
+
+ // nowrap
+ if ((aAttrName == nsGkAtoms::nowrap) &&
+ (aElementName == nsGkAtoms::td ||
+ aElementName == nsGkAtoms::th)) {
+ return true;
+ }
+
+ // readonly
+ if ((aAttrName == nsGkAtoms::readonly) &&
+ (aElementName == nsGkAtoms::input ||
+ aElementName == nsGkAtoms::textarea)) {
+ return true;
+ }
+
+ // selected
+ if ((aAttrName == nsGkAtoms::selected) &&
+ (aElementName == nsGkAtoms::option)) {
+ return true;
+ }
+
+ // autoplay and controls
+ if ((aElementName == nsGkAtoms::video || aElementName == nsGkAtoms::audio) &&
+ (aAttrName == nsGkAtoms::autoplay || aAttrName == nsGkAtoms::muted ||
+ aAttrName == nsGkAtoms::controls)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsXHTMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName)
+{
+
+ if (aNamespaceID != kNameSpaceID_XHTML) {
+ return mAddSpace;
+ }
+
+ if (aName == nsGkAtoms::title ||
+ aName == nsGkAtoms::meta ||
+ aName == nsGkAtoms::link ||
+ aName == nsGkAtoms::style ||
+ aName == nsGkAtoms::select ||
+ aName == nsGkAtoms::option ||
+ aName == nsGkAtoms::script ||
+ aName == nsGkAtoms::html) {
+ return true;
+ }
+ else {
+ nsIParserService* parserService = nsContentUtils::GetParserService();
+
+ if (parserService) {
+ bool res;
+ parserService->
+ IsBlock(parserService->HTMLCaseSensitiveAtomTagToId(aName), res);
+ return res;
+ }
+ }
+
+ return mAddSpace;
+}
+
+bool
+nsXHTMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName)
+{
+
+ if (aNamespaceID != kNameSpaceID_XHTML) {
+ return false;
+ }
+
+ if ((aName == nsGkAtoms::html) ||
+ (aName == nsGkAtoms::head) ||
+ (aName == nsGkAtoms::body) ||
+ (aName == nsGkAtoms::ul) ||
+ (aName == nsGkAtoms::ol) ||
+ (aName == nsGkAtoms::dl) ||
+ (aName == nsGkAtoms::table) ||
+ (aName == nsGkAtoms::tbody) ||
+ (aName == nsGkAtoms::tr) ||
+ (aName == nsGkAtoms::br) ||
+ (aName == nsGkAtoms::meta) ||
+ (aName == nsGkAtoms::link) ||
+ (aName == nsGkAtoms::script) ||
+ (aName == nsGkAtoms::select) ||
+ (aName == nsGkAtoms::map) ||
+ (aName == nsGkAtoms::area) ||
+ (aName == nsGkAtoms::style)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsXHTMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName)
+{
+
+ if (aNamespaceID != kNameSpaceID_XHTML) {
+ return false;
+ }
+
+ if ((aName == nsGkAtoms::html) ||
+ (aName == nsGkAtoms::head) ||
+ (aName == nsGkAtoms::body) ||
+ (aName == nsGkAtoms::ul) ||
+ (aName == nsGkAtoms::ol) ||
+ (aName == nsGkAtoms::dl) ||
+ (aName == nsGkAtoms::select) ||
+ (aName == nsGkAtoms::table) ||
+ (aName == nsGkAtoms::tbody)) {
+ return true;
+ }
+ return false;
+}
+
+bool
+nsXHTMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName)
+{
+
+ if (aNamespaceID != kNameSpaceID_XHTML) {
+ return false;
+ }
+
+ if ((aName == nsGkAtoms::html) ||
+ (aName == nsGkAtoms::head) ||
+ (aName == nsGkAtoms::body) ||
+ (aName == nsGkAtoms::tr) ||
+ (aName == nsGkAtoms::th) ||
+ (aName == nsGkAtoms::td) ||
+ (aName == nsGkAtoms::pre) ||
+ (aName == nsGkAtoms::title) ||
+ (aName == nsGkAtoms::li) ||
+ (aName == nsGkAtoms::dt) ||
+ (aName == nsGkAtoms::dd) ||
+ (aName == nsGkAtoms::blockquote) ||
+ (aName == nsGkAtoms::select) ||
+ (aName == nsGkAtoms::option) ||
+ (aName == nsGkAtoms::p) ||
+ (aName == nsGkAtoms::map) ||
+ (aName == nsGkAtoms::div)) {
+ return true;
+ }
+ else {
+ nsIParserService* parserService = nsContentUtils::GetParserService();
+
+ if (parserService) {
+ bool res;
+ parserService->
+ IsBlock(parserService->HTMLCaseSensitiveAtomTagToId(aName), res);
+ return res;
+ }
+ }
+
+ return false;
+}
+
+
+void
+nsXHTMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode)
+{
+ if (!ShouldMaintainPreLevel() ||
+ !aNode->IsHTMLElement()) {
+ return;
+ }
+
+ if (IsElementPreformatted(aNode) ||
+ aNode->IsAnyOfHTMLElements(nsGkAtoms::script,
+ nsGkAtoms::style,
+ nsGkAtoms::noscript,
+ nsGkAtoms::noframes)) {
+ PreLevel()++;
+ }
+}
+
+void
+nsXHTMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode)
+{
+ if (!ShouldMaintainPreLevel() ||
+ !aNode->IsHTMLElement()) {
+ return;
+ }
+
+ if (IsElementPreformatted(aNode) ||
+ aNode->IsAnyOfHTMLElements(nsGkAtoms::script,
+ nsGkAtoms::style,
+ nsGkAtoms::noscript,
+ nsGkAtoms::noframes)) {
+ --PreLevel();
+ }
+}
+
+bool
+nsXHTMLContentSerializer::IsElementPreformatted(nsIContent* aNode)
+{
+ MOZ_ASSERT(ShouldMaintainPreLevel(), "We should not be calling this needlessly");
+
+ if (!aNode->IsElement()) {
+ return false;
+ }
+ RefPtr<nsStyleContext> styleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aNode->AsElement(),
+ nullptr, nullptr);
+ if (styleContext) {
+ const nsStyleText* textStyle = styleContext->StyleText();
+ return textStyle->WhiteSpaceOrNewlineIsSignificant();
+ }
+ return false;
+}
+
+bool
+nsXHTMLContentSerializer::SerializeLIValueAttribute(nsIContent* aElement,
+ nsAString& aStr)
+{
+ // We are copying and we are at the "first" LI node of OL in selected range.
+ // It may not be the first LI child of OL but it's first in the selected range.
+ // Note that we get into this condition only once per a OL.
+ bool found = false;
+ nsCOMPtr<nsIDOMNode> currNode = do_QueryInterface(aElement);
+ nsAutoString valueStr;
+
+ olState state (0, false);
+
+ if (!mOLStateStack.IsEmpty()) {
+ state = mOLStateStack[mOLStateStack.Length()-1];
+ // isFirstListItem should be true only before the serialization of the
+ // first item in the list.
+ state.isFirstListItem = false;
+ mOLStateStack[mOLStateStack.Length()-1] = state;
+ }
+
+ int32_t startVal = state.startVal;
+ int32_t offset = 0;
+
+ // Traverse previous siblings until we find one with "value" attribute.
+ // offset keeps track of how many previous siblings we had tocurrNode traverse.
+ while (currNode && !found) {
+ nsCOMPtr<nsIDOMElement> currElement = do_QueryInterface(currNode);
+ // currElement may be null if it were a text node.
+ if (currElement) {
+ nsAutoString tagName;
+ currElement->GetTagName(tagName);
+ if (tagName.LowerCaseEqualsLiteral("li")) {
+ currElement->GetAttribute(NS_LITERAL_STRING("value"), valueStr);
+ if (valueStr.IsEmpty())
+ offset++;
+ else {
+ found = true;
+ nsresult rv = NS_OK;
+ startVal = valueStr.ToInteger(&rv);
+ }
+ }
+ }
+ nsCOMPtr<nsIDOMNode> tmp;
+ currNode->GetPreviousSibling(getter_AddRefs(tmp));
+ currNode.swap(tmp);
+ }
+ // If LI was not having "value", Set the "value" attribute for it.
+ // Note that We are at the first LI in the selected range of OL.
+ if (offset == 0 && found) {
+ // offset = 0 => LI itself has the value attribute and we did not need to traverse back.
+ // Just serialize value attribute like other tags.
+ NS_ENSURE_TRUE(SerializeAttr(EmptyString(), NS_LITERAL_STRING("value"),
+ valueStr, aStr, false), false);
+ }
+ else if (offset == 1 && !found) {
+ /*(offset = 1 && !found) means either LI is the first child node of OL
+ and LI is not having "value" attribute.
+ In that case we would not like to set "value" attribute to reduce the changes.
+ */
+ //do nothing...
+ }
+ else if (offset > 0) {
+ // Set value attribute.
+ nsAutoString valueStr;
+
+ //As serializer needs to use this valueAttr we are creating here,
+ valueStr.AppendInt(startVal + offset);
+ NS_ENSURE_TRUE(SerializeAttr(EmptyString(), NS_LITERAL_STRING("value"),
+ valueStr, aStr, false), false);
+ }
+
+ return true;
+}
+
+bool
+nsXHTMLContentSerializer::IsFirstChildOfOL(nsIContent* aElement)
+{
+ nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
+ nsAutoString parentName;
+
+ nsCOMPtr<nsIDOMNode> parentNode;
+ node->GetParentNode(getter_AddRefs(parentNode));
+ if (parentNode)
+ parentNode->GetNodeName(parentName);
+ else
+ return false;
+
+ if (parentName.LowerCaseEqualsLiteral("ol")) {
+
+ if (!mOLStateStack.IsEmpty()) {
+ olState state = mOLStateStack[mOLStateStack.Length()-1];
+ if (state.isFirstListItem)
+ return true;
+ }
+
+ return false;
+ }
+ else
+ return false;
+}
+
+bool
+nsXHTMLContentSerializer::HasNoChildren(nsIContent * aContent) {
+
+ for (nsIContent* child = aContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ if (!child->IsNodeOfType(nsINode::eTEXT))
+ return false;
+
+ if (child->TextLength())
+ return false;
+ }
+
+ return true;
+}
diff --git a/dom/base/nsXHTMLContentSerializer.h b/dom/base/nsXHTMLContentSerializer.h
new file mode 100644
index 000000000..7473ba074
--- /dev/null
+++ b/dom/base/nsXHTMLContentSerializer.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsIContentSerializer implementation that can be used with an
+ * nsIDocumentEncoder to convert an XHTML (not HTML!) DOM to an XHTML
+ * string that could be parsed into more or less the original DOM.
+ */
+
+#ifndef nsXHTMLContentSerializer_h__
+#define nsXHTMLContentSerializer_h__
+
+#include "mozilla/Attributes.h"
+#include "nsXMLContentSerializer.h"
+#include "nsIEntityConverter.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIContent;
+class nsIAtom;
+
+class nsXHTMLContentSerializer : public nsXMLContentSerializer {
+ public:
+ nsXHTMLContentSerializer();
+ virtual ~nsXHTMLContentSerializer();
+
+ NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
+ const char* aCharSet, bool aIsCopying,
+ bool aRewriteEncodingDeclaration) override;
+
+ NS_IMETHOD AppendText(nsIContent* aText,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr) override;
+
+ NS_IMETHOD AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr) override;
+
+ protected:
+
+
+ virtual bool CheckElementStart(nsIContent * aContent,
+ bool & aForceFormat,
+ nsAString& aStr,
+ nsresult& aResult) override;
+
+ MOZ_MUST_USE
+ virtual bool AfterElementStart(nsIContent* aContent,
+ nsIContent* aOriginalElement,
+ nsAString& aStr) override;
+
+ virtual bool CheckElementEnd(mozilla::dom::Element* aContent,
+ bool& aForceFormat,
+ nsAString& aStr) override;
+
+ virtual void AfterElementEnd(nsIContent * aContent,
+ nsAString& aStr) override;
+
+ virtual bool LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName) override;
+ virtual bool LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName) override;
+ virtual bool LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName) override;
+ virtual bool LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName) override;
+
+ bool HasLongLines(const nsString& text, int32_t& aLastNewlineOffset);
+
+ // functions to check if we enter in or leave from a preformated content
+ virtual void MaybeEnterInPreContent(nsIContent* aNode) override;
+ virtual void MaybeLeaveFromPreContent(nsIContent* aNode) override;
+
+ MOZ_MUST_USE
+ virtual bool SerializeAttributes(nsIContent* aContent,
+ nsIContent *aOriginalElement,
+ nsAString& aTagPrefix,
+ const nsAString& aTagNamespaceURI,
+ nsIAtom* aTagName,
+ nsAString& aStr,
+ uint32_t aSkipAttr,
+ bool aAddNSAttr) override;
+
+ bool IsFirstChildOfOL(nsIContent* aElement);
+
+ MOZ_MUST_USE
+ bool SerializeLIValueAttribute(nsIContent* aElement,
+ nsAString& aStr);
+ bool IsShorthandAttr(const nsIAtom* aAttrName,
+ const nsIAtom* aElementName);
+
+ MOZ_MUST_USE
+ virtual bool AppendAndTranslateEntities(const nsAString& aStr,
+ nsAString& aOutputStr) override;
+
+ nsresult EscapeURI(nsIContent* aContent,
+ const nsAString& aURI,
+ nsAString& aEscapedURI);
+
+private:
+ bool IsElementPreformatted(nsIContent* aNode);
+
+protected:
+ nsCOMPtr<nsIEntityConverter> mEntityConverter;
+
+ /*
+ * isHTMLParser should be set to true by the HTML parser which inherits from
+ * this class. It avoids to redefine methods just for few changes.
+ */
+ bool mIsHTMLSerializer;
+
+ bool mDoHeader;
+ bool mIsCopying; // Set to true only while copying
+
+ /*
+ * mDisableEntityEncoding is higher than 0 while the serializer is serializing
+ * the content of a element whose content is considerd CDATA by the
+ * serializer (such elements are 'script', 'style', 'noscript' and
+ * possibly others in XHTML) This doesn't have anything to do with if the
+ * element is defined as CDATA in the DTD, it simply means we'll
+ * output the content of the element without doing any entity encoding
+ * what so ever.
+ */
+ int32_t mDisableEntityEncoding;
+
+ // This is to ensure that we only do meta tag fixups when dealing with
+ // whole documents.
+ bool mRewriteEncodingDeclaration;
+
+ // To keep track of First LI child of OL in selected range
+ bool mIsFirstChildOfOL;
+
+ // To keep track of startvalue of OL and first list item for nested lists
+ struct olState {
+ olState(int32_t aStart, bool aIsFirst)
+ : startVal(aStart),
+ isFirstListItem(aIsFirst)
+ {
+ }
+
+ olState(const olState & aOlState)
+ {
+ startVal = aOlState.startVal;
+ isFirstListItem = aOlState.isFirstListItem;
+ }
+
+ // the value of the start attribute in the OL
+ int32_t startVal;
+
+ // is true only before the serialization of the first li of an ol
+ // should be false for other li in the list
+ bool isFirstListItem;
+ };
+
+ // Stack to store one olState struct per <OL>.
+ AutoTArray<olState, 8> mOLStateStack;
+
+ bool HasNoChildren(nsIContent* aContent);
+};
+
+nsresult
+NS_NewXHTMLContentSerializer(nsIContentSerializer** aSerializer);
+
+#endif
diff --git a/dom/base/nsXMLContentSerializer.cpp b/dom/base/nsXMLContentSerializer.cpp
new file mode 100644
index 000000000..54fadaa94
--- /dev/null
+++ b/dom/base/nsXMLContentSerializer.cpp
@@ -0,0 +1,1828 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsIContentSerializer implementation that can be used with an
+ * nsIDocumentEncoder to convert an XML DOM to an XML string that
+ * could be parsed into more or less the original DOM.
+ */
+
+#include "nsXMLContentSerializer.h"
+
+#include "nsGkAtoms.h"
+#include "nsIDOMProcessingInstruction.h"
+#include "nsIDOMComment.h"
+#include "nsIDOMDocumentType.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIParserService.h"
+#include "nsNameSpaceManager.h"
+#include "nsTextFragment.h"
+#include "nsString.h"
+#include "mozilla/Sprintf.h"
+#include "nsUnicharUtils.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsAttrName.h"
+#include "nsILineBreaker.h"
+#include "mozilla/dom/Element.h"
+#include "nsParserConstants.h"
+
+using namespace mozilla::dom;
+
+#define kXMLNS "xmlns"
+
+// to be readable, we assume that an indented line contains
+// at least this number of characters (arbitrary value here).
+// This is a limit for the indentation.
+#define MIN_INDENTED_LINE_LENGTH 15
+
+// the string used to indent.
+#define INDENT_STRING " "
+#define INDENT_STRING_LENGTH 2
+
+nsresult
+NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer)
+{
+ RefPtr<nsXMLContentSerializer> it = new nsXMLContentSerializer();
+ it.forget(aSerializer);
+ return NS_OK;
+}
+
+nsXMLContentSerializer::nsXMLContentSerializer()
+ : mPrefixIndex(0),
+ mColPos(0),
+ mIndentOverflow(0),
+ mIsIndentationAddedOnCurrentLine(false),
+ mInAttribute(false),
+ mAddNewlineForRootNode(false),
+ mAddSpace(false),
+ mMayIgnoreLineBreakSequence(false),
+ mBodyOnly(false),
+ mInBody(0)
+{
+}
+
+nsXMLContentSerializer::~nsXMLContentSerializer()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsXMLContentSerializer, nsIContentSerializer)
+
+NS_IMETHODIMP
+nsXMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
+ const char* aCharSet, bool aIsCopying,
+ bool aRewriteEncodingDeclaration)
+{
+ mPrefixIndex = 0;
+ mColPos = 0;
+ mIndentOverflow = 0;
+ mIsIndentationAddedOnCurrentLine = false;
+ mInAttribute = false;
+ mAddNewlineForRootNode = false;
+ mAddSpace = false;
+ mMayIgnoreLineBreakSequence = false;
+ mBodyOnly = false;
+ mInBody = 0;
+
+ mCharset = aCharSet;
+ mFlags = aFlags;
+
+ // Set the line break character:
+ if ((mFlags & nsIDocumentEncoder::OutputCRLineBreak)
+ && (mFlags & nsIDocumentEncoder::OutputLFLineBreak)) { // Windows
+ mLineBreak.AssignLiteral("\r\n");
+ }
+ else if (mFlags & nsIDocumentEncoder::OutputCRLineBreak) { // Mac
+ mLineBreak.Assign('\r');
+ }
+ else if (mFlags & nsIDocumentEncoder::OutputLFLineBreak) { // Unix/DOM
+ mLineBreak.Assign('\n');
+ }
+ else {
+ mLineBreak.AssignLiteral(NS_LINEBREAK); // Platform/default
+ }
+
+ mDoRaw = !!(mFlags & nsIDocumentEncoder::OutputRaw);
+
+ mDoFormat = (mFlags & nsIDocumentEncoder::OutputFormatted && !mDoRaw);
+
+ mDoWrap = (mFlags & nsIDocumentEncoder::OutputWrap && !mDoRaw);
+
+ mAllowLineBreaking = !(mFlags & nsIDocumentEncoder::OutputDisallowLineBreaking);
+
+ if (!aWrapColumn) {
+ mMaxColumn = 72;
+ }
+ else {
+ mMaxColumn = aWrapColumn;
+ }
+
+ mPreLevel = 0;
+ mIsIndentationAddedOnCurrentLine = false;
+ return NS_OK;
+}
+
+nsresult
+nsXMLContentSerializer::AppendTextData(nsIContent* aNode,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr,
+ bool aTranslateEntities)
+{
+ nsIContent* content = aNode;
+ const nsTextFragment* frag;
+ if (!content || !(frag = content->GetText())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t fragLength = frag->GetLength();
+ int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
+ int32_t length = endoffset - aStartOffset;
+
+ NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
+ NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!");
+
+ if (length <= 0) {
+ // XXX Zero is a legal value, maybe non-zero values should be an
+ // error.
+ return NS_OK;
+ }
+
+ if (frag->Is2b()) {
+ const char16_t *strStart = frag->Get2b() + aStartOffset;
+ if (aTranslateEntities) {
+ NS_ENSURE_TRUE(AppendAndTranslateEntities(Substring(strStart, strStart + length), aStr),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(aStr.Append(Substring(strStart, strStart + length), mozilla::fallible),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ else {
+ if (aTranslateEntities) {
+ NS_ENSURE_TRUE(AppendAndTranslateEntities(NS_ConvertASCIItoUTF16(frag->Get1b()+aStartOffset, length), aStr),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(aStr.Append(NS_ConvertASCIItoUTF16(frag->Get1b()+aStartOffset, length), mozilla::fallible),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendText(nsIContent* aText,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aText);
+
+ nsAutoString data;
+ nsresult rv;
+
+ rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ if (mDoRaw || PreLevel() > 0) {
+ NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoFormat) {
+ NS_ENSURE_TRUE(AppendToStringFormatedWrapped(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoWrap) {
+ NS_ENSURE_TRUE(AppendToStringWrapped(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendCDATASection(nsIContent* aCDATASection,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aCDATASection);
+ nsresult rv;
+
+ NS_NAMED_LITERAL_STRING(cdata , "<![CDATA[");
+
+ if (mDoRaw || PreLevel() > 0) {
+ NS_ENSURE_TRUE(AppendToString(cdata, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoFormat) {
+ NS_ENSURE_TRUE(AppendToStringFormatedWrapped(cdata, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoWrap) {
+ NS_ENSURE_TRUE(AppendToStringWrapped(cdata, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(AppendToString(cdata, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ nsAutoString data;
+ rv = AppendTextData(aCDATASection, aStartOffset, aEndOffset, data, false);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("]]>"), aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendProcessingInstruction(nsIContent* aPI,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr)
+{
+ nsCOMPtr<nsIDOMProcessingInstruction> pi = do_QueryInterface(aPI);
+ NS_ENSURE_ARG(pi);
+ nsresult rv;
+ nsAutoString target, data, start;
+
+ NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ rv = pi->GetTarget(target);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ rv = pi->GetData(data);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ NS_ENSURE_TRUE(start.AppendLiteral("<?", mozilla::fallible), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(start.Append(target, mozilla::fallible), NS_ERROR_OUT_OF_MEMORY);
+
+ if (mDoRaw || PreLevel() > 0) {
+ NS_ENSURE_TRUE(AppendToString(start, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoFormat) {
+ if (mAddSpace) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ NS_ENSURE_TRUE(AppendToStringFormatedWrapped(start, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoWrap) {
+ NS_ENSURE_TRUE(AppendToStringWrapped(start, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(AppendToString(start, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ if (!data.IsEmpty()) {
+ NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("?>"), aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ MaybeFlagNewlineForRootNode(aPI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendComment(nsIContent* aComment,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr)
+{
+ nsCOMPtr<nsIDOMComment> comment = do_QueryInterface(aComment);
+ NS_ENSURE_ARG(comment);
+ nsresult rv;
+ nsAutoString data;
+
+ rv = comment->GetData(data);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ int32_t dataLength = data.Length();
+ if (aStartOffset || (aEndOffset != -1 && aEndOffset < dataLength)) {
+ int32_t length =
+ (aEndOffset == -1) ? dataLength : std::min(aEndOffset, dataLength);
+ length -= aStartOffset;
+
+ nsAutoString frag;
+ if (length > 0) {
+ data.Mid(frag, aStartOffset, length);
+ }
+ data.Assign(frag);
+ }
+
+ NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ NS_NAMED_LITERAL_STRING(startComment, "<!--");
+
+ if (mDoRaw || PreLevel() > 0) {
+ NS_ENSURE_TRUE(AppendToString(startComment, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoFormat) {
+ if (mAddSpace) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ NS_ENSURE_TRUE(AppendToStringFormatedWrapped(startComment, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mDoWrap) {
+ NS_ENSURE_TRUE(AppendToStringWrapped(startComment, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(AppendToString(startComment, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // Even if mDoformat, we don't format the content because it
+ // could have been preformated by the author
+ NS_ENSURE_TRUE(AppendToStringConvertLF(data, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("-->"), aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ MaybeFlagNewlineForRootNode(aComment);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendDoctype(nsIContent* aDocType,
+ nsAString& aStr)
+{
+ nsCOMPtr<nsIDOMDocumentType> docType = do_QueryInterface(aDocType);
+ NS_ENSURE_ARG(docType);
+ nsresult rv;
+ nsAutoString name, publicId, systemId, internalSubset;
+
+ rv = docType->GetName(name);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+ rv = docType->GetPublicId(publicId);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+ rv = docType->GetSystemId(systemId);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+ rv = docType->GetInternalSubset(internalSubset);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING("<!DOCTYPE "), aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(name, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ char16_t quote;
+ if (!publicId.IsEmpty()) {
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" PUBLIC "), aStr), NS_ERROR_OUT_OF_MEMORY);
+ if (publicId.FindChar(char16_t('"')) == -1) {
+ quote = char16_t('"');
+ }
+ else {
+ quote = char16_t('\'');
+ }
+ NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(publicId, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ if (!systemId.IsEmpty()) {
+ NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
+ if (systemId.FindChar(char16_t('"')) == -1) {
+ quote = char16_t('"');
+ }
+ else {
+ quote = char16_t('\'');
+ }
+ NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(systemId, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ else if (!systemId.IsEmpty()) {
+ if (systemId.FindChar(char16_t('"')) == -1) {
+ quote = char16_t('"');
+ }
+ else {
+ quote = char16_t('\'');
+ }
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" SYSTEM "), aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(systemId, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(quote, aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ if (!internalSubset.IsEmpty()) {
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(" ["), aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(internalSubset, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(char16_t(']'), aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ NS_ENSURE_TRUE(AppendToString(kGreaterThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+ MaybeFlagNewlineForRootNode(aDocType);
+
+ return NS_OK;
+}
+
+nsresult
+nsXMLContentSerializer::PushNameSpaceDecl(const nsAString& aPrefix,
+ const nsAString& aURI,
+ nsIContent* aOwner)
+{
+ NameSpaceDecl* decl = mNameSpaceStack.AppendElement();
+ if (!decl) return NS_ERROR_OUT_OF_MEMORY;
+
+ decl->mPrefix.Assign(aPrefix);
+ decl->mURI.Assign(aURI);
+ // Don't addref - this weak reference will be removed when
+ // we pop the stack
+ decl->mOwner = aOwner;
+ return NS_OK;
+}
+
+void
+nsXMLContentSerializer::PopNameSpaceDeclsFor(nsIContent* aOwner)
+{
+ int32_t index, count;
+
+ count = mNameSpaceStack.Length();
+ for (index = count - 1; index >= 0; index--) {
+ if (mNameSpaceStack[index].mOwner != aOwner) {
+ break;
+ }
+ mNameSpaceStack.RemoveElementAt(index);
+ }
+}
+
+bool
+nsXMLContentSerializer::ConfirmPrefix(nsAString& aPrefix,
+ const nsAString& aURI,
+ nsIContent* aElement,
+ bool aIsAttribute)
+{
+ if (aPrefix.EqualsLiteral(kXMLNS)) {
+ return false;
+ }
+
+ if (aURI.EqualsLiteral("http://www.w3.org/XML/1998/namespace")) {
+ // The prefix must be xml for this namespace. We don't need to declare it,
+ // so always just set the prefix to xml.
+ aPrefix.AssignLiteral("xml");
+
+ return false;
+ }
+
+ bool mustHavePrefix;
+ if (aIsAttribute) {
+ if (aURI.IsEmpty()) {
+ // Attribute in the null namespace. This just shouldn't have a prefix.
+ // And there's no need to push any namespace decls
+ aPrefix.Truncate();
+ return false;
+ }
+
+ // Attribute not in the null namespace -- must have a prefix
+ mustHavePrefix = true;
+ } else {
+ // Not an attribute, so doesn't _have_ to have a prefix
+ mustHavePrefix = false;
+ }
+
+ // Keep track of the closest prefix that's bound to aURI and whether we've
+ // found such a thing. closestURIMatch holds the prefix, and uriMatch
+ // indicates whether we actually have one.
+ nsAutoString closestURIMatch;
+ bool uriMatch = false;
+
+ // Also keep track of whether we've seen aPrefix already. If we have, that
+ // means that it's already bound to a URI different from aURI, so even if we
+ // later (so in a more outer scope) see it bound to aURI we can't reuse it.
+ bool haveSeenOurPrefix = false;
+
+ int32_t count = mNameSpaceStack.Length();
+ int32_t index = count - 1;
+ while (index >= 0) {
+ NameSpaceDecl& decl = mNameSpaceStack.ElementAt(index);
+ // Check if we've found a prefix match
+ if (aPrefix.Equals(decl.mPrefix)) {
+
+ // If the URIs match and aPrefix is not bound to any other URI, we can
+ // use aPrefix
+ if (!haveSeenOurPrefix && aURI.Equals(decl.mURI)) {
+ // Just use our uriMatch stuff. That will deal with an empty aPrefix
+ // the right way. We can break out of the loop now, though.
+ uriMatch = true;
+ closestURIMatch = aPrefix;
+ break;
+ }
+
+ haveSeenOurPrefix = true;
+
+ // If they don't, and either:
+ // 1) We have a prefix (so we'd be redeclaring this prefix to point to a
+ // different namespace) or
+ // 2) We're looking at an existing default namespace decl on aElement (so
+ // we can't create a new default namespace decl for this URI)
+ // then generate a new prefix. Note that we do NOT generate new prefixes
+ // if we happen to have aPrefix == decl->mPrefix == "" and mismatching
+ // URIs when |decl| doesn't have aElement as its owner. In that case we
+ // can simply push the new namespace URI as the default namespace for
+ // aElement.
+ if (!aPrefix.IsEmpty() || decl.mOwner == aElement) {
+ NS_ASSERTION(!aURI.IsEmpty(),
+ "Not allowed to add a xmlns attribute with an empty "
+ "namespace name unless it declares the default "
+ "namespace.");
+
+ GenerateNewPrefix(aPrefix);
+ // Now we need to validate our new prefix/uri combination; check it
+ // against the full namespace stack again. Note that just restarting
+ // the while loop is ok, since we haven't changed aURI, so the
+ // closestURIMatch and uriMatch state is not affected.
+ index = count - 1;
+ haveSeenOurPrefix = false;
+ continue;
+ }
+ }
+
+ // If we've found a URI match, then record the first one
+ if (!uriMatch && aURI.Equals(decl.mURI)) {
+ // Need to check that decl->mPrefix is not declared anywhere closer to
+ // us. If it is, we can't use it.
+ bool prefixOK = true;
+ int32_t index2;
+ for (index2 = count-1; index2 > index && prefixOK; --index2) {
+ prefixOK = (mNameSpaceStack[index2].mPrefix != decl.mPrefix);
+ }
+
+ if (prefixOK) {
+ uriMatch = true;
+ closestURIMatch.Assign(decl.mPrefix);
+ }
+ }
+
+ --index;
+ }
+
+ // At this point the following invariants hold:
+ // 1) The prefix in closestURIMatch is mapped to aURI in our scope if
+ // uriMatch is set.
+ // 2) There is nothing on the namespace stack that has aPrefix as the prefix
+ // and a _different_ URI, except for the case aPrefix.IsEmpty (and
+ // possible default namespaces on ancestors)
+
+ // So if uriMatch is set it's OK to use the closestURIMatch prefix. The one
+ // exception is when closestURIMatch is actually empty (default namespace
+ // decl) and we must have a prefix.
+ if (uriMatch && (!mustHavePrefix || !closestURIMatch.IsEmpty())) {
+ aPrefix.Assign(closestURIMatch);
+ return false;
+ }
+
+ if (aPrefix.IsEmpty()) {
+ // At this point, aPrefix is empty (which means we never had a prefix to
+ // start with). If we must have a prefix, just generate a new prefix and
+ // then send it back through the namespace stack checks to make sure it's
+ // OK.
+ if (mustHavePrefix) {
+ GenerateNewPrefix(aPrefix);
+ return ConfirmPrefix(aPrefix, aURI, aElement, aIsAttribute);
+ }
+
+ // One final special case. If aPrefix is empty and we never saw an empty
+ // prefix (default namespace decl) on the namespace stack and we're in the
+ // null namespace there is no reason to output an |xmlns=""| here. It just
+ // makes the output less readable.
+ if (!haveSeenOurPrefix && aURI.IsEmpty()) {
+ return false;
+ }
+ }
+
+ // Now just set aURI as the new default namespace URI. Indicate that we need
+ // to create a namespace decl for the final prefix
+ return true;
+}
+
+void
+nsXMLContentSerializer::GenerateNewPrefix(nsAString& aPrefix)
+{
+ aPrefix.Assign('a');
+ char buf[128];
+ SprintfLiteral(buf, "%d", mPrefixIndex++);
+ AppendASCIItoUTF16(buf, aPrefix);
+}
+
+bool
+nsXMLContentSerializer::SerializeAttr(const nsAString& aPrefix,
+ const nsAString& aName,
+ const nsAString& aValue,
+ nsAString& aStr,
+ bool aDoEscapeEntities)
+{
+ nsAutoString attrString_;
+ // For innerHTML we can do faster appending without
+ // temporary strings.
+ bool rawAppend = mDoRaw && aDoEscapeEntities;
+ nsAString& attrString = (rawAppend) ? aStr : attrString_;
+
+ NS_ENSURE_TRUE(attrString.Append(char16_t(' '), mozilla::fallible), false);
+ if (!aPrefix.IsEmpty()) {
+ NS_ENSURE_TRUE(attrString.Append(aPrefix, mozilla::fallible), false);
+ NS_ENSURE_TRUE(attrString.Append(char16_t(':'), mozilla::fallible), false);
+ }
+ NS_ENSURE_TRUE(attrString.Append(aName, mozilla::fallible), false);
+
+ if (aDoEscapeEntities) {
+ // if problem characters are turned into character entity references
+ // then there will be no problem with the value delimiter characters
+ NS_ENSURE_TRUE(attrString.AppendLiteral("=\"", mozilla::fallible), false);
+
+ mInAttribute = true;
+ bool result = AppendAndTranslateEntities(aValue, attrString);
+ mInAttribute = false;
+ NS_ENSURE_TRUE(result, false);
+
+ NS_ENSURE_TRUE(attrString.Append(char16_t('"'), mozilla::fallible), false);
+ if (rawAppend) {
+ return true;
+ }
+ }
+ else {
+ // Depending on whether the attribute value contains quotes or apostrophes we
+ // need to select the delimiter character and escape characters using
+ // character entity references, ignoring the value of aDoEscapeEntities.
+ // See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2.2 for
+ // the standard on character entity references in values. We also have to
+ // make sure to escape any '&' characters.
+
+ bool bIncludesSingle = false;
+ bool bIncludesDouble = false;
+ nsAString::const_iterator iCurr, iEnd;
+ aValue.BeginReading(iCurr);
+ aValue.EndReading(iEnd);
+ for ( ; iCurr != iEnd; ++iCurr) {
+ if (*iCurr == char16_t('\'')) {
+ bIncludesSingle = true;
+ if (bIncludesDouble) {
+ break;
+ }
+ } else if (*iCurr == char16_t('"')) {
+ bIncludesDouble = true;
+ if (bIncludesSingle) {
+ break;
+ }
+ }
+ }
+
+ // Delimiter and escaping is according to the following table
+ // bIncludesDouble bIncludesSingle Delimiter Escape Double Quote
+ // FALSE FALSE " FALSE
+ // FALSE TRUE " FALSE
+ // TRUE FALSE ' FALSE
+ // TRUE TRUE " TRUE
+ char16_t cDelimiter =
+ (bIncludesDouble && !bIncludesSingle) ? char16_t('\'') : char16_t('"');
+ NS_ENSURE_TRUE(attrString.Append(char16_t('='), mozilla::fallible), false);
+ NS_ENSURE_TRUE(attrString.Append(cDelimiter, mozilla::fallible), false);
+ nsAutoString sValue(aValue);
+ NS_ENSURE_TRUE(sValue.ReplaceSubstring(NS_LITERAL_STRING("&"),
+ NS_LITERAL_STRING("&amp;"), mozilla::fallible), false);
+ if (bIncludesDouble && bIncludesSingle) {
+ NS_ENSURE_TRUE(sValue.ReplaceSubstring(NS_LITERAL_STRING("\""),
+ NS_LITERAL_STRING("&quot;"), mozilla::fallible), false);
+ }
+ NS_ENSURE_TRUE(attrString.Append(sValue, mozilla::fallible), false);
+ NS_ENSURE_TRUE(attrString.Append(cDelimiter, mozilla::fallible), false);
+ }
+ if (mDoRaw || PreLevel() > 0) {
+ NS_ENSURE_TRUE(AppendToStringConvertLF(attrString, aStr), false);
+ }
+ else if (mDoFormat) {
+ NS_ENSURE_TRUE(AppendToStringFormatedWrapped(attrString, aStr), false);
+ }
+ else if (mDoWrap) {
+ NS_ENSURE_TRUE(AppendToStringWrapped(attrString, aStr), false);
+ }
+ else {
+ NS_ENSURE_TRUE(AppendToStringConvertLF(attrString, aStr), false);
+ }
+
+ return true;
+}
+
+uint32_t
+nsXMLContentSerializer::ScanNamespaceDeclarations(nsIContent* aContent,
+ nsIContent *aOriginalElement,
+ const nsAString& aTagNamespaceURI)
+{
+ uint32_t index, count;
+ nsAutoString uriStr, valueStr;
+
+ count = aContent->GetAttrCount();
+
+ // First scan for namespace declarations, pushing each on the stack
+ uint32_t skipAttr = count;
+ for (index = 0; index < count; index++) {
+
+ const BorrowedAttrInfo info = aContent->GetAttrInfoAt(index);
+ const nsAttrName* name = info.mName;
+
+ int32_t namespaceID = name->NamespaceID();
+ nsIAtom *attrName = name->LocalName();
+
+ if (namespaceID == kNameSpaceID_XMLNS ||
+ // Also push on the stack attrs named "xmlns" in the null
+ // namespace... because once we serialize those out they'll look like
+ // namespace decls. :(
+ // XXXbz what if we have both "xmlns" in the null namespace and "xmlns"
+ // in the xmlns namespace?
+ (namespaceID == kNameSpaceID_None &&
+ attrName == nsGkAtoms::xmlns)) {
+ info.mValue->ToString(uriStr);
+
+ if (!name->GetPrefix()) {
+ if (aTagNamespaceURI.IsEmpty() && !uriStr.IsEmpty()) {
+ // If the element is in no namespace we need to add a xmlns
+ // attribute to declare that. That xmlns attribute must not have a
+ // prefix (see http://www.w3.org/TR/REC-xml-names/#dt-prefix), ie it
+ // must declare the default namespace. We just found an xmlns
+ // attribute that declares the default namespace to something
+ // non-empty. We're going to ignore this attribute, for children we
+ // will detect that we need to add it again and attributes aren't
+ // affected by the default namespace.
+ skipAttr = index;
+ }
+ else {
+ // Default NS attribute does not have prefix (and the name is "xmlns")
+ PushNameSpaceDecl(EmptyString(), uriStr, aOriginalElement);
+ }
+ }
+ else {
+ PushNameSpaceDecl(nsDependentAtomString(attrName), uriStr,
+ aOriginalElement);
+ }
+ }
+ }
+ return skipAttr;
+}
+
+
+bool
+nsXMLContentSerializer::IsJavaScript(nsIContent * aContent, nsIAtom* aAttrNameAtom,
+ int32_t aAttrNamespaceID, const nsAString& aValueString)
+{
+ bool isHtml = aContent->IsHTMLElement();
+ bool isXul = aContent->IsXULElement();
+ bool isSvg = aContent->IsSVGElement();
+
+ if (aAttrNamespaceID == kNameSpaceID_None &&
+ (isHtml || isXul || isSvg) &&
+ (aAttrNameAtom == nsGkAtoms::href ||
+ aAttrNameAtom == nsGkAtoms::src)) {
+
+ static const char kJavaScript[] = "javascript";
+ int32_t pos = aValueString.FindChar(':');
+ if (pos < (int32_t)(sizeof kJavaScript - 1))
+ return false;
+ nsAutoString scheme(Substring(aValueString, 0, pos));
+ scheme.StripWhitespace();
+ if ((scheme.Length() == (sizeof kJavaScript - 1)) &&
+ scheme.EqualsIgnoreCase(kJavaScript))
+ return true;
+ else
+ return false;
+ }
+
+ return aContent->IsEventAttributeName(aAttrNameAtom);
+}
+
+
+bool
+nsXMLContentSerializer::SerializeAttributes(nsIContent* aContent,
+ nsIContent *aOriginalElement,
+ nsAString& aTagPrefix,
+ const nsAString& aTagNamespaceURI,
+ nsIAtom* aTagName,
+ nsAString& aStr,
+ uint32_t aSkipAttr,
+ bool aAddNSAttr)
+{
+
+ nsAutoString prefixStr, uriStr, valueStr;
+ nsAutoString xmlnsStr;
+ xmlnsStr.AssignLiteral(kXMLNS);
+ uint32_t index, count;
+
+ // If we had to add a new namespace declaration, serialize
+ // and push it on the namespace stack
+ if (aAddNSAttr) {
+ if (aTagPrefix.IsEmpty()) {
+ // Serialize default namespace decl
+ NS_ENSURE_TRUE(SerializeAttr(EmptyString(), xmlnsStr, aTagNamespaceURI, aStr, true), false);
+ }
+ else {
+ // Serialize namespace decl
+ NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true), false);
+ }
+ PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement);
+ }
+
+ count = aContent->GetAttrCount();
+
+ // Now serialize each of the attributes
+ // XXX Unfortunately we need a namespace manager to get
+ // attribute URIs.
+ for (index = 0; index < count; index++) {
+ if (aSkipAttr == index) {
+ continue;
+ }
+
+ const nsAttrName* name = aContent->GetAttrNameAt(index);
+ int32_t namespaceID = name->NamespaceID();
+ nsIAtom* attrName = name->LocalName();
+ nsIAtom* attrPrefix = name->GetPrefix();
+
+ // Filter out any attribute starting with [-|_]moz
+ nsDependentAtomString attrNameStr(attrName);
+ if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
+ StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
+ continue;
+ }
+
+ if (attrPrefix) {
+ attrPrefix->ToString(prefixStr);
+ }
+ else {
+ prefixStr.Truncate();
+ }
+
+ bool addNSAttr = false;
+ if (kNameSpaceID_XMLNS != namespaceID) {
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr);
+ addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true);
+ }
+
+ aContent->GetAttr(namespaceID, attrName, valueStr);
+
+ nsDependentAtomString nameStr(attrName);
+ bool isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr);
+
+ NS_ENSURE_TRUE(SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS), false);
+
+ if (addNSAttr) {
+ NS_ASSERTION(!prefixStr.IsEmpty(),
+ "Namespaced attributes must have a prefix");
+ NS_ENSURE_TRUE(SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true), false);
+ PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement);
+ }
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendElementStart(Element* aElement,
+ Element* aOriginalElement,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aElement);
+
+ nsIContent* content = aElement;
+
+ bool forceFormat = false;
+ nsresult rv = NS_OK;
+ if (!CheckElementStart(content, forceFormat, aStr, rv)) {
+ // When we go to AppendElementEnd for this element, we're going to
+ // MaybeLeaveFromPreContent(). So make sure to MaybeEnterInPreContent()
+ // now, so our PreLevel() doesn't get confused.
+ MaybeEnterInPreContent(content);
+ return rv;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
+ aElement->NodeInfo()->GetPrefix(tagPrefix);
+ aElement->NodeInfo()->GetName(tagLocalName);
+ aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
+
+ uint32_t skipAttr = ScanNamespaceDeclarations(content,
+ aOriginalElement, tagNamespaceURI);
+
+ nsIAtom *name = content->NodeInfo()->NameAtom();
+ bool lineBreakBeforeOpen = LineBreakBeforeOpen(content->GetNameSpaceID(), name);
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
+ if (mColPos && lineBreakBeforeOpen) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ if (!mColPos) {
+ NS_ENSURE_TRUE(AppendIndentation(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mAddSpace) {
+ NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
+ mAddSpace = false;
+ }
+ }
+ else if (mAddSpace) {
+ NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
+ mAddSpace = false;
+ }
+ else {
+ NS_ENSURE_TRUE(MaybeAddNewlineForRootNode(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // Always reset to avoid false newlines in case MaybeAddNewlineForRootNode wasn't
+ // called
+ mAddNewlineForRootNode = false;
+
+ bool addNSAttr;
+ addNSAttr = ConfirmPrefix(tagPrefix, tagNamespaceURI, aOriginalElement,
+ false);
+
+ // Serialize the qualified name of the element
+ NS_ENSURE_TRUE(AppendToString(kLessThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+ if (!tagPrefix.IsEmpty()) {
+ NS_ENSURE_TRUE(AppendToString(tagPrefix, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(":"), aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ NS_ENSURE_TRUE(AppendToString(tagLocalName, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ MaybeEnterInPreContent(content);
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
+ NS_ENSURE_TRUE(IncrIndentation(name), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ NS_ENSURE_TRUE(SerializeAttributes(content, aOriginalElement, tagPrefix, tagNamespaceURI,
+ name, aStr, skipAttr, addNSAttr),
+ NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ENSURE_TRUE(AppendEndOfElementStart(aElement, aOriginalElement, aStr),
+ NS_ERROR_OUT_OF_MEMORY);
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()
+ && LineBreakAfterOpen(content->GetNameSpaceID(), name)) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ NS_ENSURE_TRUE(AfterElementStart(content, aOriginalElement, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+// aElement is the actual element we're outputting. aOriginalElement is the one
+// in the original DOM, which is the one we have to test for kids.
+static bool
+ElementNeedsSeparateEndTag(Element* aElement, Element* aOriginalElement)
+{
+ if (aOriginalElement->GetChildCount()) {
+ // We have kids, so we need a separate end tag. This needs to be checked on
+ // aOriginalElement because that's the one that's actually in the DOM and
+ // might have kids.
+ return true;
+ }
+
+ if (!aElement->IsHTMLElement()) {
+ // Empty non-HTML elements can just skip a separate end tag.
+ return false;
+ }
+
+ // HTML container tags should have a separate end tag even if empty, per spec.
+ // See
+ // https://w3c.github.io/DOM-Parsing/#dfn-concept-xml-serialization-algorithm
+ bool isHTMLContainer = true; // Default in case we get no parser service.
+ nsIParserService* parserService = nsContentUtils::GetParserService();
+ if (parserService) {
+ nsIAtom* localName = aElement->NodeInfo()->NameAtom();
+ parserService->IsContainer(
+ parserService->HTMLCaseSensitiveAtomTagToId(localName),
+ isHTMLContainer);
+ }
+ return isHTMLContainer;
+}
+
+bool
+nsXMLContentSerializer::AppendEndOfElementStart(Element* aElement,
+ Element* aOriginalElement,
+ nsAString& aStr)
+{
+ if (ElementNeedsSeparateEndTag(aElement, aOriginalElement)) {
+ return AppendToString(kGreaterThan, aStr);
+ }
+
+ // We don't need a separate end tag. For HTML elements (which at this point
+ // must be non-containers), append a space before the '/', per spec. See
+ // https://w3c.github.io/DOM-Parsing/#dfn-concept-xml-serialization-algorithm
+ if (aOriginalElement->IsHTMLElement()) {
+ if (!AppendToString(kSpace, aStr)) {
+ return false;
+ }
+ }
+
+ return AppendToString(NS_LITERAL_STRING("/>"), aStr);
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendElementEnd(Element* aElement,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG(aElement);
+
+ nsIContent* content = aElement;
+
+ bool forceFormat = false, outputElementEnd;
+ outputElementEnd = CheckElementEnd(aElement, forceFormat, aStr);
+
+ nsIAtom *name = content->NodeInfo()->NameAtom();
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
+ DecrIndentation(name);
+ }
+
+ if (!outputElementEnd) {
+ // Keep this in sync with the cleanup at the end of this method.
+ PopNameSpaceDeclsFor(aElement);
+ MaybeLeaveFromPreContent(content);
+ MaybeFlagNewlineForRootNode(aElement);
+ AfterElementEnd(content, aStr);
+ return NS_OK;
+ }
+
+ nsAutoString tagPrefix, tagLocalName, tagNamespaceURI;
+
+ aElement->NodeInfo()->GetPrefix(tagPrefix);
+ aElement->NodeInfo()->GetName(tagLocalName);
+ aElement->NodeInfo()->GetNamespaceURI(tagNamespaceURI);
+
+#ifdef DEBUG
+ bool debugNeedToPushNamespace =
+#endif
+ ConfirmPrefix(tagPrefix, tagNamespaceURI, aElement, false);
+ NS_ASSERTION(!debugNeedToPushNamespace, "Can't push namespaces in closing tag!");
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()) {
+
+ bool lineBreakBeforeClose = LineBreakBeforeClose(content->GetNameSpaceID(), name);
+
+ if (mColPos && lineBreakBeforeClose) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ if (!mColPos) {
+ NS_ENSURE_TRUE(AppendIndentation(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else if (mAddSpace) {
+ NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
+ mAddSpace = false;
+ }
+ }
+ else if (mAddSpace) {
+ NS_ENSURE_TRUE(AppendToString(char16_t(' '), aStr), NS_ERROR_OUT_OF_MEMORY);
+ mAddSpace = false;
+ }
+
+ NS_ENSURE_TRUE(AppendToString(kEndTag, aStr), NS_ERROR_OUT_OF_MEMORY);
+ if (!tagPrefix.IsEmpty()) {
+ NS_ENSURE_TRUE(AppendToString(tagPrefix, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(NS_LITERAL_STRING(":"), aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ NS_ENSURE_TRUE(AppendToString(tagLocalName, aStr), NS_ERROR_OUT_OF_MEMORY);
+ NS_ENSURE_TRUE(AppendToString(kGreaterThan, aStr), NS_ERROR_OUT_OF_MEMORY);
+
+ // Keep what follows in sync with the cleanup in the !outputElementEnd case.
+ PopNameSpaceDeclsFor(aElement);
+
+ MaybeLeaveFromPreContent(content);
+
+ if ((mDoFormat || forceFormat) && !mDoRaw && !PreLevel()
+ && LineBreakAfterClose(content->GetNameSpaceID(), name)) {
+ NS_ENSURE_TRUE(AppendNewLineToString(aStr), NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ MaybeFlagNewlineForRootNode(aElement);
+ }
+
+ AfterElementEnd(content, aStr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr)
+{
+ NS_ENSURE_ARG_POINTER(aDocument);
+
+ nsAutoString version, encoding, standalone;
+ aDocument->GetXMLDeclaration(version, encoding, standalone);
+
+ if (version.IsEmpty())
+ return NS_OK; // A declaration must have version, or there is no decl
+
+ NS_NAMED_LITERAL_STRING(endQuote, "\"");
+
+ aStr += NS_LITERAL_STRING("<?xml version=\"") + version + endQuote;
+
+ if (!mCharset.IsEmpty()) {
+ aStr += NS_LITERAL_STRING(" encoding=\"") +
+ NS_ConvertASCIItoUTF16(mCharset) + endQuote;
+ }
+ // Otherwise just don't output an encoding attr. Not that we expect
+ // mCharset to ever be empty.
+#ifdef DEBUG
+ else {
+ NS_WARNING("Empty mCharset? How come?");
+ }
+#endif
+
+ if (!standalone.IsEmpty()) {
+ aStr += NS_LITERAL_STRING(" standalone=\"") + standalone + endQuote;
+ }
+
+ NS_ENSURE_TRUE(aStr.AppendLiteral("?>", mozilla::fallible), NS_ERROR_OUT_OF_MEMORY);
+ mAddNewlineForRootNode = true;
+
+ return NS_OK;
+}
+
+bool
+nsXMLContentSerializer::CheckElementStart(nsIContent * aContent,
+ bool & aForceFormat,
+ nsAString& aStr,
+ nsresult& aResult)
+{
+ aResult = NS_OK;
+ aForceFormat = false;
+ return true;
+}
+
+bool
+nsXMLContentSerializer::CheckElementEnd(Element* aElement,
+ bool& aForceFormat,
+ nsAString& aStr)
+{
+ // We don't output a separate end tag for empty element
+ aForceFormat = false;
+
+ // XXXbz this is a bit messed up, but by now we don't have our fixed-up
+ // version of aElement anymore. Let's hope fixup never changes the localName
+ // or namespace...
+ return ElementNeedsSeparateEndTag(aElement, aElement);
+}
+
+bool
+nsXMLContentSerializer::AppendToString(const char16_t aChar,
+ nsAString& aOutputStr)
+{
+ if (mBodyOnly && !mInBody) {
+ return true;
+ }
+ mColPos += 1;
+ return aOutputStr.Append(aChar, mozilla::fallible);
+}
+
+bool
+nsXMLContentSerializer::AppendToString(const nsAString& aStr,
+ nsAString& aOutputStr)
+{
+ if (mBodyOnly && !mInBody) {
+ return true;
+ }
+ mColPos += aStr.Length();
+ return aOutputStr.Append(aStr, mozilla::fallible);
+}
+
+
+static const uint16_t kGTVal = 62;
+
+#define _ 0
+
+// This table indexes into kEntityStrings[].
+static const uint8_t kEntities[] = {
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, 2, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ 3, _, 4
+};
+
+// This table indexes into kEntityStrings[].
+static const uint8_t kAttrEntities[] = {
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, 1, _, _, _, 2, _,
+ _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _,
+ 3, _, 4
+};
+
+#undef _
+
+static const char* const kEntityStrings[] = {
+ /* 0 */ nullptr,
+ /* 1 */ "&quot;",
+ /* 2 */ "&amp;",
+ /* 3 */ "&lt;",
+ /* 4 */ "&gt;",
+};
+
+bool
+nsXMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr,
+ nsAString& aOutputStr)
+{
+ nsReadingIterator<char16_t> done_reading;
+ aStr.EndReading(done_reading);
+
+ // for each chunk of |aString|...
+ uint32_t advanceLength = 0;
+ nsReadingIterator<char16_t> iter;
+
+ const uint8_t* entityTable = mInAttribute ? kAttrEntities : kEntities;
+
+ for (aStr.BeginReading(iter);
+ iter != done_reading;
+ iter.advance(int32_t(advanceLength))) {
+ uint32_t fragmentLength = done_reading - iter;
+ const char16_t* c = iter.get();
+ const char16_t* fragmentStart = c;
+ const char16_t* fragmentEnd = c + fragmentLength;
+ const char* entityText = nullptr;
+
+ advanceLength = 0;
+ // for each character in this chunk, check if it
+ // needs to be replaced
+ for (; c < fragmentEnd; c++, advanceLength++) {
+ char16_t val = *c;
+ if ((val <= kGTVal) && entityTable[val]) {
+ entityText = kEntityStrings[entityTable[val]];
+ break;
+ }
+ }
+
+ NS_ENSURE_TRUE(aOutputStr.Append(fragmentStart, advanceLength, mozilla::fallible), false);
+ if (entityText) {
+ NS_ENSURE_TRUE(AppendASCIItoUTF16(entityText, aOutputStr, mozilla::fallible), false);
+ advanceLength++;
+ }
+ }
+
+ return true;
+}
+
+bool
+nsXMLContentSerializer::MaybeAddNewlineForRootNode(nsAString& aStr)
+{
+ if (mAddNewlineForRootNode) {
+ return AppendNewLineToString(aStr);
+ }
+
+ return true;
+}
+
+void
+nsXMLContentSerializer::MaybeFlagNewlineForRootNode(nsINode* aNode)
+{
+ nsINode* parent = aNode->GetParentNode();
+ if (parent) {
+ mAddNewlineForRootNode = parent->IsNodeOfType(nsINode::eDOCUMENT);
+ }
+}
+
+void
+nsXMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode)
+{
+ // support of the xml:space attribute
+ if (ShouldMaintainPreLevel() &&
+ aNode->HasAttr(kNameSpaceID_XML, nsGkAtoms::space)) {
+ nsAutoString space;
+ aNode->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space);
+ if (space.EqualsLiteral("preserve"))
+ ++PreLevel();
+ }
+}
+
+void
+nsXMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode)
+{
+ // support of the xml:space attribute
+ if (ShouldMaintainPreLevel() &&
+ aNode->HasAttr(kNameSpaceID_XML, nsGkAtoms::space)) {
+ nsAutoString space;
+ aNode->GetAttr(kNameSpaceID_XML, nsGkAtoms::space, space);
+ if (space.EqualsLiteral("preserve"))
+ --PreLevel();
+ }
+}
+
+bool
+nsXMLContentSerializer::AppendNewLineToString(nsAString& aStr)
+{
+ bool result = AppendToString(mLineBreak, aStr);
+ mMayIgnoreLineBreakSequence = true;
+ mColPos = 0;
+ mAddSpace = false;
+ mIsIndentationAddedOnCurrentLine = false;
+ return result;
+}
+
+bool
+nsXMLContentSerializer::AppendIndentation(nsAString& aStr)
+{
+ mIsIndentationAddedOnCurrentLine = true;
+ bool result = AppendToString(mIndent, aStr);
+ mAddSpace = false;
+ mMayIgnoreLineBreakSequence = false;
+ return result;
+}
+
+bool
+nsXMLContentSerializer::IncrIndentation(nsIAtom* aName)
+{
+ // we want to keep the source readable
+ if (mDoWrap &&
+ mIndent.Length() >= uint32_t(mMaxColumn) - MIN_INDENTED_LINE_LENGTH) {
+ ++mIndentOverflow;
+ }
+ else {
+ return mIndent.AppendLiteral(INDENT_STRING, mozilla::fallible);
+ }
+
+ return true;
+}
+
+void
+nsXMLContentSerializer::DecrIndentation(nsIAtom* aName)
+{
+ if(mIndentOverflow)
+ --mIndentOverflow;
+ else
+ mIndent.Cut(0, INDENT_STRING_LENGTH);
+}
+
+bool
+nsXMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName)
+{
+ return mAddSpace;
+}
+
+bool
+nsXMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName)
+{
+ return false;
+}
+
+bool
+nsXMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName)
+{
+ return mAddSpace;
+}
+
+bool
+nsXMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName)
+{
+ return false;
+}
+
+bool
+nsXMLContentSerializer::AppendToStringConvertLF(const nsAString& aStr,
+ nsAString& aOutputStr)
+{
+ if (mBodyOnly && !mInBody) {
+ return true;
+ }
+
+ if (mDoRaw) {
+ NS_ENSURE_TRUE(AppendToString(aStr, aOutputStr), false);
+ }
+ else {
+ // Convert line-endings to mLineBreak
+ uint32_t start = 0;
+ uint32_t theLen = aStr.Length();
+ while (start < theLen) {
+ int32_t eol = aStr.FindChar('\n', start);
+ if (eol == kNotFound) {
+ nsDependentSubstring dataSubstring(aStr, start, theLen - start);
+ NS_ENSURE_TRUE(AppendToString(dataSubstring, aOutputStr), false);
+ start = theLen;
+ // if there was a line break before this substring
+ // AppendNewLineToString was called, so we should reverse
+ // this flag
+ mMayIgnoreLineBreakSequence = false;
+ }
+ else {
+ nsDependentSubstring dataSubstring(aStr, start, eol - start);
+ NS_ENSURE_TRUE(AppendToString(dataSubstring, aOutputStr), false);
+ NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
+ start = eol + 1;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+nsXMLContentSerializer::AppendFormatedWrapped_WhitespaceSequence(
+ nsASingleFragmentString::const_char_iterator &aPos,
+ const nsASingleFragmentString::const_char_iterator aEnd,
+ const nsASingleFragmentString::const_char_iterator aSequenceStart,
+ bool &aMayIgnoreStartOfLineWhitespaceSequence,
+ nsAString &aOutputStr)
+{
+ // Handle the complete sequence of whitespace.
+ // Continue to iterate until we find the first non-whitespace char.
+ // Updates "aPos" to point to the first unhandled char.
+ // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
+ // as well as the other "global" state flags.
+
+ bool sawBlankOrTab = false;
+ bool leaveLoop = false;
+
+ do {
+ switch (*aPos) {
+ case ' ':
+ case '\t':
+ sawBlankOrTab = true;
+ MOZ_FALLTHROUGH;
+ case '\n':
+ ++aPos;
+ // do not increase mColPos,
+ // because we will reduce the whitespace to a single char
+ break;
+ default:
+ leaveLoop = true;
+ break;
+ }
+ } while (!leaveLoop && aPos < aEnd);
+
+ if (mAddSpace) {
+ // if we had previously been asked to add space,
+ // our situation has not changed
+ }
+ else if (!sawBlankOrTab && mMayIgnoreLineBreakSequence) {
+ // nothing to do in the case where line breaks have already been added
+ // before the call of AppendToStringWrapped
+ // and only if we found line break in the sequence
+ mMayIgnoreLineBreakSequence = false;
+ }
+ else if (aMayIgnoreStartOfLineWhitespaceSequence) {
+ // nothing to do
+ aMayIgnoreStartOfLineWhitespaceSequence = false;
+ }
+ else {
+ if (sawBlankOrTab) {
+ if (mDoWrap && mColPos + 1 >= mMaxColumn) {
+ // no much sense in delaying, we only have one slot left,
+ // let's write a break now
+ bool result = aOutputStr.Append(mLineBreak, mozilla::fallible);
+ mColPos = 0;
+ mIsIndentationAddedOnCurrentLine = false;
+ mMayIgnoreLineBreakSequence = true;
+ NS_ENSURE_TRUE(result, false);
+ }
+ else {
+ // do not write out yet, we may write out either a space or a linebreak
+ // let's delay writing it out until we know more
+ mAddSpace = true;
+ ++mColPos; // eat a slot of available space
+ }
+ }
+ else {
+ // Asian text usually does not contain spaces, therefore we should not
+ // transform a linebreak into a space.
+ // Since we only saw linebreaks, but no spaces or tabs,
+ // let's write a linebreak now.
+ NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
+ }
+ }
+
+ return true;
+}
+
+bool
+nsXMLContentSerializer::AppendWrapped_NonWhitespaceSequence(
+ nsASingleFragmentString::const_char_iterator &aPos,
+ const nsASingleFragmentString::const_char_iterator aEnd,
+ const nsASingleFragmentString::const_char_iterator aSequenceStart,
+ bool &aMayIgnoreStartOfLineWhitespaceSequence,
+ bool &aSequenceStartAfterAWhiteSpace,
+ nsAString& aOutputStr)
+{
+ mMayIgnoreLineBreakSequence = false;
+ aMayIgnoreStartOfLineWhitespaceSequence = false;
+
+ // Handle the complete sequence of non-whitespace in this block
+ // Iterate until we find the first whitespace char or an aEnd condition
+ // Updates "aPos" to point to the first unhandled char.
+ // Also updates the aMayIgnoreStartOfLineWhitespaceSequence flag,
+ // as well as the other "global" state flags.
+
+ bool thisSequenceStartsAtBeginningOfLine = !mColPos;
+ bool onceAgainBecauseWeAddedBreakInFront = false;
+ bool foundWhitespaceInLoop;
+ uint32_t length, colPos;
+
+ do {
+
+ if (mColPos) {
+ colPos = mColPos;
+ }
+ else {
+ if (mDoFormat && !mDoRaw && !PreLevel() && !onceAgainBecauseWeAddedBreakInFront) {
+ colPos = mIndent.Length();
+ }
+ else
+ colPos = 0;
+ }
+ foundWhitespaceInLoop = false;
+ length = 0;
+ // we iterate until the next whitespace character
+ // or until we reach the maximum of character per line
+ // or until the end of the string to add.
+ do {
+ if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
+ foundWhitespaceInLoop = true;
+ break;
+ }
+
+ ++aPos;
+ ++length;
+ } while ( (!mDoWrap || colPos + length < mMaxColumn) && aPos < aEnd);
+
+ // in the case we don't reached the end of the string, but we reached the maxcolumn,
+ // we see if there is a whitespace after the maxcolumn
+ // if yes, then we can append directly the string instead of
+ // appending a new line etc.
+ if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
+ foundWhitespaceInLoop = true;
+ }
+
+ if (aPos == aEnd || foundWhitespaceInLoop) {
+ // there is enough room for the complete block we found
+ if (mDoFormat && !mColPos) {
+ NS_ENSURE_TRUE(AppendIndentation(aOutputStr), false);
+ }
+ else if (mAddSpace) {
+ bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
+ mAddSpace = false;
+ NS_ENSURE_TRUE(result, false);
+ }
+
+ mColPos += length;
+ NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, aPos - aSequenceStart, mozilla::fallible), false);
+
+ // We have not yet reached the max column, we will continue to
+ // fill the current line in the next outer loop iteration
+ // (this one in AppendToStringWrapped)
+ // make sure we return in this outer loop
+ onceAgainBecauseWeAddedBreakInFront = false;
+ }
+ else { // we reach the max column
+ if (!thisSequenceStartsAtBeginningOfLine &&
+ (mAddSpace || (!mDoFormat && aSequenceStartAfterAWhiteSpace))) {
+ // when !mDoFormat, mAddSpace is not used, mAddSpace is always false
+ // so, in the case where mDoWrap && !mDoFormat, if we want to enter in this condition...
+
+ // We can avoid to wrap. We try to add the whole block
+ // in an empty new line
+
+ NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
+ aPos = aSequenceStart;
+ thisSequenceStartsAtBeginningOfLine = true;
+ onceAgainBecauseWeAddedBreakInFront = true;
+ }
+ else {
+ // we must wrap
+ onceAgainBecauseWeAddedBreakInFront = false;
+ bool foundWrapPosition = false;
+ int32_t wrapPosition = 0;
+
+ if (mAllowLineBreaking) {
+ nsILineBreaker *lineBreaker = nsContentUtils::LineBreaker();
+
+ wrapPosition = lineBreaker->Prev(aSequenceStart,
+ (aEnd - aSequenceStart),
+ (aPos - aSequenceStart) + 1);
+ if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
+ foundWrapPosition = true;
+ }
+ else {
+ wrapPosition = lineBreaker->Next(aSequenceStart,
+ (aEnd - aSequenceStart),
+ (aPos - aSequenceStart));
+ if (wrapPosition != NS_LINEBREAKER_NEED_MORE_TEXT) {
+ foundWrapPosition = true;
+ }
+ }
+ }
+
+ if (foundWrapPosition) {
+ if (!mColPos && mDoFormat) {
+ NS_ENSURE_TRUE(AppendIndentation(aOutputStr), false);
+ }
+ else if (mAddSpace) {
+ bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
+ mAddSpace = false;
+ NS_ENSURE_TRUE(result, false);
+ }
+ NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, wrapPosition, mozilla::fallible), false);
+
+ NS_ENSURE_TRUE(AppendNewLineToString(aOutputStr), false);
+ aPos = aSequenceStart + wrapPosition;
+ aMayIgnoreStartOfLineWhitespaceSequence = true;
+ }
+ else {
+ // try some simple fallback logic
+ // go forward up to the next whitespace position,
+ // in the worst case this will be all the rest of the data
+
+ // we update the mColPos variable with the length of
+ // the part already parsed.
+ mColPos += length;
+
+ // now try to find the next whitespace
+ do {
+ if (*aPos == ' ' || *aPos == '\t' || *aPos == '\n') {
+ break;
+ }
+
+ ++aPos;
+ ++mColPos;
+ } while (aPos < aEnd);
+
+ if (mAddSpace) {
+ bool result = aOutputStr.Append(char16_t(' '), mozilla::fallible);
+ mAddSpace = false;
+ NS_ENSURE_TRUE(result, false);
+ }
+ NS_ENSURE_TRUE(aOutputStr.Append(aSequenceStart, aPos - aSequenceStart, mozilla::fallible), false);
+ }
+ }
+ aSequenceStartAfterAWhiteSpace = false;
+ }
+ } while (onceAgainBecauseWeAddedBreakInFront);
+
+ return true;
+}
+
+bool
+nsXMLContentSerializer::AppendToStringFormatedWrapped(const nsASingleFragmentString& aStr,
+ nsAString& aOutputStr)
+{
+ if (mBodyOnly && !mInBody) {
+ return true;
+ }
+
+ nsASingleFragmentString::const_char_iterator pos, end, sequenceStart;
+
+ aStr.BeginReading(pos);
+ aStr.EndReading(end);
+
+ bool sequenceStartAfterAWhitespace = false;
+ if (pos < end) {
+ nsAString::const_char_iterator end2;
+ aOutputStr.EndReading(end2);
+ --end2;
+ if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
+ sequenceStartAfterAWhitespace = true;
+ }
+ }
+
+ // if the current line already has text on it, such as a tag,
+ // leading whitespace is significant
+ bool mayIgnoreStartOfLineWhitespaceSequence =
+ (!mColPos || (mIsIndentationAddedOnCurrentLine &&
+ sequenceStartAfterAWhitespace &&
+ uint32_t(mColPos) == mIndent.Length()));
+
+ while (pos < end) {
+ sequenceStart = pos;
+
+ // if beginning of a whitespace sequence
+ if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
+ NS_ENSURE_TRUE(AppendFormatedWrapped_WhitespaceSequence(pos, end, sequenceStart,
+ mayIgnoreStartOfLineWhitespaceSequence, aOutputStr), false);
+ }
+ else { // any other non-whitespace char
+ NS_ENSURE_TRUE(AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart,
+ mayIgnoreStartOfLineWhitespaceSequence, sequenceStartAfterAWhitespace,
+ aOutputStr), false);
+ }
+ }
+
+ return true;
+}
+
+bool
+nsXMLContentSerializer::AppendWrapped_WhitespaceSequence(
+ nsASingleFragmentString::const_char_iterator &aPos,
+ const nsASingleFragmentString::const_char_iterator aEnd,
+ const nsASingleFragmentString::const_char_iterator aSequenceStart,
+ nsAString &aOutputStr)
+{
+ // Handle the complete sequence of whitespace.
+ // Continue to iterate until we find the first non-whitespace char.
+ // Updates "aPos" to point to the first unhandled char.
+ mAddSpace = false;
+ mIsIndentationAddedOnCurrentLine = false;
+
+ bool leaveLoop = false;
+ nsASingleFragmentString::const_char_iterator lastPos = aPos;
+
+ do {
+ switch (*aPos) {
+ case ' ':
+ case '\t':
+ // if there are too many spaces on a line, we wrap
+ if (mColPos >= mMaxColumn) {
+ if (lastPos != aPos) {
+ NS_ENSURE_TRUE(aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible), false);
+ }
+ NS_ENSURE_TRUE(AppendToString(mLineBreak, aOutputStr), false);
+ mColPos = 0;
+ lastPos = aPos;
+ }
+
+ ++mColPos;
+ ++aPos;
+ break;
+ case '\n':
+ if (lastPos != aPos) {
+ NS_ENSURE_TRUE(aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible), false);
+ }
+ NS_ENSURE_TRUE(AppendToString(mLineBreak, aOutputStr), false);
+ mColPos = 0;
+ ++aPos;
+ lastPos = aPos;
+ break;
+ default:
+ leaveLoop = true;
+ break;
+ }
+ } while (!leaveLoop && aPos < aEnd);
+
+ if (lastPos != aPos) {
+ NS_ENSURE_TRUE(aOutputStr.Append(lastPos, aPos - lastPos, mozilla::fallible), false);
+ }
+
+ return true;
+}
+
+bool
+nsXMLContentSerializer::AppendToStringWrapped(const nsASingleFragmentString& aStr,
+ nsAString& aOutputStr)
+{
+ if (mBodyOnly && !mInBody) {
+ return true;
+ }
+
+ nsASingleFragmentString::const_char_iterator pos, end, sequenceStart;
+
+ aStr.BeginReading(pos);
+ aStr.EndReading(end);
+
+ // not used in this case, but needed by AppendWrapped_NonWhitespaceSequence
+ bool mayIgnoreStartOfLineWhitespaceSequence = false;
+ mMayIgnoreLineBreakSequence = false;
+
+ bool sequenceStartAfterAWhitespace = false;
+ if (pos < end && !aOutputStr.IsEmpty()) {
+ nsAString::const_char_iterator end2;
+ aOutputStr.EndReading(end2);
+ --end2;
+ if (*end2 == ' ' || *end2 == '\n' || *end2 == '\t') {
+ sequenceStartAfterAWhitespace = true;
+ }
+ }
+
+ while (pos < end) {
+ sequenceStart = pos;
+
+ // if beginning of a whitespace sequence
+ if (*pos == ' ' || *pos == '\n' || *pos == '\t') {
+ sequenceStartAfterAWhitespace = true;
+ NS_ENSURE_TRUE(AppendWrapped_WhitespaceSequence(pos, end,
+ sequenceStart, aOutputStr), false);
+ }
+ else { // any other non-whitespace char
+ NS_ENSURE_TRUE(AppendWrapped_NonWhitespaceSequence(pos, end, sequenceStart,
+ mayIgnoreStartOfLineWhitespaceSequence,
+ sequenceStartAfterAWhitespace, aOutputStr), false);
+ }
+ }
+
+ return true;
+}
+
+bool
+nsXMLContentSerializer::ShouldMaintainPreLevel() const
+{
+ // Only attempt to maintain the pre level for consumers who care about it.
+ return !mDoRaw || (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre);
+}
diff --git a/dom/base/nsXMLContentSerializer.h b/dom/base/nsXMLContentSerializer.h
new file mode 100644
index 000000000..941acb179
--- /dev/null
+++ b/dom/base/nsXMLContentSerializer.h
@@ -0,0 +1,406 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsIContentSerializer implementation that can be used with an
+ * nsIDocumentEncoder to convert an XML DOM to an XML string that
+ * could be parsed into more or less the original DOM.
+ */
+
+#ifndef nsXMLContentSerializer_h__
+#define nsXMLContentSerializer_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIContentSerializer.h"
+#include "nsISupportsUtils.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+#define kIndentStr NS_LITERAL_STRING(" ")
+#define kEndTag NS_LITERAL_STRING("</")
+
+class nsIAtom;
+class nsINode;
+
+class nsXMLContentSerializer : public nsIContentSerializer {
+ public:
+ nsXMLContentSerializer();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
+ const char* aCharSet, bool aIsCopying,
+ bool aRewriteEncodingDeclaration) override;
+
+ NS_IMETHOD AppendText(nsIContent* aText, int32_t aStartOffset,
+ int32_t aEndOffset, nsAString& aStr) override;
+
+ NS_IMETHOD AppendCDATASection(nsIContent* aCDATASection,
+ int32_t aStartOffset, int32_t aEndOffset,
+ nsAString& aStr) override;
+
+ NS_IMETHOD AppendProcessingInstruction(nsIContent* aPI,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr) override;
+
+ NS_IMETHOD AppendComment(nsIContent* aComment, int32_t aStartOffset,
+ int32_t aEndOffset, nsAString& aStr) override;
+
+ NS_IMETHOD AppendDoctype(nsIContent *aDoctype,
+ nsAString& aStr) override;
+
+ NS_IMETHOD AppendElementStart(mozilla::dom::Element* aElement,
+ mozilla::dom::Element* aOriginalElement,
+ nsAString& aStr) override;
+
+ NS_IMETHOD AppendElementEnd(mozilla::dom::Element* aElement,
+ nsAString& aStr) override;
+
+ NS_IMETHOD Flush(nsAString& aStr) override { return NS_OK; }
+
+ NS_IMETHOD AppendDocumentStart(nsIDocument *aDocument,
+ nsAString& aStr) override;
+
+ protected:
+ virtual ~nsXMLContentSerializer();
+
+ /**
+ * Appends a char16_t character and increments the column position
+ */
+ MOZ_MUST_USE
+ bool AppendToString(const char16_t aChar,
+ nsAString& aOutputStr);
+
+ /**
+ * Appends a nsAString string and increments the column position
+ */
+ MOZ_MUST_USE
+ bool AppendToString(const nsAString& aStr,
+ nsAString& aOutputStr);
+
+ /**
+ * Appends a string by replacing all line-endings
+ * by mLineBreak, except in the case of raw output.
+ * It increments the column position.
+ */
+ MOZ_MUST_USE
+ bool AppendToStringConvertLF(const nsAString& aStr,
+ nsAString& aOutputStr);
+
+ /**
+ * Appends a string by wrapping it when necessary.
+ * It updates the column position.
+ */
+ MOZ_MUST_USE
+ bool AppendToStringWrapped(const nsASingleFragmentString& aStr,
+ nsAString& aOutputStr);
+
+ /**
+ * Appends a string by formating and wrapping it when necessary
+ * It updates the column position.
+ */
+ MOZ_MUST_USE
+ bool AppendToStringFormatedWrapped(const nsASingleFragmentString& aStr,
+ nsAString& aOutputStr);
+
+ // used by AppendToStringWrapped
+ MOZ_MUST_USE
+ bool AppendWrapped_WhitespaceSequence(
+ nsASingleFragmentString::const_char_iterator &aPos,
+ const nsASingleFragmentString::const_char_iterator aEnd,
+ const nsASingleFragmentString::const_char_iterator aSequenceStart,
+ nsAString &aOutputStr);
+
+ // used by AppendToStringFormatedWrapped
+ MOZ_MUST_USE
+ bool AppendFormatedWrapped_WhitespaceSequence(
+ nsASingleFragmentString::const_char_iterator &aPos,
+ const nsASingleFragmentString::const_char_iterator aEnd,
+ const nsASingleFragmentString::const_char_iterator aSequenceStart,
+ bool &aMayIgnoreStartOfLineWhitespaceSequence,
+ nsAString &aOutputStr);
+
+ // used by AppendToStringWrapped and AppendToStringFormatedWrapped
+ MOZ_MUST_USE
+ bool AppendWrapped_NonWhitespaceSequence(
+ nsASingleFragmentString::const_char_iterator &aPos,
+ const nsASingleFragmentString::const_char_iterator aEnd,
+ const nsASingleFragmentString::const_char_iterator aSequenceStart,
+ bool &aMayIgnoreStartOfLineWhitespaceSequence,
+ bool &aSequenceStartAfterAWhiteSpace,
+ nsAString &aOutputStr);
+
+ /**
+ * add mLineBreak to the string
+ * It updates the column position and other flags.
+ */
+ MOZ_MUST_USE
+ bool AppendNewLineToString(nsAString& aOutputStr);
+
+
+ /**
+ * Appends a string by translating entities
+ * It doesn't increment the column position
+ */
+ MOZ_MUST_USE
+ virtual bool AppendAndTranslateEntities(const nsAString& aStr,
+ nsAString& aOutputStr);
+
+ /**
+ * retrieve the text content of the node and append it to the given string
+ * It doesn't increment the column position
+ */
+ nsresult AppendTextData(nsIContent* aNode,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ nsAString& aStr,
+ bool aTranslateEntities);
+
+ virtual nsresult PushNameSpaceDecl(const nsAString& aPrefix,
+ const nsAString& aURI,
+ nsIContent* aOwner);
+ void PopNameSpaceDeclsFor(nsIContent* aOwner);
+
+ /**
+ * The problem that ConfirmPrefix fixes is that anyone can insert nodes
+ * through the DOM that have a namespace URI and a random or empty or
+ * previously existing prefix that's totally unrelated to the prefixes
+ * declared at that point through xmlns attributes. So what ConfirmPrefix
+ * does is ensure that we can map aPrefix to the namespace URI aURI (for
+ * example, that the prefix is not already mapped to some other namespace).
+ * aPrefix will be adjusted, if necessary, so the value of the prefix
+ * _after_ this call is what should be serialized.
+ * @param aPrefix the prefix that may need adjusting
+ * @param aURI the namespace URI we want aPrefix to point to
+ * @param aElement the element we're working with (needed for proper default
+ * namespace handling)
+ * @param aIsAttribute true if we're confirming a prefix for an attribute.
+ * @return true if we need to push the (prefix, uri) pair on the namespace
+ * stack (note that this can happen even if the prefix is
+ * empty).
+ */
+ bool ConfirmPrefix(nsAString& aPrefix,
+ const nsAString& aURI,
+ nsIContent* aElement,
+ bool aIsAttribute);
+ /**
+ * GenerateNewPrefix generates a new prefix and writes it to aPrefix
+ */
+ void GenerateNewPrefix(nsAString& aPrefix);
+
+ uint32_t ScanNamespaceDeclarations(nsIContent* aContent,
+ nsIContent *aOriginalElement,
+ const nsAString& aTagNamespaceURI);
+
+ MOZ_MUST_USE
+ virtual bool SerializeAttributes(nsIContent* aContent,
+ nsIContent *aOriginalElement,
+ nsAString& aTagPrefix,
+ const nsAString& aTagNamespaceURI,
+ nsIAtom* aTagName,
+ nsAString& aStr,
+ uint32_t aSkipAttr,
+ bool aAddNSAttr);
+
+ MOZ_MUST_USE
+ bool SerializeAttr(const nsAString& aPrefix,
+ const nsAString& aName,
+ const nsAString& aValue,
+ nsAString& aStr,
+ bool aDoEscapeEntities);
+
+ bool IsJavaScript(nsIContent * aContent,
+ nsIAtom* aAttrNameAtom,
+ int32_t aAttrNamespaceID,
+ const nsAString& aValueString);
+
+ /**
+ * This method can be redefined to check if the element can be serialized.
+ * It is called when the serialization of the start tag is asked
+ * (AppendElementStart)
+ * In this method you can also force the formating
+ * by setting aForceFormat to true.
+ * @return boolean true if the element can be output
+ */
+ virtual bool CheckElementStart(nsIContent * aContent,
+ bool & aForceFormat,
+ nsAString& aStr,
+ nsresult& aResult);
+
+ /**
+ * This method is responsible for appending the '>' at the end of the start
+ * tag, possibly preceded by '/' and maybe a ' ' before that too.
+ *
+ * aElement and aOriginalElement are the same as the corresponding arguments
+ * to AppendElementStart.
+ */
+ MOZ_MUST_USE
+ bool AppendEndOfElementStart(mozilla::dom::Element* aEleemnt,
+ mozilla::dom::Element* aOriginalElement,
+ nsAString& aStr);
+
+ /**
+ * This method can be redefine to serialize additional things just after
+ * after the serialization ot the start tag.
+ * (called at the end of AppendElementStart)
+ */
+ MOZ_MUST_USE
+ virtual bool AfterElementStart(nsIContent* aContent,
+ nsIContent* aOriginalElement,
+ nsAString& aStr) { return true; };
+
+ /**
+ * This method can be redefined to check if the element can be serialized.
+ * It is called when the serialization of the end tag is asked
+ * (AppendElementEnd)
+ * In this method you can also force the formating
+ * by setting aForceFormat to true.
+ * @return boolean true if the element can be output
+ */
+ virtual bool CheckElementEnd(mozilla::dom::Element* aElement,
+ bool& aForceFormat,
+ nsAString& aStr);
+
+ /**
+ * This method can be redefine to serialize additional things just after
+ * after the serialization ot the end tag.
+ * (called at the end of AppendElementStart)
+ */
+ virtual void AfterElementEnd(nsIContent * aContent,
+ nsAString& aStr) { };
+
+ /**
+ * Returns true if a line break should be inserted before an element open tag
+ */
+ virtual bool LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName);
+
+ /**
+ * Returns true if a line break should be inserted after an element open tag
+ */
+ virtual bool LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName);
+
+ /**
+ * Returns true if a line break should be inserted after an element close tag
+ */
+ virtual bool LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName);
+
+ /**
+ * Returns true if a line break should be inserted after an element close tag
+ */
+ virtual bool LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName);
+
+ /**
+ * add intendation. Call only in the case of formating and if the current
+ * position is at 0. It updates the column position.
+ */
+ MOZ_MUST_USE
+ bool AppendIndentation(nsAString& aStr);
+
+ MOZ_MUST_USE
+ bool IncrIndentation(nsIAtom* aName);
+ void DecrIndentation(nsIAtom* aName);
+
+ // Functions to check for newlines that needs to be added between nodes in
+ // the root of a document. See mAddNewlineForRootNode
+ MOZ_MUST_USE
+ bool MaybeAddNewlineForRootNode(nsAString& aStr);
+ void MaybeFlagNewlineForRootNode(nsINode* aNode);
+
+ // Functions to check if we enter in or leave from a preformated content
+ virtual void MaybeEnterInPreContent(nsIContent* aNode);
+ virtual void MaybeLeaveFromPreContent(nsIContent* aNode);
+
+ bool ShouldMaintainPreLevel() const;
+ int32_t PreLevel() const {
+ MOZ_ASSERT(ShouldMaintainPreLevel());
+ return mPreLevel;
+ }
+ int32_t& PreLevel() {
+ MOZ_ASSERT(ShouldMaintainPreLevel());
+ return mPreLevel;
+ }
+
+ int32_t mPrefixIndex;
+
+ struct NameSpaceDecl {
+ nsString mPrefix;
+ nsString mURI;
+ nsIContent* mOwner;
+ };
+
+ nsTArray<NameSpaceDecl> mNameSpaceStack;
+
+ // nsIDocumentEncoder flags
+ MOZ_INIT_OUTSIDE_CTOR uint32_t mFlags;
+
+ // characters to use for line break
+ nsString mLineBreak;
+
+ // The charset that was passed to Init()
+ nsCString mCharset;
+
+ // current column position on the current line
+ uint32_t mColPos;
+
+ // true = pretty formating should be done (OutputFormated flag)
+ MOZ_INIT_OUTSIDE_CTOR bool mDoFormat;
+
+ // true = no formatting,(OutputRaw flag)
+ // no newline convertion and no rewrap long lines even if OutputWrap is set.
+ MOZ_INIT_OUTSIDE_CTOR bool mDoRaw;
+
+ // true = wrapping should be done (OutputWrap flag)
+ MOZ_INIT_OUTSIDE_CTOR bool mDoWrap;
+
+ // true = we can break lines (OutputDisallowLineBreaking flag)
+ MOZ_INIT_OUTSIDE_CTOR bool mAllowLineBreaking;
+
+ // number of maximum column in a line, in the wrap mode
+ MOZ_INIT_OUTSIDE_CTOR uint32_t mMaxColumn;
+
+ // current indent value
+ nsString mIndent;
+
+ // this is the indentation level after the indentation reached
+ // the maximum length of indentation
+ int32_t mIndentOverflow;
+
+ // says if the indentation has been already added on the current line
+ bool mIsIndentationAddedOnCurrentLine;
+
+ // the string which is currently added is in an attribute
+ bool mInAttribute;
+
+ // true = a newline character should be added. It's only
+ // useful when serializing root nodes. see MaybeAddNewlineForRootNode and
+ // MaybeFlagNewlineForRootNode
+ bool mAddNewlineForRootNode;
+
+ // Indicates that a space will be added if and only if content is
+ // continued on the same line while serializing source. Otherwise,
+ // the newline character acts as the whitespace and no space is needed.
+ // used when mDoFormat = true
+ bool mAddSpace;
+
+ // says that if the next string to add contains a newline character at the
+ // begining, then this newline character should be ignored, because a
+ // such character has already been added into the output string
+ bool mMayIgnoreLineBreakSequence;
+
+ bool mBodyOnly;
+ int32_t mInBody;
+
+private:
+ // number of nested elements which have preformated content
+ MOZ_INIT_OUTSIDE_CTOR int32_t mPreLevel;
+};
+
+nsresult
+NS_NewXMLContentSerializer(nsIContentSerializer** aSerializer);
+
+#endif
diff --git a/dom/base/nsXMLNameSpaceMap.cpp b/dom/base/nsXMLNameSpaceMap.cpp
new file mode 100644
index 000000000..7134cfebb
--- /dev/null
+++ b/dom/base/nsXMLNameSpaceMap.cpp
@@ -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/. */
+
+/*
+ * A class for keeping track of prefix-to-namespace-id mappings
+ */
+
+#include "nsXMLNameSpaceMap.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+
+template <>
+class nsDefaultComparator <nsNameSpaceEntry, nsIAtom*> {
+ public:
+ bool Equals(const nsNameSpaceEntry& aEntry, nsIAtom* const& aPrefix) const {
+ return aEntry.prefix == aPrefix;
+ }
+};
+
+template <>
+class nsDefaultComparator <nsNameSpaceEntry, int32_t> {
+ public:
+ bool Equals(const nsNameSpaceEntry& aEntry, const int32_t& aNameSpace) const {
+ return aEntry.nameSpaceID == aNameSpace;
+ }
+};
+
+
+/* static */ nsXMLNameSpaceMap*
+nsXMLNameSpaceMap::Create(bool aForXML)
+{
+ nsXMLNameSpaceMap *map = new nsXMLNameSpaceMap();
+ NS_ENSURE_TRUE(map, nullptr);
+
+ if (aForXML) {
+ nsresult rv1 = map->AddPrefix(nsGkAtoms::xmlns,
+ kNameSpaceID_XMLNS);
+ nsresult rv2 = map->AddPrefix(nsGkAtoms::xml, kNameSpaceID_XML);
+
+ if (NS_FAILED(rv1) || NS_FAILED(rv2)) {
+ delete map;
+ map = nullptr;
+ }
+ }
+
+ return map;
+}
+
+nsXMLNameSpaceMap::nsXMLNameSpaceMap()
+ : mNameSpaces(4)
+{
+}
+
+nsresult
+nsXMLNameSpaceMap::AddPrefix(nsIAtom *aPrefix, int32_t aNameSpaceID)
+{
+ if (!mNameSpaces.Contains(aPrefix) && !mNameSpaces.AppendElement(aPrefix)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mNameSpaces[mNameSpaces.IndexOf(aPrefix)].nameSpaceID = aNameSpaceID;
+ return NS_OK;
+}
+
+nsresult
+nsXMLNameSpaceMap::AddPrefix(nsIAtom *aPrefix, nsString &aURI)
+{
+ int32_t id;
+ nsresult rv = nsContentUtils::NameSpaceManager()->RegisterNameSpace(aURI,
+ id);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return AddPrefix(aPrefix, id);
+}
+
+int32_t
+nsXMLNameSpaceMap::FindNameSpaceID(nsIAtom *aPrefix) const
+{
+ size_t index = mNameSpaces.IndexOf(aPrefix);
+ if (index != mNameSpaces.NoIndex) {
+ return mNameSpaces[index].nameSpaceID;
+ }
+
+ // The default mapping for no prefix is no namespace. If a non-null prefix
+ // was specified and we didn't find it, we return an error.
+
+ return aPrefix ? kNameSpaceID_Unknown : kNameSpaceID_None;
+}
+
+nsIAtom*
+nsXMLNameSpaceMap::FindPrefix(int32_t aNameSpaceID) const
+{
+ size_t index = mNameSpaces.IndexOf(aNameSpaceID);
+ if (index != mNameSpaces.NoIndex) {
+ return mNameSpaces[index].prefix;
+ }
+
+ return nullptr;
+}
+
+void
+nsXMLNameSpaceMap::Clear()
+{
+ mNameSpaces.Clear();
+}
diff --git a/dom/base/nsXMLNameSpaceMap.h b/dom/base/nsXMLNameSpaceMap.h
new file mode 100644
index 000000000..c8c1eb8ba
--- /dev/null
+++ b/dom/base/nsXMLNameSpaceMap.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsXMLNameSpaceMap_h_
+#define nsXMLNameSpaceMap_h_
+
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+
+struct nsNameSpaceEntry
+{
+ explicit nsNameSpaceEntry(nsIAtom* aPrefix)
+ : prefix(aPrefix) {}
+
+ nsCOMPtr<nsIAtom> prefix;
+ MOZ_INIT_OUTSIDE_CTOR int32_t nameSpaceID;
+};
+
+/**
+ * nsXMLNameSpaceMap contains a set of prefixes which are mapped onto
+ * namespaces. It allows the set to be searched by prefix or by namespace ID.
+ */
+class nsXMLNameSpaceMap
+{
+public:
+ /**
+ * Allocates a new nsXMLNameSpaceMap (with new()) and if aForXML is
+ * true initializes it with the xmlns and xml namespaces.
+ */
+ static nsXMLNameSpaceMap* Create(bool aForXML);
+
+ /**
+ * Add a prefix and its corresponding namespace ID to the map.
+ * Passing a null |aPrefix| corresponds to the default namespace, which may
+ * be set to something other than kNameSpaceID_None.
+ */
+ nsresult AddPrefix(nsIAtom *aPrefix, int32_t aNameSpaceID);
+
+ /**
+ * Add a prefix and a namespace URI to the map. The URI will be converted
+ * to its corresponding namespace ID.
+ */
+ nsresult AddPrefix(nsIAtom *aPrefix, nsString &aURI);
+
+ /*
+ * Returns the namespace ID for the given prefix, if it is in the map.
+ * If |aPrefix| is null and is not in the map, then a null namespace
+ * (kNameSpaceID_None) is returned. If |aPrefix| is non-null and is not in
+ * the map, then kNameSpaceID_Unknown is returned.
+ */
+ int32_t FindNameSpaceID(nsIAtom *aPrefix) const;
+
+ /**
+ * If the given namespace ID is in the map, then the first prefix which
+ * maps to that namespace is returned. Otherwise, null is returned.
+ */
+ nsIAtom* FindPrefix(int32_t aNameSpaceID) const;
+
+ /* Removes all prefix mappings. */
+ void Clear();
+
+ ~nsXMLNameSpaceMap() { Clear(); }
+
+private:
+ nsXMLNameSpaceMap(); // use Create() to create new instances
+
+ nsTArray<nsNameSpaceEntry> mNameSpaces;
+};
+
+#endif
diff --git a/dom/base/test/345339_iframe.html b/dom/base/test/345339_iframe.html
new file mode 100644
index 000000000..890fb0d1c
--- /dev/null
+++ b/dom/base/test/345339_iframe.html
@@ -0,0 +1,27 @@
+<!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 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 000000000..ac81cb031
--- /dev/null
+++ b/dom/base/test/Ahem.ttf
Binary files differ
diff --git a/dom/base/test/accesscontrol.resource b/dom/base/test/accesscontrol.resource
new file mode 100644
index 000000000..aca66f6f8
--- /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 000000000..75f1f8897
--- /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 000000000..bed764fbf
--- /dev/null
+++ b/dom/base/test/audio.ogg
Binary files differ
diff --git a/dom/base/test/audioEndedDuringPlaying.webm b/dom/base/test/audioEndedDuringPlaying.webm
new file mode 100644
index 000000000..6ae65448d
--- /dev/null
+++ b/dom/base/test/audioEndedDuringPlaying.webm
Binary files differ
diff --git a/dom/base/test/badContentType.eventsource b/dom/base/test/badContentType.eventsource
new file mode 100644
index 000000000..c9d0739e1
--- /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 000000000..a1f9e38d9
--- /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 000000000..c9d0739e1
--- /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 000000000..545a9a201
--- /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 000000000..0c635f0b5
--- /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 000000000..9bb8badca
--- /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 000000000..ad6fa694f
--- /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 000000000..9bb8badca
--- /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 000000000..60949f7a2
--- /dev/null
+++ b/dom/base/test/browser.ini
@@ -0,0 +1,28 @@
+[DEFAULT]
+support-files =
+ empty.html
+ file_bug1011748_redirect.sjs
+ file_bug1011748_OK.sjs
+ file_messagemanager_unload.html
+ file_use_counter_outer.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
+
+[browser_bug593387.js]
+[browser_bug902350.js]
+tags = mcb
+[browser_bug1011748.js]
+[browser_bug1058164.js]
+[browser_messagemanager_loadprocessscript.js]
+[browser_messagemanager_targetframeloader.js]
+[browser_messagemanager_unload.js]
+[browser_pagehide_on_tab_close.js]
+skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
+[browser_state_notifications.js]
+skip-if = true # Bug 1271028
+[browser_use_counters.js]
+[browser_bug1307747.js]
diff --git a/dom/base/test/browser_bug1011748.js b/dom/base/test/browser_bug1011748.js
new file mode 100644
index 000000000..a2158500d
--- /dev/null
+++ b/dom/base/test/browser_bug1011748.js
@@ -0,0 +1,31 @@
+const gHttpTestRoot = "http://example.com/browser/dom/base/test/";
+
+add_task(function* () {
+ var statusTexts = [];
+ var xhr = new XMLHttpRequest();
+ var observer = {
+ observe: function (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", false);
+ yield 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 000000000..1ee01ca40
--- /dev/null
+++ b/dom/base/test/browser_bug1058164.js
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+/**
+ * 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 checkSatisfied = () => {
+ if (order.length < expectedOrder.length) {
+ // We're still waiting...
+ return;
+ } else {
+ browser.removeEventListener("pagehide", eventListener);
+ browser.removeEventListener("pageshow", eventListener);
+
+ for (let i = 0; i < expectedOrder.length; ++i) {
+ is(order[i], expectedOrder[i], "Got expected event");
+ }
+ resolve();
+ }
+ };
+
+ let eventListener = (e) => {
+ if (e.persisted) {
+ order.push(e.type);
+ checkSatisfied();
+ }
+ };
+
+ browser.addEventListener("pagehide", eventListener);
+ browser.addEventListener("pageshow", eventListener);
+ });
+}
+
+/**
+ * Tests that frame scripts get pageshow / pagehide events when
+ * swapping browser frameloaders (which occurs when moving a tab
+ * into a different window).
+ */
+add_task(function* test_swap_frameloader_pagevisibility_events() {
+ // Load a new tab that we'll tear out...
+ let tab = gBrowser.addTab(PAGE);
+ gBrowser.selectedTab = tab;
+ let firstBrowser = tab.linkedBrowser;
+ yield 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.
+ yield BrowserTestUtils.waitForEvent(newWindow, "load");
+ let newWindowBrowser = newWindow.gBrowser.selectedBrowser;
+
+ // Wait for the expected pagehide and pageshow events on the initial browser
+ yield 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 = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let emptyBrowser = newTab.linkedBrowser;
+
+ // Wait for that initial browser to show its pageshow event so that we
+ // don't confuse it with the other expected events. Note that we can't
+ // use BrowserTestUtils.waitForEvent here because we're using the
+ // e10s add-on shims in the e10s-case. I'm doing this because I couldn't
+ // find a way of sending down a frame script to the newly opened windows
+ // and tabs fast enough to attach the event handlers before they were
+ // fired.
+ yield new Promise((resolve) => {
+ emptyBrowser.addEventListener("pageshow", function onPageShow() {
+ emptyBrowser.removeEventListener("pageshow", onPageShow);
+ resolve();
+ });
+ });
+
+ // 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);
+
+ yield emptyBrowserPromise;
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/dom/base/test/browser_bug1307747.js b/dom/base/test/browser_bug1307747.js
new file mode 100644
index 000000000..8d1448cc2
--- /dev/null
+++ b/dom/base/test/browser_bug1307747.js
@@ -0,0 +1,32 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+
+var {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+
+const BASE_URI = "http://mochi.test:8888/browser/dom/base/test/empty.html";
+
+add_task(function* test_initialize() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_URI);
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ let blob = yield ContentTask.spawn(browser, null, function() {
+ let blob = new Blob([new Array(1024*1024).join('123456789ABCDEF')]);
+ return blob;
+ });
+
+ ok(blob, "We have a blob!");
+ is(blob.size, new Array(1024*1024).join('123456789ABCDEF').length, "The size matches");
+
+ let status = yield ContentTask.spawn(browser, blob, function(blob) {
+ return new Promise(resolve => {
+ let fr = new content.FileReader();
+ fr.readAsText(blob);
+ fr.onloadend = function() {
+ resolve(fr.result == new Array(1024*1024).join('123456789ABCDEF'));
+ }
+ });
+ });
+
+ ok(status, "Data match!");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/base/test/browser_bug593387.js b/dom/base/test/browser_bug593387.js
new file mode 100644
index 000000000..aa4f9dc0f
--- /dev/null
+++ b/dom/base/test/browser_bug593387.js
@@ -0,0 +1,70 @@
+/*
+ * Test for bug 593387
+ * Loads a chrome document in a content docshell and then inserts a
+ * X-Frame-Options: DENY iframe into the document and verifies that the document
+ * loads. The policy we are enforcing is outlined here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=593387#c17
+*/
+
+add_task(function* test() {
+ yield BrowserTestUtils.withNewTab({ gBrowser,
+ url: "chrome://global/content/mozilla.xhtml" },
+ function* (newBrowser) {
+ // NB: We load the chrome:// page in the parent process.
+ yield testXFOFrameInChrome(newBrowser);
+
+ // Run next test (try the same with a content top-level context)
+ yield BrowserTestUtils.loadURI(newBrowser, "http://example.com/");
+ yield BrowserTestUtils.browserLoaded(newBrowser);
+
+ yield ContentTask.spawn(newBrowser, null, testXFOFrameInContent);
+ });
+});
+
+function testXFOFrameInChrome(newBrowser) {
+ // Insert an iframe that specifies "X-Frame-Options: DENY" and verify
+ // that it loads, since the top context is chrome
+ var deferred = {};
+ deferred.promise = new Promise((resolve) => {
+ deferred.resolve = resolve;
+ });
+
+ var frame = newBrowser.contentDocument.createElement("iframe");
+ frame.src = "http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=deny&xfo=deny";
+ frame.addEventListener("load", function loaded() {
+ frame.removeEventListener("load", loaded, true);
+
+ // Test that the frame loaded
+ var test = this.contentDocument.getElementById("test");
+ is(test.tagName, "H1", "wrong element type");
+ is(test.textContent, "deny", "wrong textContent");
+ deferred.resolve();
+ }, true);
+
+ newBrowser.contentDocument.body.appendChild(frame);
+ return deferred.promise;
+}
+
+function testXFOFrameInContent(newBrowser) {
+ // Insert an iframe that specifies "X-Frame-Options: DENY" and verify that it
+ // is blocked from loading since the top browsing context is another site
+ var deferred = {};
+ deferred.promise = new Promise((resolve) => {
+ deferred.resolve = resolve;
+ });
+
+ var frame = content.document.createElement("iframe");
+ frame.src = "http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=deny&xfo=deny";
+ frame.addEventListener("load", function loaded() {
+ frame.removeEventListener("load", loaded, true);
+
+ // Test that the frame DID NOT load
+ var test = this.contentDocument.getElementById("test");
+ Assert.equal(test, null, "should be about:blank");
+
+ deferred.resolve();
+ }, true);
+
+ content.document.body.appendChild(frame);
+ return deferred.promise;
+}
diff --git a/dom/base/test/browser_bug902350.js b/dom/base/test/browser_bug902350.js
new file mode 100644
index 000000000..cce014779
--- /dev/null
+++ b/dom/base/test/browser_bug902350.js
@@ -0,0 +1,66 @@
+/*
+ * Mixed Content Block frame navigates for target="_top" - Test for Bug 902350
+ */
+
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+const gHttpTestRoot = "https://example.com/tests/dom/base/test/";
+var origBlockActive;
+var gTestBrowser = null;
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+});
+
+function MixedTestsCompleted() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop()
+
+ BrowserTestUtils.browserLoaded(gTestBrowser, true /*includeSubFrames*/).then(MixedTest1A);
+ var url = gHttpTestRoot + "file_bug902350.html";
+ gTestBrowser.loadURI(url);
+}
+
+// Need to capture 2 loads, one for the main page and one for the iframe
+function MixedTest1A() {
+ BrowserTestUtils.browserLoaded(gTestBrowser, true /*includeSubFrames*/).then(MixedTest1B);
+}
+
+// Find the iframe and click the link in it
+function MixedTest1B() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(MixedTest1C);
+
+ ContentTask.spawn(gTestBrowser, null, function() {
+ var frame = content.document.getElementById("testing_frame");
+ var topTarget = frame.contentWindow.document.getElementById("topTarget");
+ topTarget.click();
+ });
+
+ // The link click should have caused a load and should not invoke the Mixed Content Blocker
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ ok (!gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"),
+ "Mixed Content Doorhanger did not appear when trying to navigate top");
+}
+
+function MixedTest1C() {
+ ContentTask.spawn(gTestBrowser, null, function() {
+ Assert.equal(content.location.href, "http://example.com/",
+ "Navigating to insecure domain through target='_top' failed.")
+ }).then(MixedTestsCompleted);
+}
+
diff --git a/dom/base/test/browser_messagemanager_loadprocessscript.js b/dom/base/test/browser_messagemanager_loadprocessscript.js
new file mode 100644
index 000000000..feabfb43c
--- /dev/null
+++ b/dom/base/test/browser_messagemanager_loadprocessscript.js
@@ -0,0 +1,114 @@
+var ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ppmm.QueryInterface(Ci.nsIProcessScriptLoader);
+
+function processScript() {
+ let cpmm = Components.classes["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Components.interfaces.nsISyncMessageSender);
+ if (cpmm !== this) {
+ dump("Test failed: wrong global object\n");
+ return;
+ }
+
+ this.cpmm = cpmm;
+
+ addMessageListener("ProcessTest:Reply", function listener(msg) {
+ removeMessageListener("ProcessTest:Reply", listener);
+ sendAsyncMessage("ProcessTest:Finished");
+ });
+ sendSyncMessage("ProcessTest:Loaded");
+}
+var processScriptURL = "data:,(" + processScript.toString() + ")()";
+
+function initTestScript() {
+ 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 = Task.async(function*(mm) {
+ let { target } = yield promiseMessage(mm, "ProcessTest:Loaded");
+ target.sendAsyncMessage("ProcessTest:Reply");
+ yield 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);
+ })
+}
+
+// Test that loading a process script loads in all existing processes
+add_task(function*() {
+ let checks = [];
+ for (let i = 0; i < ppmm.childCount; i++)
+ checks.push(checkProcess(ppmm.getChildAt(i)));
+
+ ppmm.loadProcessScript(processScriptURL, false);
+ yield Promise.all(checks);
+});
+
+// Test that loading a process script loads in new processes
+add_task(function*() {
+ // This test is only relevant in e10s
+ if (!gMultiProcessBrowser)
+ return;
+
+ is(ppmm.childCount, 2, "Should be two processes at this point");
+
+ // Load something in the main process
+ gBrowser.selectedBrowser.loadURI("about:robots");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ let init = 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 (ppmm.childCount == 1) {
+ let mainMM = ppmm.getChildAt(0);
+
+ let check = checkProcess(ppmm);
+ ppmm.loadProcessScript(processScriptURL, true);
+
+ // The main process should respond
+ yield check;
+
+ check = checkProcess(ppmm);
+ // Reset the default browser to start a new child process
+ gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, true);
+ gBrowser.selectedBrowser.loadURI("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ is(ppmm.childCount, 2, "Should be back to two processes at this point");
+
+ // The new process should have responded
+ yield check;
+
+ ppmm.removeDelayedProcessScript(processScriptURL);
+
+ let childMM;
+ childMM = ppmm.getChildAt(0) == mainMM ? ppmm.getChildAt(1) : ppmm.getChildAt(0);
+
+ childMM.loadProcessScript(initTestScriptURL, false);
+ let msg = yield 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 000000000..32575d073
--- /dev/null
+++ b/dom/base/test/browser_messagemanager_targetframeloader.js
@@ -0,0 +1,31 @@
+function frameScript()
+{
+ sendSyncMessage("Test:Message");
+ sendAsyncMessage("Test:Message");
+ sendAsyncMessage("Test:Done");
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ var newTab = gBrowser.addTab("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 000000000..413e38493
--- /dev/null
+++ b/dom/base/test/browser_messagemanager_unload.js
@@ -0,0 +1,102 @@
+function frameScript()
+{
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ 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.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils).outerWindowID;
+ function onOuterWindowDestroyed(subject, topic, data) {
+ if (docShell) {
+ sendAsyncMessage("Test:Fail", "docShell is non-null");
+ }
+
+ let id = subject.QueryInterface(Components.interfaces.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", false);
+
+ sendAsyncMessage("Test:Ready");
+ }, true);
+}
+
+const EXPECTED = [
+ // Unload events on the TabChildGlobal. 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 = gBrowser.addTab("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_pagehide_on_tab_close.js b/dom/base/test/browser_pagehide_on_tab_close.js
new file mode 100644
index 000000000..0da7617e1
--- /dev/null
+++ b/dom/base/test/browser_pagehide_on_tab_close.js
@@ -0,0 +1,17 @@
+function test() {
+ waitForExplicitFinish();
+
+ var tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+
+ tab.linkedBrowser.addEventListener("load", function onload() {
+ tab.linkedBrowser.removeEventListener("load", onload);
+
+ tab.linkedBrowser.addEventListener("pagehide", function() {
+ ok(true, "got page hide event");
+ finish();
+ });
+
+ executeSoon(() => { gBrowser.removeTab(tab); });
+ }, true);
+}
diff --git a/dom/base/test/browser_state_notifications.js b/dom/base/test/browser_state_notifications.js
new file mode 100644
index 000000000..3279acb39
--- /dev/null
+++ b/dom/base/test/browser_state_notifications.js
@@ -0,0 +1,189 @@
+/* globals Components: true, Promise: true, gBrowser: true, Test: true,
+ info: true, is: true, window: true, waitForExplicitFinish: true,
+ finish: true, ok: true*/
+
+"use strict";
+
+var { interfaces: Ci, classes: Cc, utils: Cu } = Components;
+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();
+ Task.spawn(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) => {
+ const { promise, resolve, reject } = Promise.defer();
+ 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);
+ }
+ }
+ };
+ const id = setTimeout(timeout, 90000);
+ addObserver(observer, topic, false);
+ queue.push(topic, resolve);
+
+ return promise;
+};
+receive.queue = [];
+
+const openTab = uri => gBrowser.selectedTab = gBrowser.addTab(uri);
+
+const sleep = ms => {
+ const { promise, resolve } = Promise.defer();
+ setTimeout(resolve, ms);
+ return promise;
+};
+
+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(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 = yield documentInteractive;
+
+ let loadedDocument1 = yield documentLoaded;
+ is(loadedDocument1.readyState, "complete", "document is loaded");
+ is(interactiveDocument1, loadedDocument1, "interactive document is loaded");
+
+ let shownPage = yield pageShown;
+ is(interactiveDocument1, shownPage, "loaded document is shown");
+
+ // Wait until history entry is created before loading new uri.
+ yield 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);
+
+ browser1.loadURI(uri2);
+
+ let hiddenPage = yield pageHidden;
+ is(interactiveDocument1, hiddenPage, "loaded document is hidden");
+
+ let interactiveDocument2 = yield documentInteractive;
+
+ let loadedDocument2 = yield documentLoaded;
+ is(loadedDocument2.readyState, "complete", "document is loaded");
+ is(interactiveDocument2, loadedDocument2, "interactive document is loaded");
+
+ shownPage = yield 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 = yield pageHidden;
+ is(interactiveDocument2, hiddenPage, "new document is hidden");
+
+ shownPage = yield pageShown;
+ is(interactiveDocument1, shownPage, "previous document is shown");
+
+ info("load uri#3");
+
+ browser1.loadURI(uri3);
+
+ pageShown = receive("content-page-shown", isData);
+
+ let interactiveDocument3 = yield documentInteractive;
+
+ let loadedDocument3 = yield documentLoaded;
+ is(loadedDocument3.readyState, "complete", "document is loaded");
+ is(interactiveDocument3, loadedDocument3, "interactive document is loaded");
+
+ shownPage = yield 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 = yield documentInteractive;
+
+ let loadedDocument4 = yield documentLoaded;
+ is(loadedDocument4.readyState, "complete", "document is loaded");
+ is(interactiveDocument4, loadedDocument4, "interactive document is loaded");
+
+ shownPage = yield pageShown;
+ is(interactiveDocument4, shownPage, "loaded chrome document is shown");
+
+ pageHidden = receive("chrome-page-hidden");
+ gBrowser.removeTab(tab2);
+
+ hiddenPage = yield pageHidden;
+ is(interactiveDocument4, hiddenPage, "chrome document hidden");
+});
diff --git a/dom/base/test/browser_use_counters.js b/dom/base/test/browser_use_counters.js
new file mode 100644
index 000000000..68bd04f88
--- /dev/null
+++ b/dom/base/test/browser_use_counters.js
@@ -0,0 +1,305 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+
+requestLongerTimeout(2);
+
+var {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+Cu.import("resource://gre/modules/Services.jsm");
+
+const gHttpTestRoot = "http://example.com/browser/dom/base/test/";
+
+/**
+ * Enable local telemetry recording for the duration of the tests.
+ */
+var gOldContentCanRecord = false;
+var gOldParentCanRecord = false;
+add_task(function* test_initialize() {
+ let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+ gOldParentCanRecord = Telemetry.canRecordExtended
+ Telemetry.canRecordExtended = true;
+
+ // 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).
+ yield SpecialPowers.pushPrefEnv({ set: [[ "dom.ipc.processCount", 1 ]] });
+
+ gOldContentCanRecord = yield ContentTask.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(function* () {
+ // Check that use counters are incremented by SVGs loaded directly in iframes.
+ yield check_use_counter_iframe("file_use_counter_svg_getElementById.svg",
+ "SVGSVGELEMENT_GETELEMENTBYID");
+ yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg",
+ "SVGSVGELEMENT_CURRENTSCALE_getter");
+ yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg",
+ "SVGSVGELEMENT_CURRENTSCALE_setter");
+
+ // 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.
+ yield check_use_counter_iframe("file_use_counter_svg_getElementById.svg",
+ "SVGSVGELEMENT_GETELEMENTBYID", false);
+ yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg",
+ "SVGSVGELEMENT_CURRENTSCALE_getter", false);
+ yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg",
+ "SVGSVGELEMENT_CURRENTSCALE_setter", 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.
+ yield check_use_counter_img("file_use_counter_svg_getElementById.svg",
+ "PROPERTY_FILL");
+ yield check_use_counter_img("file_use_counter_svg_currentScale.svg",
+ "PROPERTY_FILL");
+
+ // Check that use counters are incremented by directly loading SVGs
+ // that reference patterns defined in another SVG file.
+ yield check_use_counter_direct("file_use_counter_svg_fill_pattern.svg",
+ "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.
+ yield check_use_counter_direct("file_use_counter_svg_fill_pattern_internal.svg",
+ "PROPERTY_FILLOPACITY");
+ // data: URLs don't correctly propagate to their referring document yet.
+ //yield check_use_counter_direct("file_use_counter_svg_fill_pattern_data.svg",
+ // "PROPERTY_FILL_OPACITY");
+});
+
+add_task(function* () {
+ let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+ Telemetry.canRecordExtended = gOldParentCanRecord;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { oldCanRecord: gOldContentCanRecord }, function (arg) {
+ Cu.import("resource://gre/modules/PromiseUtils.jsm");
+ yield new Promise(resolve => {
+ let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+ telemetry.canRecordExtended = arg.oldCanRecord;
+ resolve();
+ });
+ });
+});
+
+
+function waitForDestroyedDocuments() {
+ let deferred = promise.defer();
+ SpecialPowers.exactGC(deferred.resolve);
+ return deferred.promise;
+}
+
+function waitForPageLoad(browser) {
+ return ContentTask.spawn(browser, null, function*() {
+ Cu.import("resource://gre/modules/PromiseUtils.jsm");
+ yield new Promise(resolve => {
+ let listener = () => {
+ removeEventListener("load", listener, true);
+ resolve();
+ }
+ addEventListener("load", listener, true);
+ });
+ });
+}
+
+function grabHistogramsFromContent(use_counter_middlefix, page_before = null) {
+ let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+ let suffix = Services.appinfo.browserTabsRemoteAutostart ? "#content" : "";
+ let gather = () => [
+ telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum,
+ telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_DOCUMENT" + suffix).snapshot().sum,
+ telemetry.getHistogramById("CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum,
+ telemetry.getHistogramById("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum,
+ ];
+ return BrowserTestUtils.waitForCondition(() => {
+ return page_before != telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum;
+ }).then(gather, gather);
+}
+
+var check_use_counter_iframe = Task.async(function* (file, use_counter_middlefix, check_documents=true) {
+ info("checking " + file + " with histogram " + use_counter_middlefix);
+
+ let newTab = gBrowser.addTab( "about:blank");
+ gBrowser.selectedTab = newTab;
+ newTab.linkedBrowser.stop();
+
+ // Hold on to the current values of the telemetry histograms we're
+ // interested in.
+ let [histogram_page_before, histogram_document_before,
+ histogram_docs_before, histogram_toplevel_docs_before] =
+ yield grabHistogramsFromContent(use_counter_middlefix);
+
+ gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html");
+ yield waitForPageLoad(gBrowser.selectedBrowser);
+
+ // Inject our desired file into the iframe of the newly-loaded page.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) {
+ Cu.import("resource://gre/modules/PromiseUtils.jsm");
+ let deferred = PromiseUtils.defer();
+
+ let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+
+ let iframe = content.document.getElementById('content');
+ iframe.src = opts.file;
+ let listener = (event) => {
+ event.target.removeEventListener("load", listener, true);
+
+ // We flush the main document first, then the iframe's document to
+ // ensure any propagation that might happen from content->parent should
+ // have already happened when counters are reported to telemetry.
+ wu.forceUseCounterFlush(content.document);
+ wu.forceUseCounterFlush(iframe.contentDocument);
+
+ deferred.resolve();
+ };
+ iframe.addEventListener("load", listener, true);
+
+ return deferred.promise;
+ });
+
+ // Tear down the page.
+ gBrowser.removeTab(newTab);
+
+ // The histograms only get recorded when the document actually gets
+ // destroyed, which might not have happened yet due to GC/CC effects, etc.
+ // Try to force document destruction.
+ yield waitForDestroyedDocuments();
+
+ // Grab histograms again and compare.
+ let [histogram_page_after, histogram_document_after,
+ histogram_docs_after, histogram_toplevel_docs_after] =
+ yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
+
+ is(histogram_page_after, histogram_page_before + 1,
+ "page counts for " + use_counter_middlefix + " after are correct");
+ ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
+ "top level document counts are correct");
+ if (check_documents) {
+ is(histogram_document_after, histogram_document_before + 1,
+ "document counts for " + use_counter_middlefix + " after are correct");
+ }
+});
+
+var check_use_counter_img = Task.async(function* (file, use_counter_middlefix) {
+ info("checking " + file + " as image with histogram " + use_counter_middlefix);
+
+ let newTab = gBrowser.addTab("about:blank");
+ gBrowser.selectedTab = newTab;
+ newTab.linkedBrowser.stop();
+
+ // Hold on to the current values of the telemetry histograms we're
+ // interested in.
+ let [histogram_page_before, histogram_document_before,
+ histogram_docs_before, histogram_toplevel_docs_before] =
+ yield grabHistogramsFromContent(use_counter_middlefix);
+
+ gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html");
+ yield waitForPageLoad(gBrowser.selectedBrowser);
+
+ // Inject our desired file into the img of the newly-loaded page.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) {
+ Cu.import("resource://gre/modules/PromiseUtils.jsm");
+ let deferred = PromiseUtils.defer();
+
+ let img = content.document.getElementById('display');
+ img.src = opts.file;
+ let listener = (event) => {
+ img.removeEventListener("load", listener, true);
+
+ // Flush for the image. It matters what order we do these in, so that
+ // the image can propagate its use counters to the document prior to the
+ // document reporting its use counters.
+ let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ wu.forceUseCounterFlush(img);
+
+ // Flush for the main window.
+ wu.forceUseCounterFlush(content.document);
+
+ deferred.resolve();
+ };
+ img.addEventListener("load", listener, true);
+
+ return deferred.promise;
+ });
+
+ // Tear down the page.
+ gBrowser.removeTab(newTab);
+
+ // The histograms only get recorded when the document actually gets
+ // destroyed, which might not have happened yet due to GC/CC effects, etc.
+ // Try to force document destruction.
+ yield waitForDestroyedDocuments();
+
+ // Grab histograms again and compare.
+ let [histogram_page_after, histogram_document_after,
+ histogram_docs_after, histogram_toplevel_docs_after] =
+ yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
+ is(histogram_page_after, histogram_page_before + 1,
+ "page counts for " + use_counter_middlefix + " after are correct");
+ is(histogram_document_after, histogram_document_before + 1,
+ "document counts for " + use_counter_middlefix + " after are correct");
+ ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
+ "top level document counts are correct");
+ // 2 documents: one for the outer html page containing the <img> element, and
+ // one for the SVG image itself.
+ ok(histogram_docs_after >= histogram_docs_before + 2,
+ "document counts are correct");
+});
+
+var check_use_counter_direct = Task.async(function* (file, use_counter_middlefix, xfail=false) {
+ info("checking " + file + " with histogram " + use_counter_middlefix);
+
+ let newTab = gBrowser.addTab( "about:blank");
+ gBrowser.selectedTab = newTab;
+ newTab.linkedBrowser.stop();
+
+ // Hold on to the current values of the telemetry histograms we're
+ // interested in.
+ let [histogram_page_before, histogram_document_before,
+ histogram_docs_before, histogram_toplevel_docs_before] =
+ yield grabHistogramsFromContent(use_counter_middlefix);
+
+ gBrowser.selectedBrowser.loadURI(gHttpTestRoot + file);
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ Cu.import("resource://gre/modules/PromiseUtils.jsm");
+ yield new Promise(resolve => {
+ let listener = () => {
+ removeEventListener("load", listener, true);
+
+ let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ wu.forceUseCounterFlush(content.document);
+
+ setTimeout(resolve, 0);
+ }
+ addEventListener("load", listener, true);
+ });
+ });
+
+ // Tear down the page.
+ gBrowser.removeTab(newTab);
+
+ // The histograms only get recorded when the document actually gets
+ // destroyed, which might not have happened yet due to GC/CC effects, etc.
+ // Try to force document destruction.
+ yield waitForDestroyedDocuments();
+
+ // Grab histograms again and compare.
+ let [histogram_page_after, histogram_document_after,
+ histogram_docs_after, histogram_toplevel_docs_after] =
+ yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before);
+ if (!xfail) {
+ is(histogram_page_after, histogram_page_before + 1,
+ "page counts for " + use_counter_middlefix + " after are correct");
+ is(histogram_document_after, histogram_document_before + 1,
+ "document counts for " + use_counter_middlefix + " after are correct");
+ }
+ ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1,
+ "top level document counts are correct");
+ ok(histogram_docs_after >= histogram_docs_before + 1,
+ "document counts are correct");
+});
diff --git a/dom/base/test/bug282547.sjs b/dom/base/test/bug282547.sjs
new file mode 100644
index 000000000..8476a6172
--- /dev/null
+++ b/dom/base/test/bug282547.sjs
@@ -0,0 +1,9 @@
+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 000000000..af497f590
--- /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 000000000..150f5ea6d
--- /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 000000000..935f00afd
--- /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 000000000..5e3b4936a
--- /dev/null
+++ b/dom/base/test/bug403852_fileOpener.js
@@ -0,0 +1,17 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+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 () {
+ sendAsyncMessage("file.opened", {
+ file: File.createFromNsIFile(testFile),
+ mtime: testFile.lastModifiedTime,
+ fileWithDate: File.createFromNsIFile(testFile, { lastModified: 123 }),
+ fileDate: 123,
+ });
+});
diff --git a/dom/base/test/bug419132.html b/dom/base/test/bug419132.html
new file mode 100644
index 000000000..ab2934c2d
--- /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 000000000..331f31d96
--- /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 000000000..3ef8c656a
--- /dev/null
+++ b/dom/base/test/bug435425.sjs
@@ -0,0 +1,24 @@
+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 000000000..e8c8f2aa4
--- /dev/null
+++ b/dom/base/test/bug435425_redirect.sjs
@@ -0,0 +1,6 @@
+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 000000000..e69de29bb
--- /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 000000000..e69de29bb
--- /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 000000000..b98588d40
--- /dev/null
+++ b/dom/base/test/bug444546.sjs
@@ -0,0 +1,20 @@
+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 000000000..38098585e
--- /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 000000000..d014df2ce
--- /dev/null
+++ b/dom/base/test/bug457746.sjs
@@ -0,0 +1,11 @@
+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, body.length);
+}
+
diff --git a/dom/base/test/bug461735-post-redirect.js b/dom/base/test/bug461735-post-redirect.js
new file mode 100644
index 000000000..bae1963d3
--- /dev/null
+++ b/dom/base/test/bug461735-post-redirect.js
@@ -0,0 +1,3 @@
+var a = 0;
+var b = 0;
+c(); \ No newline at end of file
diff --git a/dom/base/test/bug461735-redirect1.sjs b/dom/base/test/bug461735-redirect1.sjs
new file mode 100644
index 000000000..a31eb066a
--- /dev/null
+++ b/dom/base/test/bug461735-redirect1.sjs
@@ -0,0 +1,4 @@
+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 000000000..cfd929978
--- /dev/null
+++ b/dom/base/test/bug461735-redirect2.sjs
@@ -0,0 +1,4 @@
+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 000000000..0d7c089b7
--- /dev/null
+++ b/dom/base/test/bug466080.sjs
@@ -0,0 +1,17 @@
+
+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("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 000000000..e69de29bb
--- /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 000000000..69ce7c227
--- /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 000000000..e64863616
--- /dev/null
+++ b/dom/base/test/bug475156.sjs
@@ -0,0 +1,27 @@
+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 000000000..daadb5ab2
--- /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 000000000..3c147a135
--- /dev/null
+++ b/dom/base/test/bug540854.sjs
@@ -0,0 +1,19 @@
+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 000000000..edb785491
--- /dev/null
+++ b/dom/base/test/bug578096LoadChromeScript.js
@@ -0,0 +1,16 @@
+var file;
+Components.utils.importGlobalProperties(["File"]);
+
+addMessageListener("file.create", function (message) {
+ file = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("TmpD", Components.interfaces.nsIFile);
+ file.append("foo.txt");
+ file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ sendAsyncMessage("file.created", File.createFromNsIFile(file));
+});
+
+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 000000000..9ce788da7
--- /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 000000000..3b4f8331b
--- /dev/null
+++ b/dom/base/test/bug638112.sjs
@@ -0,0 +1,26 @@
+function getInputStream(path)
+{
+ var file = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsILocalFile);
+ 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 000000000..b139b2fdf
--- /dev/null
+++ b/dom/base/test/bug696301-script-1.js
@@ -0,0 +1,3 @@
+var a = 0;
+var global = "ran";
+c(); \ No newline at end of file
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 000000000..cb762eff8
--- /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 000000000..b139b2fdf
--- /dev/null
+++ b/dom/base/test/bug696301-script-2.js
@@ -0,0 +1,3 @@
+var a = 0;
+var global = "ran";
+c(); \ No newline at end of file
diff --git a/dom/base/test/bug704320.sjs b/dom/base/test/bug704320.sjs
new file mode 100644
index 000000000..dff77f4b3
--- /dev/null
+++ b/dom/base/test/bug704320.sjs
@@ -0,0 +1,299 @@
+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) {
+ if (request.method == 'HEAD') {
+ // respond to a HEAD request with a 418 so that we can easily distinguish
+ // HSTS priming responses and ignore them
+ response.setStatusLine('1.1', 418, "I'm a teapot");
+ return;
+ }
+ 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 000000000..907d22493
--- /dev/null
+++ b/dom/base/test/bug704320_counter.sjs
@@ -0,0 +1,94 @@
+// 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 000000000..07f6a041d
--- /dev/null
+++ b/dom/base/test/bug819051.sjs
@@ -0,0 +1,7 @@
+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 000000000..f7e67ef6b
--- /dev/null
+++ b/dom/base/test/chrome.ini
@@ -0,0 +1,27 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ file_empty.html
+ file_bug945152.jar
+ file_bug945152_worker.js
+ file_bug1008126_worker.js
+ mozbrowser_api_utils.js
+
+[test_anonymousContent_xul_window.xul]
+[test_bug715041.xul]
+[test_bug715041_removal.xul]
+[test_bug945152.html]
+[test_bug1008126.html]
+[test_bug1016960.html]
+[test_copypaste.xul]
+subsuite = clipboard
+[test_domrequesthelper.xul]
+[test_messagemanager_principal.html]
+[test_messagemanager_send_principal.html]
+skip-if = buildapp == 'mulet'
+[test_mozbrowser_apis_allowed.html]
+[test_navigator_resolve_identity_xrays.xul]
+support-files = file_navigator_resolve_identity_xrays.xul
+[test_sandboxed_blob_uri.html]
+[test_sendQueryContentAndSelectionSetEvent.html]
+[test_websocket_frame.html]
diff --git a/dom/base/test/chrome/blockNoPlugins.xml b/dom/base/test/chrome/blockNoPlugins.xml
new file mode 100644
index 000000000..e4e191b37
--- /dev/null
+++ b/dom/base/test/chrome/blockNoPlugins.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310001">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ </pluginItems>
+</blocklist>
diff --git a/dom/base/test/chrome/blockPluginHard.xml b/dom/base/test/chrome/blockPluginHard.xml
new file mode 100644
index 000000000..24eb5bc6f
--- /dev/null
+++ b/dom/base/test/chrome/blockPluginHard.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="2"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/dom/base/test/chrome/bug418986-1.js b/dom/base/test/chrome/bug418986-1.js
new file mode 100644
index 000000000..918d90775
--- /dev/null
+++ b/dom/base/test/chrome/bug418986-1.js
@@ -0,0 +1,73 @@
+// The main test function.
+var test = function (isContent) {
+ SimpleTest.waitForExplicitFinish();
+
+ 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) {
+ is(eval(a), eval(b), a + " should be equal to " + b);
+ };
+
+ // Returns generator object that iterates through pref values.
+ let prefVals = (for (prefVal of [false, true]) prefVal);
+
+ // 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 (!item.startsWith("moz")) {
+ checkPair("window." + item, "chromeWindow." + item);
+ }
+ }
+ });
+ if (!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 000000000..850b320a2
--- /dev/null
+++ b/dom/base/test/chrome/bug421622-referer.sjs
@@ -0,0 +1,8 @@
+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 000000000..30d4f8a03
--- /dev/null
+++ b/dom/base/test/chrome/bug884693.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response)
+{
+ let [status, statusText, body] = request.queryString.split("&");
+ 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 000000000..765bbd2df
--- /dev/null
+++ b/dom/base/test/chrome/chrome.ini
@@ -0,0 +1,76 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ blockNoPlugins.xml
+ blockPluginHard.xml
+ bug418986-1.js
+ cpows_child.js
+ cpows_parent.xul
+ file_bug549682.xul
+ file_bug616841.xul
+ file_bug816340.xul
+ file_bug990812-1.xul
+ file_bug990812-2.xul
+ file_bug990812-3.xul
+ file_bug990812-4.xul
+ file_bug990812-5.xul
+ file_bug1139964.xul
+ file_bug1209621.xul
+ fileconstructor_file.png
+ frame_bug814638.xul
+ frame_registerElement_content.html
+ registerElement_ep.js
+ host_bug814638.xul
+ window_nsITextInputProcessor.xul
+ title_window.xul
+ window_swapFrameLoaders.xul
+ window_groupedSHistory.xul
+
+[test_bug120684.xul]
+[test_bug206691.xul]
+[test_bug289714.xul]
+[test_bug339494.xul]
+[test_bug357450.xul]
+support-files = ../file_bug357450.js
+[test_bug380418.html]
+[test_bug380418.html^headers^]
+[test_bug383430.html]
+[test_bug418986-1.xul]
+[test_bug421622.xul]
+[test_bug429785.xul]
+[test_bug430050.xul]
+[test_bug467123.xul]
+[test_bug549682.xul]
+[test_bug571390.xul]
+[test_bug1098074_throw_from_ReceiveMessage.xul]
+[test_bug616841.xul]
+[test_bug635835.xul]
+[test_bug682305.html]
+[test_bug683852.xul]
+[test_bug752226-3.xul]
+[test_bug752226-4.xul]
+[test_bug765993.html]
+[test_bug780199.xul]
+[test_bug780529.xul]
+[test_bug800386.xul]
+[test_bug814638.xul]
+[test_bug816340.xul]
+[test_bug884693.xul]
+[test_bug914381.html]
+[test_bug990812.xul]
+[test_bug1063837.xul]
+[test_bug1139964.xul]
+[test_bug1209621.xul]
+[test_cpows.xul]
+[test_registerElement_content.xul]
+[test_registerElement_ep.xul]
+[test_domparsing.xul]
+[test_fileconstructor.xul]
+[test_fileconstructor_tempfile.xul]
+[test_nsITextInputProcessor.xul]
+[test_range_getClientRectsAndTexts.html]
+[test_title.xul]
+[test_windowroot.xul]
+[test_swapFrameLoaders.xul]
+[test_groupedSHistory.xul]
+[test_bug1339722.html]
diff --git a/dom/base/test/chrome/cpows_child.js b/dom/base/test/chrome/cpows_child.js
new file mode 100644
index 000000000..28ae4d1a7
--- /dev/null
+++ b/dom/base/test/chrome/cpows_child.js
@@ -0,0 +1,382 @@
+dump('loaded child cpow test\n');
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+(function start() {
+ [is_remote] = sendRpcMessage("cpows:is_remote");
+
+ var tests = [
+ parent_test,
+ error_reporting_test,
+ dom_test,
+ xray_test,
+ symbol_test,
+ compartment_test,
+ regexp_test,
+ postmessage_test,
+ sync_test,
+ async_test,
+ rpc_test,
+ lifetime_test,
+ cancel_test,
+ cancel_test2,
+ dead_test,
+ unsafe_test,
+ ];
+
+ function go() {
+ if (tests.length == 0) {
+ sendRpcMessage("cpows:done", {});
+ return;
+ }
+
+ var test = tests[0];
+ tests.shift();
+ test(function() {
+ go();
+ });
+ }
+
+ go();
+})();
+
+function ok(condition, message) {
+ dump('condition: ' + condition + ', ' + message + '\n');
+ if (!condition) {
+ sendAsyncMessage("cpows:fail", { message: message });
+ throw 'failed check: ' + message;
+ }
+}
+
+var sync_obj;
+var async_obj;
+
+function make_object()
+{
+ let o = { };
+ o.i = 5;
+ o.b = true;
+ o.s = "hello";
+ o.x = { i: 10 };
+ o.f = function () { return 99; };
+ o.ctor = function() { this.a = 3; }
+
+ // Doing anything with this Proxy will throw.
+ var throwing = new Proxy({}, new Proxy({}, {
+ get: function (trap) { throw trap; }
+ }));
+
+ let array = [1, 2, 3];
+
+ let for_json = { "n": 3, "a": array, "s": "hello", o: { "x": 10 } };
+
+ let proto = { data: 42 };
+ let with_proto = Object.create(proto);
+
+ let with_null_proto = Object.create(null);
+
+ content.document.title = "Hello, Kitty";
+ return { "data": o,
+ "throwing": throwing,
+ "document": content.document,
+ "array": array,
+ "for_json": for_json,
+ "with_proto": with_proto,
+ "with_null_proto": with_null_proto
+ };
+}
+
+function make_json()
+{
+ return { check: "ok" };
+}
+
+function parent_test(finish)
+{
+ function f(check_func) {
+ // Make sure this doesn't crash.
+ let array = new Uint32Array(10);
+ content.crypto.getRandomValues(array);
+
+ let result = check_func(10);
+ ok(result == 20, "calling function in parent worked");
+ return result;
+ }
+
+ addMessageListener("cpows:from_parent", (msg) => {
+ let obj = msg.objects.obj;
+ ok(obj.a == 1, "correct value from parent");
+
+ // Test that a CPOW reference to a function in the chrome process
+ // is callable from unprivileged content. Greasemonkey uses this
+ // functionality.
+ let func = msg.objects.func;
+ let sb = Cu.Sandbox('http://www.example.com', {});
+ sb.func = func;
+ ok(sb.eval('func()') == 101, "can call parent's function in child");
+
+ finish();
+ });
+ sendRpcMessage("cpows:parent_test", {}, {func: f});
+}
+
+function error_reporting_test(finish) {
+ sendRpcMessage("cpows:error_reporting_test", {}, {});
+ finish();
+}
+
+function dom_test(finish)
+{
+ let element = content.document.createElement("div");
+ element.id = "it_works";
+ content.document.body.appendChild(element);
+
+ sendRpcMessage("cpows:dom_test", {}, {element: element});
+ Components.utils.schedulePreciseGC(function() {
+ sendRpcMessage("cpows:dom_test_after_gc");
+ finish();
+ });
+}
+
+function xray_test(finish)
+{
+ let element = content.document.createElement("div");
+ element.wrappedJSObject.foo = "hello";
+
+ sendRpcMessage("cpows:xray_test", {}, {element: element});
+ finish();
+}
+
+function symbol_test(finish)
+{
+ let iterator = Symbol.iterator;
+ let named = Symbol.for("cpow-test");
+
+ let object = {
+ [iterator]: iterator,
+ [named]: named,
+ };
+ let test = ['a'];
+ sendRpcMessage("cpows:symbol_test", {}, {object: object, test: test});
+ finish();
+}
+
+// Parent->Child references should go X->parent.privilegedJunkScope->child.privilegedJunkScope->Y
+// Child->Parent references should go X->child.privilegedJunkScope->parent.unprivilegedJunkScope->Y
+function compartment_test(finish)
+{
+ // This test primarily checks various compartment invariants for CPOWs, and
+ // doesn't make sense to run in-process.
+ if (!is_remote) {
+ finish();
+ return;
+ }
+
+ let sb = Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ['XMLHttpRequest'] });
+ sb.eval('function getUnprivilegedObject() { var xhr = new XMLHttpRequest(); xhr.expando = 42; return xhr; }');
+ function testParentObject(obj) {
+ let results = [];
+ function is(a, b, msg) { results.push({ result: a === b ? "PASS" : "FAIL", message: msg }) };
+ function ok(x, msg) { results.push({ result: x ? "PASS" : "FAIL", message: msg }) };
+
+ let cpowLocation = Cu.getCompartmentLocation(obj);
+ ok(/Privileged Junk/.test(cpowLocation),
+ "child->parent CPOWs should live in the privileged junk scope: " + cpowLocation);
+ is(obj(), 42, "child->parent CPOW is invokable");
+ try {
+ obj.expando;
+ ok(false, "child->parent CPOW cannot access properties");
+ } catch (e) {
+ ok(true, "child->parent CPOW cannot access properties");
+ }
+
+ return results;
+ }
+ sendRpcMessage("cpows:compartment_test", {}, { getUnprivilegedObject: sb.getUnprivilegedObject,
+ testParentObject: testParentObject });
+ finish();
+}
+
+function regexp_test(finish)
+{
+ sendRpcMessage("cpows:regexp_test", {}, { regexp: /myRegExp/g });
+ finish();
+}
+
+function postmessage_test(finish)
+{
+ sendRpcMessage("cpows:postmessage_test", {}, { win: content.window });
+ finish();
+}
+
+function sync_test(finish)
+{
+ dump('beginning cpow sync test\n');
+ sync_obj = make_object();
+ sendRpcMessage("cpows:sync",
+ make_json(),
+ make_object());
+ finish();
+}
+
+function async_test(finish)
+{
+ dump('beginning cpow async test\n');
+ async_obj = make_object();
+ sendAsyncMessage("cpows:async",
+ make_json(),
+ async_obj);
+
+ addMessageListener("cpows:async_done", finish);
+}
+
+var rpc_obj;
+
+function rpc_test(finish)
+{
+ dump('beginning cpow rpc test\n');
+ rpc_obj = make_object();
+ rpc_obj.data.reenter = function () {
+ sendRpcMessage("cpows:reenter", { }, { data: { valid: true } });
+ return "ok";
+ }
+ sendRpcMessage("cpows:rpc",
+ make_json(),
+ rpc_obj);
+ finish();
+}
+
+function lifetime_test(finish)
+{
+ if (!is_remote) {
+ // Only run this test when running out-of-process. Otherwise it
+ // will fail, since local CPOWs don't follow the same ownership
+ // rules.
+ finish();
+ return;
+ }
+
+ dump("beginning lifetime test\n");
+ var obj = {"will_die": {"f": 1}};
+ let [result] = sendRpcMessage("cpows:lifetime_test_1", {}, {obj: obj});
+ ok(result == 10, "got sync result");
+ ok(obj.wont_die.f == 2, "got reverse CPOW");
+ obj.will_die = null;
+ Components.utils.schedulePreciseGC(function() {
+ addMessageListener("cpows:lifetime_test_3", (msg) => {
+ ok(obj.wont_die.f == 2, "reverse CPOW still works");
+ finish();
+ });
+ sendRpcMessage("cpows:lifetime_test_2");
+ });
+}
+
+function cancel_test(finish)
+{
+ if (!is_remote) {
+ // No point in doing this in single-process mode.
+ finish();
+ return;
+ }
+
+ let fin1 = false, fin2 = false;
+
+ // CPOW from the parent runs f. When it sends a sync message, the
+ // CPOW is canceled. The parent starts running again immediately
+ // after the CPOW is canceled; f also continues running.
+ function f() {
+ let res = sendSyncMessage("cpows:cancel_sync_message");
+ ok(res[0] == 12, "cancel_sync_message result correct");
+ fin1 = true;
+ if (fin1 && fin2) finish();
+ }
+
+ sendAsyncMessage("cpows:cancel_test", null, {f: f});
+ addMessageListener("cpows:cancel_test_done", msg => {
+ fin2 = true;
+ if (fin1 && fin2) finish();
+ });
+}
+
+function cancel_test2(finish)
+{
+ if (!is_remote) {
+ // No point in doing this in single-process mode.
+ finish();
+ return;
+ }
+
+ let fin1 = false, fin2 = false;
+
+ // CPOW from the parent runs f. When it does a sync XHR, the
+ // CPOW is canceled. The parent starts running again immediately
+ // after the CPOW is canceled; f also continues running.
+ function f() {
+ let req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Components.interfaces.nsIXMLHttpRequest);
+ let fin = false;
+ let reqListener = () => {
+ if (req.readyState != req.DONE) {
+ return;
+ }
+ ok(req.status == 200, "XHR succeeded");
+ fin = true;
+ };
+
+ req.onload = reqListener;
+ req.open("get", "http://example.com", false);
+ req.send(null);
+
+ ok(fin == true, "XHR happened");
+
+ fin1 = true;
+ if (fin1 && fin2) finish();
+ }
+
+ sendAsyncMessage("cpows:cancel_test2", null, {f: f});
+ addMessageListener("cpows:cancel_test2_done", msg => {
+ fin2 = true;
+ if (fin1 && fin2) finish();
+ });
+}
+
+function unsafe_test(finish)
+{
+ if (!is_remote) {
+ // Only run this test when running out-of-process.
+ finish();
+ return;
+ }
+
+ function f() {}
+
+ sendAsyncMessage("cpows:unsafe", null, {f});
+ addMessageListener("cpows:unsafe_done", msg => {
+ sendRpcMessage("cpows:safe", null, {f});
+ addMessageListener("cpows:safe_done", finish);
+ });
+}
+
+function dead_test(finish)
+{
+ if (!is_remote) {
+ // Only run this test when running out-of-process.
+ finish();
+ return;
+ }
+
+ let gcTrigger = function() {
+ // Force the GC to dead-ify the thing.
+ content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .garbageCollect();
+ }
+
+ {
+ let thing = { value: "Gonna croak" };
+ sendAsyncMessage("cpows:dead", null, { thing, gcTrigger });
+ }
+
+ addMessageListener("cpows:dead_done", finish);
+}
diff --git a/dom/base/test/chrome/cpows_parent.xul b/dom/base/test/chrome/cpows_parent.xul
new file mode 100644
index 000000000..f633f0a79
--- /dev/null
+++ b/dom/base/test/chrome/cpows_parent.xul
@@ -0,0 +1,493 @@
+<?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="MessageManager CPOW tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start()">
+
+ <!-- test results are displayed in the html:body -->
+ <label value="CPOWs"/>
+
+ <script type="application/javascript"><![CDATA[
+ var test_state = "remote";
+ var test_node = null;
+ var reentered = false;
+ var savedMM = null;
+ const Cu = Components.utils;
+
+ function info(message) {
+ return opener.wrappedJSObject.info(message);
+ }
+
+ function ok(condition, message) {
+ return opener.wrappedJSObject.ok(condition, message);
+ }
+
+ function is(v1, v2, message) {
+ return opener.wrappedJSObject.is(v1, v2, message);
+ }
+
+ function todo_is(v1, v2, message) {
+ return opener.wrappedJSObject.todo_is(v1, v2, message);
+ }
+
+ // Make sure that an error in this file actually causes the test to fail.
+ var gReceivedErrorProbe = false;
+ window.onerror = function (msg, url, line) {
+ if (/Test Error Probe/.test(msg)) {
+ gReceivedErrorProbe = true;
+ return;
+ }
+ ok(false, "Error while executing: \n" + msg + "\n" + url + ":" + line);
+ };
+
+ function testCpowMessage(message) {
+ ok(message.json.check == "ok", "correct json");
+
+ ok(!Components.utils.isCrossProcessWrapper(message.json), "not everything is a CPOW");
+
+ let data = message.objects.data;
+ let document = message.objects.document;
+ if (test_state == "remote") {
+ ok(Components.utils.isCrossProcessWrapper(data), "got a CPOW");
+ ok(Components.utils.isCrossProcessWrapper(document), "got a CPOW");
+ }
+ ok(data.i === 5, "integer property");
+ ok(data.b === true, "boolean property");
+ ok(data.s === "hello", "string property");
+ ok(data.x.i === 10, "nested property");
+ ok(data.f() === 99, "function call");
+ is(Object.getOwnPropertyDescriptor(data, "doesn't exist"), undefined,
+ "getOwnPropertyDescriptor returns undefined for non-existant properties");
+ ok(Object.getOwnPropertyDescriptor(data, "i").value, 5,
+ "getOwnPropertyDescriptor.value works");
+ let obj = new data.ctor();
+ ok(obj.a === 3, "constructor call");
+ is(document.title, "Hello, Kitty", "document node");
+ is(typeof document.cookie, "string", "can get document.cookie");
+ is(typeof document.defaultView.navigator.userAgent, "string", "can get navigator.userAgent");
+
+ // Don't crash.
+ document.defaultView.screen;
+
+ data.i = 6;
+ data.b = false;
+ data.s = "bye";
+ data.x = null;
+ ok(data.i === 6, "integer property");
+ ok(data.b === false, "boolean property");
+ ok(data.s === "bye", "string property");
+ ok(data.x === null, "nested property");
+
+ let throwing = message.objects.throwing;
+ // Based on the table on:
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
+ let tests = [
+ () => Object.getOwnPropertyDescriptor(throwing, 'test'),
+ () => Object.getOwnPropertyNames(throwing),
+ () => Object.defineProperty(throwing, 'test', {value: 1}),
+ () => delete throwing.test,
+ () => "test" in throwing,
+ () => Object.prototype.hasOwnProperty.call(throwing, 'test'),
+ () => throwing.test,
+ () => { throwing.test = 1 },
+ // () => { for (let prop in throwing) {} }, Bug 783829
+ () => { for (let prop of throwing) {} },
+ () => Object.keys(throwing),
+ () => Function.prototype.call.call(throwing),
+ () => new throwing,
+ () => Object.preventExtensions(throwing),
+ () => Object.freeze(throwing),
+ () => Object.seal(throwing),
+ ]
+
+ for (let test of tests) {
+ let threw = false;
+ try {
+ test()
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "proxy operation threw exception");
+ }
+
+ let array = message.objects.array;
+ let i = 1;
+ for (let elt of array) {
+ ok(elt === i, "correct element found");
+ i++;
+ }
+ ok(i === 4, "array has correct length");
+
+ let j = message.objects.for_json;
+ let str = JSON.stringify(j);
+ let j2 = JSON.parse(str);
+ ok(j2.n === 3, "JSON integer property");
+ ok(j2.a[0] === 1, "JSON array index");
+ ok(j2.a[1] === 2, "JSON array index");
+ ok(j2.a[2] === 3, "JSON array index");
+ ok(j2.s === "hello", "JSON string property");
+ ok(j2.o.x === 10, "JSON object property");
+
+ let with_proto = message.objects.with_proto;
+ let proto = Object.getPrototypeOf(with_proto);
+ ok(proto.data == 42, "Object.getPrototypeOf works on CPOW");
+
+ let with_null_proto = message.objects.with_null_proto;
+ proto = Object.getPrototypeOf(with_null_proto);
+ ok(proto === null, "Object.getPrototypeOf works on CPOW (null proto)");
+ }
+
+ function recvAsyncMessage(message) {
+ testCpowMessage(message);
+ savedMM.sendAsyncMessage("cpows:async_done");
+ }
+
+ function recvSyncMessage(message) {
+ testCpowMessage(message);
+ }
+
+ function recvRpcMessage(message) {
+ ok(message.json.check == "ok", "correct json");
+
+ let data = message.objects.data;
+
+ // Sanity check.
+ ok(data.i === 5, "integer property");
+
+ // Check that we re-enter.
+ reentered = false;
+ let result = data.reenter();
+ ok(reentered, "re-entered rpc");
+ ok(result == "ok", "got correct result");
+ }
+
+ function recvReenterMessage(message) {
+ ok(message.objects.data.valid === true, "cpows work");
+ reentered = true;
+ }
+
+ function recvNestedSyncMessage(message) {
+ message.objects.data.reenter();
+ }
+
+ function recvReenterSyncMessage(message) {
+ ok(false, "should not have received re-entered sync message");
+ }
+
+ function recvFailMessage(message) {
+ ok(false, message.json.message);
+ }
+
+ function recvDoneMessage(message) {
+ if (test_state == "remote") {
+ test_node.parentNode.removeChild(test_node);
+ run_tests("inprocess");
+ return;
+ }
+
+ finish();
+ }
+
+ function recvParentTest(message) {
+ let func = message.objects.func;
+ let result = func(n => 2*n);
+ ok(result == 20, "result == 20");
+ function f() {
+ return 101;
+ }
+ let obj = {a:1, __exposedProps__: {"a": "r"}};
+ savedMM.sendAsyncMessage("cpows:from_parent", {}, {obj: obj, func: f});
+ }
+
+ // Make sure errors in this file actually hit window.onerror.
+ function recvErrorReportingTest(message) {
+ throw "Test Error Probe";
+ }
+
+ let savedElement = null;
+ function recvDomTest(message) {
+ savedElement = message.objects.element;
+
+ is(savedElement.QueryInterface(Components.interfaces.nsISupports), savedElement,
+ "QI to nsISupports works");
+ is(savedElement.QueryInterface(Components.interfaces.nsIDOMNode), savedElement,
+ "QI to a random (implemented) interface works");
+
+ function testNoInterface(savedElement, i) {
+ try {
+ savedElement.QueryInterface(i);
+ ok(false, "should have thrown an exception");
+ } catch (e) {
+ is(e.result, Components.results.NS_ERROR_NO_INTERFACE, "threw the right exception");
+ }
+ }
+
+ testNoInterface(savedElement, Components.interfaces.nsIDOMAttr);
+ testNoInterface(savedElement, Components.interfaces.nsIClassInfo);
+
+ // Test to ensure that we don't pass CPOWs to C++-implemented interfaces.
+ // See bug 1072980.
+ if (test_state == "remote") {
+ // This doesn't work because we intercept toString and QueryInterface specially
+ // and don't cache the function pointer.
+ // See bug 1140636.
+ todo_is(savedElement.toString, savedElement.toString, "toString identity works");
+ todo_is(savedElement.QueryInterface, savedElement.QueryInterface, "toString identity works");
+
+ is(Object.prototype.toString.call(savedElement), "[object HTMLDivElement]",
+ "prove that this works (and doesn't leak)");
+
+ is(Object.prototype.toString.call(savedElement), "[object HTMLDivElement]",
+ "prove that this works twice (since we cache it and doesn't leak)");
+
+ // This does work because we create a CPOW for isEqualNode that stays
+ // alive as long as we have a reference to the first CPOW (so as long
+ // as it's detectable).
+ is(savedElement.isEqualNode, savedElement.isEqualNode, "webidl function identity works");
+
+ let walker = Components.classes["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(Components.interfaces.inIDeepTreeWalker);
+ const SHOW_ELEMENT = Components.interfaces.nsIDOMNodeFilter.SHOW_ELEMENT;
+ walker.showAnonymousContent = true;
+ walker.showSubDocuments = false;
+
+ try {
+ walker.init(savedElement, SHOW_ELEMENT);
+ ok(false, "expected exception passing CPOW to C++");
+ } catch (e) {
+ is(e.result, Components.results.NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE,
+ "got exception when passing CPOW to C++");
+ }
+ }
+ }
+
+ function recvDomTestAfterGC(message) {
+ let id;
+ try {
+ id = savedElement.id;
+ } catch (e) {
+ ok(false, "Got exception using DOM element");
+ }
+ is(id, "it_works", "DOM element has expected ID");
+ }
+
+ function recvXrayTest(message) {
+ let element = message.objects.element;
+ is(element.foo, undefined, "DOM element does not expose content properties");
+ }
+
+ function recvSymbolTest(message) {
+ let object = message.objects.object;
+ is(object[Symbol.iterator], Symbol.iterator, "Should use Symbol.iterator");
+ is(Symbol.keyFor(object[Symbol.for("cpow-test")]), "cpow-test", "Symbols aren't registered correctly");
+ let symbols = Object.getOwnPropertySymbols(object);
+ is(symbols.length, 2, "Object should have two symbol keys");
+ let test = undefined;
+ for (let x of message.objects.test) {
+ test = x;
+ }
+ is(test, "a", "for .. of iteration should work");
+ }
+
+ let systemGlobal = this;
+ function recvCompartmentTest(message) {
+ let getUnprivilegedObject = message.objects.getUnprivilegedObject;
+ let testParentObject = message.objects.testParentObject;
+
+ // Make sure that parent->child CPOWs live in the parent's privileged junk scope.
+ let unprivilegedObject = getUnprivilegedObject();
+ is(Cu.getGlobalForObject(getUnprivilegedObject),
+ Cu.getGlobalForObject(unprivilegedObject),
+ "all parent->child CPOWs should live in the same scope");
+ let cpowLocation = Cu.getCompartmentLocation(getUnprivilegedObject);
+ ok(/Privileged Junk/.test(cpowLocation),
+ "parent->child CPOWs should live in the privileged junk scope: " + cpowLocation);
+
+ // Make sure that parent->child CPOWs point through a privileged scope in the child
+ // (the privileged junk scope, but we don't have a good way to test for that
+ // specifically).
+ is(unprivilegedObject.expando, undefined, "parent->child references should get Xrays");
+ is(unprivilegedObject.wrappedJSObject.expando, 42, "parent->child references should get waivable Xrays");
+
+ // Send an object to the child to let it verify invariants in the other direction.
+ function passMe() { return 42; };
+ passMe.expando = 42;
+ let results = testParentObject(passMe);
+ ok(results.length > 0, "Need results");
+ results.forEach((x) => is(x.result, "PASS", x.message));
+ }
+
+ function recvRegExpTest(message) {
+ let regexp = message.objects.regexp;
+
+ // These work generically.
+ is(regexp.toString(), "/myRegExp/g", "toString works right");
+ ok(regexp.test("I like myRegExp to match"), "No false positives");
+ ok(!regexp.test("asdfsdf"), "No false positives");
+
+ // These go over regexp_toShared.
+ is("filler myRegExp filler".search(regexp), 7, "String.prototype.match works right");
+ var shell = /x/;
+ shell.compile(regexp);
+ is(regexp.toString(), shell.toString(), ".compile works right");
+ }
+
+ function recvPostMessageTest(message) {
+ let win = message.objects.win;
+ win.postMessage('nookery', '*');
+ ok(true, "Didn't crash invoking postMessage over CPOW");
+ }
+
+ let savedWilldieObj;
+ let wontDie = {f:2, __exposedProps__: {"f": "r"}};
+ function recvLifetimeTest1(message) {
+ let obj = message.objects.obj;
+ savedWilldieObj = obj.will_die;
+ ok(savedWilldieObj.f == 1, "limited-lifetime CPOW works at first");
+ obj.wont_die = wontDie;
+ obj = null;
+ return 10;
+ }
+ function recvLifetimeTest2(message) {
+ let threw = false;
+ try {
+ savedWilldieObj.f;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "limited-lifetime CPOW stopped working");
+ wontDie = null;
+ Components.utils.schedulePreciseGC(function() {
+ savedMM.sendAsyncMessage("cpows:lifetime_test_3");
+ });
+ }
+
+ function recvCancelTest(msg) {
+ let failed = false;
+ try {
+ msg.objects.f();
+ } catch (e if /cross-process JS call failed/.test(String(e))) {
+ failed = true;
+ }
+ ok(failed, "CPOW should fail due to cancelation");
+ msg.target.messageManager.sendAsyncMessage("cpows:cancel_test_done");
+ }
+
+ function recvCancelSyncMessage() {
+ return 12;
+ }
+
+ function recvCancelTest2(msg) {
+ let failed = false;
+ try {
+ msg.objects.f();
+ } catch (e if /cross-process JS call failed/.test(String(e))) {
+ failed = true;
+ }
+ ok(failed, "CPOW should fail due to cancelation");
+ msg.target.messageManager.sendAsyncMessage("cpows:cancel_test2_done");
+ }
+
+ function recvUnsafe(msg) {
+ let failed = false;
+
+ const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
+ opener.wrappedJSObject.SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
+ try {
+ msg.objects.f();
+ } catch (e if /unsafe CPOW usage forbidden/.test(String(e))) {
+ failed = true;
+ }
+ opener.wrappedJSObject.SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
+ ok(failed, "CPOW should fail when unsafe");
+ msg.target.messageManager.sendAsyncMessage("cpows:unsafe_done");
+ }
+
+ function recvSafe(msg) {
+ const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
+ opener.wrappedJSObject.SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
+ try {
+ msg.objects.f();
+ } catch (e if /unsafe CPOW usage forbidden/.test(String(e))) {
+ ok(false, "cpow failed");
+ }
+ opener.wrappedJSObject.SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
+ msg.target.messageManager.sendAsyncMessage("cpows:safe_done");
+ }
+
+ function recvDead(msg) {
+ // Need to do this in a separate turn of the event loop.
+ setTimeout(() => {
+ msg.objects.gcTrigger();
+ try {
+ msg.objects.thing.value;
+ ok(false, "Should have been a dead CPOW");
+ } catch(e if /dead CPOW/.test(String(e))) {
+ ok(true, "Got the expected dead CPOW");
+ ok(e.stack, "The exception has a stack");
+ }
+ msg.target.messageManager.sendAsyncMessage("cpows:dead_done");
+ }, 0);
+ }
+
+ function run_tests(type) {
+ info("Running tests: " + type);
+ var node = document.getElementById('cpowbrowser_' + type);
+
+ test_state = type;
+ test_node = node;
+
+ function recvIsRemote(message) {
+ return type == "remote";
+ }
+
+ var mm = node.messageManager;
+ savedMM = mm;
+ mm.addMessageListener("cpows:is_remote", recvIsRemote);
+ mm.addMessageListener("cpows:async", recvAsyncMessage);
+ mm.addMessageListener("cpows:sync", recvSyncMessage);
+ mm.addMessageListener("cpows:rpc", recvRpcMessage);
+ mm.addMessageListener("cpows:reenter", recvReenterMessage);
+ mm.addMessageListener("cpows:reenter", recvReenterMessage);
+ mm.addMessageListener("cpows:nested_sync", recvNestedSyncMessage);
+ mm.addMessageListener("cpows:reenter_sync", recvReenterSyncMessage);
+ mm.addMessageListener("cpows:done", recvDoneMessage);
+ mm.addMessageListener("cpows:fail", recvFailMessage);
+ mm.addMessageListener("cpows:parent_test", recvParentTest);
+ mm.addMessageListener("cpows:error_reporting_test", recvErrorReportingTest);
+ mm.addMessageListener("cpows:dom_test", recvDomTest);
+ mm.addMessageListener("cpows:dom_test_after_gc", recvDomTestAfterGC);
+ mm.addMessageListener("cpows:xray_test", recvXrayTest);
+ if (typeof Symbol === "function") {
+ mm.addMessageListener("cpows:symbol_test", recvSymbolTest);
+ }
+ mm.addMessageListener("cpows:compartment_test", recvCompartmentTest);
+ mm.addMessageListener("cpows:regexp_test", recvRegExpTest);
+ mm.addMessageListener("cpows:postmessage_test", recvPostMessageTest);
+ mm.addMessageListener("cpows:lifetime_test_1", recvLifetimeTest1);
+ mm.addMessageListener("cpows:lifetime_test_2", recvLifetimeTest2);
+ mm.addMessageListener("cpows:cancel_test", recvCancelTest);
+ mm.addMessageListener("cpows:cancel_sync_message", recvCancelSyncMessage);
+ mm.addMessageListener("cpows:cancel_test2", recvCancelTest2);
+ mm.addMessageListener("cpows:unsafe", recvUnsafe);
+ mm.addMessageListener("cpows:safe", recvSafe);
+ mm.addMessageListener("cpows:dead", recvDead);
+ mm.loadFrameScript("chrome://mochitests/content/chrome/dom/base/test/chrome/cpows_child.js", true);
+ }
+
+ function start() {
+ run_tests('remote');
+ }
+
+ function finish() {
+ ok(gReceivedErrorProbe, "Should have reported error probe");
+ opener.setTimeout("done()", 0);
+ window.close();
+ }
+ ]]></script>
+
+ <browser type="content" src="about:blank" id="cpowbrowser_remote" remote="true"/>
+ <browser type="content" src="about:blank" id="cpowbrowser_inprocess"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug1139964.xul b/dom/base/test/chrome/file_bug1139964.xul
new file mode 100644
index 000000000..251334301
--- /dev/null
+++ b/dom/base/test/chrome/file_bug1139964.xul
@@ -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=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[
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+
+ function ok(cond, msg) {
+ opener.wrappedJSObject.ok(cond, msg);
+ }
+
+ var msgName = "TEST:Global_has_Promise";
+
+ function mmScriptForPromiseTest() {
+ 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 not 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, "TabChildGlobal should have Promise object in the global scope!");
+ ok(m.data.hasTextEncoder, "TabChildGlobal should have TextEncoder object in the global scope!");
+ ok(!m.data.hasWindow, "TabChildGlobal should not have Window object in the global scope!");
+
+ opener.setTimeout("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.xul b/dom/base/test/chrome/file_bug1209621.xul
new file mode 100644
index 000000000..05d81c3fb
--- /dev/null
+++ b/dom/base/test/chrome/file_bug1209621.xul
@@ -0,0 +1,79 @@
+<?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[
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var Cr = Components.results;
+ var Cu = Components.utils;
+ function ok(cond, msg) {
+ opener.wrappedJSObject.ok(cond, msg);
+ }
+
+ function is(actual, expected, msg) {
+ opener.wrappedJSObject.is(actual, expected, msg);
+ }
+
+ function run() {
+ var docshell = window.getInterface(Ci.nsIDocShell);
+ 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 type=content-primary.");
+ is(treeOwner.primaryTabParent, null,
+ "There shouldn't be primaryTabParent because no remote browser has type=content-primary.");
+
+ var ip = document.getElementById("inprocess");
+ var remote = document.getElementById("remote");
+ var remote2 = document.getElementById("remote2");
+
+ ip.setAttribute("type", "content-primary");
+ ok(ip.docShell, "non-remote browser should have a DocShell.");
+ is(treeOwner.primaryContentShell, ip.docShell,
+ "content-primary browser should be the primaryContentShell.");
+ is(treeOwner.primaryTabParent, null,
+ "There shouldn't be primaryTabParent because no remote browser has type=content-primary.");
+
+ ip.setAttribute("type", "content");
+ remote.setAttribute("type", "content-primary");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has type=content-primary.");
+ var tp = remote.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.tabParent;
+ ok(tp, "Remote browsers should have a TabParent.");
+ is(treeOwner.primaryTabParent, tp,
+ "content-primary remote browser should be the primaryTabParent.");
+
+ remote.setAttribute("type", "content");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has type=content-primary.");
+ is(treeOwner.primaryTabParent, null,
+ "There shouldn't be primaryTabParent because no remote browser has type=content-primary.");
+
+ remote2.setAttribute("type", "content-primary");
+ var tp2 = remote2.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.tabParent;
+ ok(tp2, "Remote browsers should have a TabParent.");
+ is(treeOwner.primaryTabParent, tp2,
+ "content-primary remote browser should be the primaryTabParent.");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has type=content-primary.");
+
+ opener.setTimeout("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.xul b/dom/base/test/chrome/file_bug549682.xul
new file mode 100644
index 000000000..667e7ca0b
--- /dev/null
+++ b/dom/base/test/chrome/file_bug549682.xul
@@ -0,0 +1,226 @@
+<?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[
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var Cr = Components.results;
+ var Cu = Components.utils;
+
+ var didRunAsync = false;
+ var didRunLocal = false;
+
+ var global = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Components.interfaces.nsIMessageBroadcaster);
+ var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Components.interfaces.nsIMessageBroadcaster);
+ var cpm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Components.interfaces.nsISyncMessageSender);
+
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ function ok(cond, msg) {
+ opener.wrappedJSObject.ok(cond, msg);
+ }
+
+ function is(actual, expected, msg) {
+ opener.wrappedJSObject.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: XPCOMUtils.generateQI([Ci.nsIMessageListener,
+ Ci.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: XPCOMUtils.generateQI([Ci.nsIMessageListener,
+ Ci.nsISupportsWeakReference]),
+
+ receiveMessage: function(msg) {
+ ok(false, 'Should not have received a message.');
+ }
+ };
+
+ function weakDoneListener() {
+ ok(weakMessageReceived, 'Got "weak" message.');
+ finish();
+ }
+
+ function finish() {
+ opener.setTimeout("done()", 0);
+ var i = document.getElementById("ifr");
+ i.parentNode.removeChild(i); // 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 wn = document.getElementById('ifr').contentWindow
+ .getInterface(Components.interfaces.nsIWebNavigation);
+ ok(wn, "Should have webnavigation");
+ var cfmm = wn.getInterface(Components.interfaces.nsIContentFrameMessageManager);
+ 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.createElement("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.xul b/dom/base/test/chrome/file_bug616841.xul
new file mode 100644
index 000000000..5110c0c9e
--- /dev/null
+++ b/dom/base/test/chrome/file_bug616841.xul
@@ -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[
+
+ 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];
+ opener.wrappedJSObject.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() {
+ opener.setTimeout("done()", 0);
+ window.close();
+ }
+
+ ]]></script>
+
+ <browser id="browser" type="content" src="about:blank"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug816340.xul b/dom/base/test/chrome/file_bug816340.xul
new file mode 100644
index 000000000..bf980e437
--- /dev/null
+++ b/dom/base/test/chrome/file_bug816340.xul
@@ -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) {
+ opener.wrappedJSObject.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");
+ opener.setTimeout("done()", 0);
+ window.close();
+ }
+
+ ]]></script>
+
+ <browser id="browser" type="content" src="about:blank"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-1.xul b/dom/base/test/chrome/file_bug990812-1.xul
new file mode 100644
index 000000000..1e085e76a
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-1.xul
@@ -0,0 +1,64 @@
+<?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[
+
+ 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 Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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();
+ opener.wrappedJSObject.is(msg.data, next, "received test:" + next);
+
+ if (order.length == 0) {
+ opener.setTimeout("next()");
+ window.close();
+ }
+ });
+
+ var browser = document.createElement("browser");
+ browser.setAttribute("messagemanagergroup", "test");
+ browser.setAttribute("src", "about:mozilla");
+ browser.setAttribute("type", "content-targetable");
+ 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.xul b/dom/base/test/chrome/file_bug990812-2.xul
new file mode 100644
index 000000000..957861cd4
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-2.xul
@@ -0,0 +1,59 @@
+<?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[
+
+ var FRAME_SCRIPT = "data:,sendAsyncMessage('test')";
+ var order = ["group", "window", "global"];
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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 () {
+ opener.setTimeout("next()");
+ self.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test" type="content-targetable" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-3.xul b/dom/base/test/chrome/file_bug990812-3.xul
new file mode 100644
index 000000000..6dc25c9bb
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-3.xul
@@ -0,0 +1,71 @@
+<?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[
+
+ var FRAME_SCRIPT = "data:,addMessageListener('test', function (msg) {" +
+ "sendSyncMessage('test', msg.data)})";
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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 == 0) {
+ 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 () {
+ opener.setTimeout("next()");
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-4.xul b/dom/base/test/chrome/file_bug990812-4.xul
new file mode 100644
index 000000000..c6d2c04e9
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-4.xul
@@ -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[
+
+ var FRAME_SCRIPT1 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'frame1')})";
+ var FRAME_SCRIPT2 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'frame2')})";
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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"));
+
+ var flo1 = browser1.QueryInterface(Ci.nsIFrameLoaderOwner);
+ var flo2 = browser2.QueryInterface(Ci.nsIFrameLoaderOwner);
+ flo1.swapFrameLoaders(flo2);
+ messageManager.broadcastAsyncMessage("test");
+
+ Promise.all([promise1, promise2]).then(function () {
+ opener.setTimeout("next()");
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-5.xul b/dom/base/test/chrome/file_bug990812-5.xul
new file mode 100644
index 000000000..b00e05bc5
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-5.xul
@@ -0,0 +1,77 @@
+<?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[
+
+ var FRAME_SCRIPT1 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'group1')})";
+ var FRAME_SCRIPT2 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'group2')})";
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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 () {
+ opener.setTimeout("next()");
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+ <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+
+ <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812.xul b/dom/base/test/chrome/file_bug990812.xul
new file mode 100644
index 000000000..e14661fe9
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812.xul
@@ -0,0 +1,58 @@
+<?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[
+
+ 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 Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ 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 == 0) {
+ opener.setTimeout("next()");
+ window.close();
+ }
+ });
+
+ var browser = document.createElement("browser");
+ browser.setAttribute("messagemanagergroup", "test");
+ browser.setAttribute("src", "about:mozilla");
+ browser.setAttribute("type", "content-targetable");
+ 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/fileconstructor_file.png b/dom/base/test/chrome/fileconstructor_file.png
new file mode 100644
index 000000000..51e8aaf38
--- /dev/null
+++ b/dom/base/test/chrome/fileconstructor_file.png
Binary files differ
diff --git a/dom/base/test/chrome/frame_bug814638.xul b/dom/base/test/chrome/frame_bug814638.xul
new file mode 100644
index 000000000..3543a42c3
--- /dev/null
+++ b/dom/base/test/chrome/frame_bug814638.xul
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814638
+-->
+<window title="Mozilla Bug 814638"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <keyset>
+ <key key="T" modifiers="control" oncommand="receivedKeyEvent()"/>
+ </keyset>
+
+ <iframe flex="1" src="about:"/>
+
+</window>
diff --git a/dom/base/test/chrome/frame_registerElement_content.html b/dom/base/test/chrome/frame_registerElement_content.html
new file mode 100644
index 000000000..aa1d75863
--- /dev/null
+++ b/dom/base/test/chrome/frame_registerElement_content.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<x-bar></x-bar>
+</body>
+</html>
diff --git a/dom/base/test/chrome/host_bug814638.xul b/dom/base/test/chrome/host_bug814638.xul
new file mode 100644
index 000000000..081a12578
--- /dev/null
+++ b/dom/base/test/chrome/host_bug814638.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814638
+-->
+<window title="Mozilla Bug 814638"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <iframe flex="1" src="frame_bug814638.xul"/>
+</window>
diff --git a/dom/base/test/chrome/nochrome_bug765993.html b/dom/base/test/chrome/nochrome_bug765993.html
new file mode 100644
index 000000000..158b20c88
--- /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 000000000..a84113e1e
--- /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 000000000..8efacff3c
--- /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/registerElement_ep.js b/dom/base/test/chrome/registerElement_ep.js
new file mode 100644
index 000000000..de32ba51c
--- /dev/null
+++ b/dom/base/test/chrome/registerElement_ep.js
@@ -0,0 +1,8 @@
+var proto = Object.create(HTMLElement.prototype);
+proto.magicNumber = 42;
+proto.createdCallback = function() {
+ finishTest(this.magicNumber === 42);
+};
+document.registerElement("x-foo", { prototype: proto });
+
+document.createElement("x-foo");
diff --git a/dom/base/test/chrome/test_bug1063837.xul b/dom/base/test/chrome/test_bug1063837.xul
new file mode 100644
index 000000000..6104295b4
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1063837.xul
@@ -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"?>
+<!--
+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 type="application/javascript"
+ 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.URI.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.xul b/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xul
new file mode 100644
index 000000000..ea154ae9c
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ 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.xul b/dom/base/test/chrome/test_bug1139964.xul
new file mode 100644
index 000000000..f0a7cefd7
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1139964.xul
@@ -0,0 +1,33 @@
+<?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 type="application/javascript"
+ 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.open("file_bug1139964.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug120684.xul b/dom/base/test/chrome/test_bug120684.xul
new file mode 100644
index 000000000..bc2fc8d95
--- /dev/null
+++ b/dom/base/test/chrome/test_bug120684.xul
@@ -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 type="application/javascript" 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(list instanceof NodeList, "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.createElement("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.createElement("foo");
+ var e2 = document.createElement("foo");
+ var e3 = document.createElement("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.xul b/dom/base/test/chrome/test_bug1209621.xul
new file mode 100644
index 000000000..4bca58d98
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1209621.xul
@@ -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 type="application/javascript" 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.open("file_bug1209621.xul", "", "chrome");
+ });
+
+ ]]>
+ </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 000000000..261055dd8
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1339722.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1339722
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1339722</title>
+ <script type="application/javascript" 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-useragent-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
+ * destoryed in the middle of ReallyStartLoading.
+ * 4. Verify that it doesn't crash.
+ **/
+
+ const {interfaces: Ci, utils: Cu} = Components;
+ Cu.import("resource://gre/modules/Services.jsm");
+
+ const TOPIC = 'http-on-useragent-request';
+ Services.obs.addObserver({
+ observe(subject, topic, data) {
+ info('Got ' + topic);
+ Services.obs.removeObserver(this, TOPIC);
+
+ // Query window proxy so it triggers DOMWindowCreated.
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let win = channel.notificationCallbacks.getInterface(Ci.mozIDOMWindowProxy);
+ }
+ }, TOPIC, false);
+
+ let docShell = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ docShell.chromeEventHandler.addEventListener('DOMWindowCreated', function handler(e) {
+ docShell.chromeEventHandler.removeEventListener('DOMWindowCreated', handler);
+ 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);
+ });
+
+ 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_bug206691.xul b/dom/base/test/chrome/test_bug206691.xul
new file mode 100644
index 000000000..f6496e2df
--- /dev/null
+++ b/dom/base/test/chrome/test_bug206691.xul
@@ -0,0 +1,33 @@
+<?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 type="application/javascript"
+ 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.xul b/dom/base/test/chrome/test_bug289714.xul
new file mode 100644
index 000000000..6d0804784
--- /dev/null
+++ b/dom/base/test/chrome/test_bug289714.xul
@@ -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 type="application/javascript" 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.xul b/dom/base/test/chrome/test_bug339494.xul
new file mode 100644
index 000000000..a81838450
--- /dev/null
+++ b/dom/base/test/chrome/test_bug339494.xul
@@ -0,0 +1,64 @@
+<?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 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=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.xul b/dom/base/test/chrome/test_bug357450.xul
new file mode 100644
index 000000000..7723364ec
--- /dev/null
+++ b/dom/base/test/chrome/test_bug357450.xul
@@ -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 000000000..6fb524e76
--- /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 type="text/javascript" src="chrome://mochikit/content/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 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 000000000..5f8d4969c
--- /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 000000000..cfb8595ac
--- /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 type="text/javascript" src="chrome://mochikit/content/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=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.xul b/dom/base/test/chrome/test_bug418986-1.xul
new file mode 100644
index 000000000..aa0c34077
--- /dev/null
+++ b/dom/base/test/chrome/test_bug418986-1.xul
@@ -0,0 +1,26 @@
+<?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 type="application/javascript"
+ 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;version=1.7" 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.xul b/dom/base/test/chrome/test_bug421622.xul
new file mode 100644
index 000000000..abbb9f2d5
--- /dev/null
+++ b/dom/base/test/chrome/test_bug421622.xul
@@ -0,0 +1,35 @@
+<?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 type="application/javascript"
+ 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.xul b/dom/base/test/chrome/test_bug429785.xul
new file mode 100644
index 000000000..5d8af4555
--- /dev/null
+++ b/dom/base/test/chrome/test_bug429785.xul
@@ -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=429785
+-->
+<window title="Mozilla Bug 429785"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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 = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService);
+ var listener = {
+ QueryInterface : function(iid) {
+ if (!iid.equals(Components.interfaces.nsISupports) &&
+ !iid.equals(Components.interfaces.nsIConsoleListener)) {
+ throw Components.results.NS_NOINTERFACE;
+ }
+ return this;
+ },
+ 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.xul b/dom/base/test/chrome/test_bug430050.xul
new file mode 100644
index 000000000..b5948fad0
--- /dev/null
+++ b/dom/base/test/chrome/test_bug430050.xul
@@ -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 type="application/javascript"
+ 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");
+ document.getElementById('b').loadURI('data:text/plain,succeeded',
+ null,
+ 'UTF-8');
+ 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.xul b/dom/base/test/chrome/test_bug467123.xul
new file mode 100644
index 000000000..d44cbe4b9
--- /dev/null
+++ b/dom/base/test/chrome/test_bug467123.xul
@@ -0,0 +1,35 @@
+<?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 type="application/javascript"
+ 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 **/
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "chrome://global/content/bindings/button.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_bug549682.xul b/dom/base/test/chrome/test_bug549682.xul
new file mode 100644
index 000000000..8c7376bb5
--- /dev/null
+++ b/dom/base/test/chrome/test_bug549682.xul
@@ -0,0 +1,33 @@
+<?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 type="application/javascript"
+ 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.open("file_bug549682.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug571390.xul b/dom/base/test/chrome/test_bug571390.xul
new file mode 100644
index 000000000..aebb2cfc8
--- /dev/null
+++ b/dom/base/test/chrome/test_bug571390.xul
@@ -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 type="application/javascript" 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.xul b/dom/base/test/chrome/test_bug616841.xul
new file mode 100644
index 000000000..9b04eb3b1
--- /dev/null
+++ b/dom/base/test/chrome/test_bug616841.xul
@@ -0,0 +1,31 @@
+<?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 type="application/javascript"
+ 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.open("file_bug616841.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug635835.xul b/dom/base/test/chrome/test_bug635835.xul
new file mode 100644
index 000000000..e06541fb4
--- /dev/null
+++ b/dom/base/test/chrome/test_bug635835.xul
@@ -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"?>
+<!--
+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 type="application/javascript"
+ 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 = Components.interfaces.nsIDOMNodeFilter.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 000000000..64f36c907
--- /dev/null
+++ b/dom/base/test/chrome/test_bug682305.html
@@ -0,0 +1,175 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=682305
+-->
+<head>
+ <title>XMLHttpRequest send and channel implemented in JS</title>
+ <script type="application/javascript" 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;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+/*
+ * Register a custom nsIProtocolHandler service
+ * in order to be able to implement *and use* an
+ * nsIChannel component written in Javascript.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+var SimpleURI = Cc["@mozilla.org/network/simple-uri;1"];
+var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+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: function(listener, context) {
+ let stream = this.open();
+ try {
+ listener.onStartRequest(this, context);
+ } catch(e) {}
+ try {
+ listener.onDataAvailable(this, context, stream, 0, stream.available());
+ } catch(e) {}
+ try {
+ listener.onStopRequest(this, context, Cr.NS_OK);
+ } catch(e) {}
+ },
+ asyncOpen2: function(listener) {
+ // throws an error if security checks fail
+ var outListener = contentSecManager.performSecurityCheck(this, listener);
+ return this.asyncOpen(outListener, null);
+ },
+ open: function() {
+ let data = "bar";
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+ return stream;
+ },
+ open2: function() {
+ // throws an error if security checks fail
+ contentSecManager.performSecurityCheck(this, null);
+ return this.open();
+ },
+ isPending: function() {
+ return false;
+ },
+ cancel: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ suspend: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ resume: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
+};
+
+
+function CustomProtocol() {}
+CustomProtocol.prototype = {
+ get scheme() {
+ return PROTOCOL_SCHEME;
+ },
+ get protocolFlags() {
+ return (Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD);
+ },
+ get defaultPort() {
+ return -1;
+ },
+ allowPort: function allowPort() {
+ return false;
+ },
+ newURI: function newURI(spec, charset, baseURI) {
+ var uri = SimpleURI.createInstance(Ci.nsIURI)
+ uri.spec = spec;
+ return uri.QueryInterface(Ci.nsIURI);
+ },
+ newChannel2: function newChannel2(URI, loadInfo) {
+ return new CustomChannel(URI, loadInfo);
+ },
+ newChannel: function newChannel(URI) {
+ return newChannel2(URI);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIProtocolHandler])
+};
+
+var gFactory = {
+ register: function() {
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ var classID = Components.ID("{ed064287-1e76-49ba-a28d-dc74394a8334}");
+ var description = PROTOCOL_SCHEME + ": protocol";
+ var contractID = "@mozilla.org/network/protocol;1?name=" + PROTOCOL_SCHEME;
+ var factory = XPCOMUtils._getFactory(CustomProtocol);
+
+ registrar.registerFactory(classID, description, contractID, factory);
+
+ this.unregister = function() {
+ registrar.unregisterFactory(classID, factory);
+ 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.xul b/dom/base/test/chrome/test_bug683852.xul
new file mode 100644
index 000000000..cebc8f358
--- /dev/null
+++ b/dom/base/test/chrome/test_bug683852.xul
@@ -0,0 +1,69 @@
+<?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">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <button value="testbutton" id="testbutton"/>
+ <!-- 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();
+
+ function startTest() {
+ is(document.contains(document), true, "Document should contain itself!");
+
+ var tb = document.getElementById("testbutton");
+ is(document.contains(tb), true, "Document should contain element in it!");
+ is(tb.contains(tb), true, "Element should contain itself.")
+ var anon = document.getAnonymousElementByAttribute(tb, "anonid", "button-box");
+ is(document.contains(anon), false, "Document should not contain anonymous element in it!");
+ is(tb.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(tb), true, "Element should contain element in it!");
+ is(document.contains(document.createElement("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>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.xul b/dom/base/test/chrome/test_bug752226-3.xul
new file mode 100644
index 000000000..ed3d5c60c
--- /dev/null
+++ b/dom/base/test/chrome/test_bug752226-3.xul
@@ -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 type="application/javascript" 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.xul b/dom/base/test/chrome/test_bug752226-4.xul
new file mode 100644
index 000000000..d2335f9b8
--- /dev/null
+++ b/dom/base/test/chrome/test_bug752226-4.xul
@@ -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 type="application/javascript" 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 Components.utils.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 000000000..b297c8906
--- /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 type="application/javascript" 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 { -moz-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 **/
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+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 > 0, "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.xul b/dom/base/test/chrome/test_bug780199.xul
new file mode 100644
index 000000000..649538bec
--- /dev/null
+++ b/dom/base/test/chrome/test_bug780199.xul
@@ -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 type="application/javascript" 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.xul b/dom/base/test/chrome/test_bug780529.xul
new file mode 100644
index 000000000..f28ed4b78
--- /dev/null
+++ b/dom/base/test/chrome/test_bug780529.xul
@@ -0,0 +1,40 @@
+<?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 type="application/javascript" 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(Components.interfaces.nsIChannelEventSink);
+ok(sink instanceof Components.interfaces.nsIChannelEventSink,
+ "Should be a channel event sink")
+ok("asyncOnChannelRedirect" in sink,
+ "Should have the right methods for an event sink");
+
+is(callbacks.getInterface(Components.interfaces.nsIXMLHttpRequest), req,
+ "Should have the right object");
+sinkReq = sink.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+isnot(sinkReq, callbacks, "Sink should not be the XHR object");
+is(sinkReq.getInterface(Components.interfaces.nsIXMLHttpRequest), req,
+ "Should have the XHR object now");
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug800386.xul b/dom/base/test/chrome/test_bug800386.xul
new file mode 100644
index 000000000..369e6ed81
--- /dev/null
+++ b/dom/base/test/chrome/test_bug800386.xul
@@ -0,0 +1,68 @@
+<?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 type="application/javascript" 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 **/
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ var triedForwarding = false;
+ var forwardFailed = false;
+
+ var xhr = new XMLHttpRequest;
+ var xhr2 = new XMLHttpRequest;
+
+ var eventSink = xhr.getInterface(Components.interfaces.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(Components.interfaces.nsIProgressEventSink)) {
+ triedForwarding = true;
+ try {
+ return xhr2.getInterface(aIID);
+ } catch (e) {
+ forwardFailed = true;
+ }
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
+ Components.interfaces.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_bug814638.xul b/dom/base/test/chrome/test_bug814638.xul
new file mode 100644
index 000000000..f507099c9
--- /dev/null
+++ b/dom/base/test/chrome/test_bug814638.xul
@@ -0,0 +1,64 @@
+<?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=814638
+-->
+<window title="Mozilla Bug 814638"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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=814638"
+ target="_blank" id="link">Mozilla Bug 814638</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 814638 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function startTest() {
+ let hostURL = "chrome://mochitests/content/chrome/dom/base/test/chrome/host_bug814638.xul";
+ let host1 = window.open(hostURL, "_blank", "chrome");
+ let host2 = window.open(hostURL, "_blank", "chrome");
+
+ let isHost1Loaded = isHost2Loaded = false
+ host1.onload = function() {
+ isHost1Loaded = true;
+ if (isHost2Loaded) swapFrames();
+ }
+ host2.onload = function() {
+ isHost2Loaded = true;
+ if (isHost1Loaded) swapFrames();
+ }
+
+ function swapFrames() {
+ let iframe1 = host1.document.querySelector("iframe");
+ let iframe2 = host2.document.querySelector("iframe");
+ iframe2.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ iframe2.swapFrameLoaders(iframe1);
+ setTimeout(function() {
+ iframe2.contentWindow.receivedKeyEvent = receivedKeyEvent;
+ let innerIframe2 = iframe2.contentDocument.querySelector("iframe");
+ let e = innerIframe2.contentDocument.createEvent("KeyboardEvent");
+ e.initKeyEvent("keypress", true, true, null, true, false, false, false, 0, "t".charCodeAt(0));
+ innerIframe2.contentDocument.documentElement.dispatchEvent(e);
+ host1.close();
+ host2.close();
+ }, 0);
+ }
+ }
+
+ function receivedKeyEvent() {
+ ok(true, "Received key event");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(startTest);
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug816340.xul b/dom/base/test/chrome/test_bug816340.xul
new file mode 100644
index 000000000..c6ad12b42
--- /dev/null
+++ b/dom/base/test/chrome/test_bug816340.xul
@@ -0,0 +1,31 @@
+<?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 type="application/javascript"
+ 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.open("file_bug816340.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug884693.xul b/dom/base/test/chrome/test_bug884693.xul
new file mode 100644
index 000000000..6a2be9d73
--- /dev/null
+++ b/dom/base/test/chrome/test_bug884693.xul
@@ -0,0 +1,67 @@
+<?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 type="application/javascript" 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";
+
+ var { classes: Cc, interfaces: Ci } = Components;
+
+ 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() || [];
+ is(messages.length, expectedMessages.length, "Got expected message count");
+ messages = messages.map(m => m.message).join(",");
+ for(let message of expectedMessages) {
+ ok(messages.indexOf(message) >= 0, "Got expected message: " + message);
+ }
+
+ resolve();
+ };
+
+ xhr.onerror = e => {
+ reject(e);
+ };
+
+ xhr.open("GET", `${SERVER_URL}?${status}&${statusText}&${body}`);
+ xhr.send();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest(204, "No content", "", "", []).
+ then(() => { return runTest(204, "No content", INVALID_XML, "", []); }).
+ then(() => { return runTest(304, "Not modified", "", "", []); }).
+ then(() => { return runTest(304, "Not modified", INVALID_XML, "", []); }).
+ then(() => { return runTest(200, "OK", "", "", ["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 000000000..aa463ca9e
--- /dev/null
+++ b/dom/base/test/chrome/test_bug914381.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650776
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 914381</title>
+ <script type="application/javascript" 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">
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+function createFileWithData(fileData) {
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+ var testFile = 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
+ 0666, 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");
+var f = File.createFromNsIFile(file);
+is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
+is(f.mozFullPath, file.path, "mozFullPath returns path if created with nsIFile");
+
+f = File.createFromFileName(file.path);
+is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
+is(f.mozFullPath, "", "mozFullPath returns blank if created with a string");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_bug990812.xul b/dom/base/test/chrome/test_bug990812.xul
new file mode 100644
index 000000000..4be173429
--- /dev/null
+++ b/dom/base/test/chrome/test_bug990812.xul
@@ -0,0 +1,43 @@
+<?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 type="application/javascript"
+ 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.xul",
+ "file_bug990812-2.xul",
+ "file_bug990812-3.xul",
+ "file_bug990812-4.xul",
+ "file_bug990812-5.xul",
+ ];
+
+ function next() {
+ if (tests.length > 0) {
+ var file = tests.shift();
+ info("-- running " + file);
+ window.open(file, "", "chrome");
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ addLoadEvent(next);
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_cpows.xul b/dom/base/test/chrome/test_cpows.xul
new file mode 100644
index 000000000..acbbebb96
--- /dev/null
+++ b/dom/base/test/chrome/test_cpows.xul
@@ -0,0 +1,33 @@
+<?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 CPOWs"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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[
+
+ SimpleTest.waitForExplicitFinish();
+
+ const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
+ SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, false);
+ SimpleTest.registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
+ });
+
+ function done() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.open("cpows_parent.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_domparsing.xul b/dom/base/test/chrome/test_domparsing.xul
new file mode 100644
index 000000000..c2a10710d
--- /dev/null
+++ b/dom/base/test/chrome/test_domparsing.xul
@@ -0,0 +1,144 @@
+<?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 type="application/javascript" 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=816410"
+ target="_blank">Mozilla Bug 816410</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript;version=1.7"><![CDATA[
+"use strict";
+/** Test for Bug 816410 **/
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function throws(a, type, message) {
+ for (let fn of Array.isArray(a) ? a : [a]) {
+ try {
+ fn();
+ ok(false, message);
+ } catch (e) {
+ if (type) {
+ is(e.name, type, message);
+ } else {
+ ok(true, message);
+ }
+ }
+ }
+}
+
+// DOMParser constructor should not throw for null arguments
+new DOMParser(undefined);
+new DOMParser(null);
+
+throws([
+ function() { new DOMParser(false); },
+ function() { new DOMParser(0); },
+ function() { new DOMParser(""); },
+ function() { new DOMParser({}); },
+], "TypeError", "DOMParser constructor should throw for extra arguments");
+
+{
+ let parser = new DOMParser();
+ throws(function() {
+ parser.init();
+ }, "NS_ERROR_UNEXPECTED", "init method should throw when DOMParser is created by constructor");
+}
+
+// XMLSerializer constructor should not throw for extra arguments
+new XMLSerializer(undefined);
+new XMLSerializer(null);
+new XMLSerializer(false);
+new XMLSerializer(0);
+new XMLSerializer("");
+new XMLSerializer({});
+
+runTest(new DOMParser(), new XMLSerializer());
+
+{
+ let parser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+ parser.init();
+ throws(function() {
+ parser.init();
+ }, "NS_ERROR_UNEXPECTED", "init method should throw when called twice");
+}
+
+runTest(Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser),
+ Cc["@mozilla.org/xmlextras/xmlserializer;1"]
+ .createInstance(Ci.nsIDOMSerializer));
+
+function runTest(parser, serializer) {
+ 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.init, "function", "init 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) {
+ is(serializer.serializeToString(parser.parseFromString(t.input, t.type)), t.expected,
+ "parseFromString test for " + t.type);
+
+ let ostream = {
+ write: function(buf, count) { this.data += buf; return count; }
+ };
+ for (let charset of [null, "UTF-8"]) {
+ ostream.data = "";
+ serializer.serializeToStream(parser.parseFromString(t.input, t.type), ostream, charset);
+ is(ostream.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.map(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, a.length, t.type)), t.expected,
+ input.name + " test for " + t.type);
+ throws(function() {
+ parser.parseFromBuffer(a, a.length + 1, t.type);
+ }, "NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY",
+ input.name + " should throw if bufLen parameter is greater than actual length"
+ );
+ }
+
+ 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.xul b/dom/base/test/chrome/test_fileconstructor.xul
new file mode 100644
index 000000000..2399b6ef8
--- /dev/null
+++ b/dom/base/test/chrome/test_fileconstructor.xul
@@ -0,0 +1,72 @@
+<?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 **/
+
+var file = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("CurWorkD", Components.interfaces.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");
+
+doTest(File.createFromFileName(file.path));
+doTest(File.createFromNsIFile(file));
+function doTest(domfile) {
+ ok(domfile instanceof File, "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)");
+}
+
+try {
+ var nonexistentfile = File.createFromFileName("i/sure/hope/this/does/not/exist/anywhere.txt");
+ ok(false, "This should never be reached!");
+} catch (e) {
+ ok(true, "Attempt to construct a non-existent file should fail.");
+}
+
+try {
+ var dir = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("CurWorkD", Components.interfaces.nsIFile);
+ var dirfile = File.createFromNsIFile(dir);
+ ok(false, "This should never be reached!");
+} catch (e) {
+ ok(true, "Attempt to construct a file from a directory should fail.");
+}
+]]>
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/test_fileconstructor_tempfile.xul b/dom/base/test/chrome/test_fileconstructor_tempfile.xul
new file mode 100644
index 000000000..e2ff3a0d2
--- /dev/null
+++ b/dom/base/test/chrome/test_fileconstructor_tempfile.xul
@@ -0,0 +1,93 @@
+<?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=982874
+
+Tests building a DOMFile with the "temporary" option and checks that
+the underlying file is removed when the DOMFile is gc'ed.
+-->
+<window title="Mozilla Bug 982874"
+ 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=982874">
+ Mozilla Bug 982874</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 982874 **/
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+SimpleTest.waitForExplicitFinish();
+
+function cleanup(tmp) {
+ // Force cycle and garbage collection and check that we removed the file.
+ for (let i = 0; i < 10; i++) {
+ Cu.forceCC();
+ Cu.forceGC();
+ }
+ if (tmp.exists()) {
+ ok(false, "Failed to remove temporary file!");
+ } else {
+ ok(true, "Temporary file removed when gc-ing the DOMFile");
+ }
+ SimpleTest.finish();
+}
+
+try {
+ // Create a file in $TMPDIR/mozilla-temp-files
+ let tmp = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ tmp.append("mozilla-temp-files");
+ tmp.append("test.txt");
+ tmp.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+
+ // Add some content to the file.
+ let fileData = "I'm a temporary file!";
+ let outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(tmp, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ // Create a scoped DOMFile so the gc will happily get rid of it.
+ {
+ let dirfile = File.createFromNsIFile(tmp, { temporary: true });
+ ok(true, "Temporary File() created");
+ let reader = new FileReader();
+ reader.readAsArrayBuffer(dirfile);
+ reader.onload = function(event) {
+ let buffer = event.target.result;
+ ok(buffer.byteLength > 0,
+ "Blob size should be > 0 : " + buffer.byteLength);
+ cleanup(tmp);
+ }
+ }
+} catch (e) {
+ ok(false, "Unable to create the File() object : " + e);
+ SimpleTest.finish();
+}
+]]>
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/test_groupedSHistory.xul b/dom/base/test/chrome/test_groupedSHistory.xul
new file mode 100644
index 000000000..23cd4c6ad
--- /dev/null
+++ b/dom/base/test/chrome/test_groupedSHistory.xul
@@ -0,0 +1,32 @@
+<?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=1276553
+-->
+<window title="Mozilla Bug 1276553"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="loadTest();">
+ <script type="application/javascript"
+ 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=1276553"
+ target="_blank">Mozilla Bug 1276553</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for GroupedSHistory **/
+ SimpleTest.waitForExplicitFinish();
+ function loadTest() {
+ window.open("window_groupedSHistory.xul", "", "width=360,height=240,chrome");
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_nsITextInputProcessor.xul b/dom/base/test/chrome/test_nsITextInputProcessor.xul
new file mode 100644
index 000000000..23ad3e249
--- /dev/null
+++ b/dom/base/test/chrome/test_nsITextInputProcessor.xul
@@ -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"?>
+<window title="Testing nsITextInputProcessor behavior"
+ 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">
+<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.open("window_nsITextInputProcessor.xul", "_blank",
+ "chrome,width=600,height=600");
+
+]]>
+</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 000000000..5d8dc5a6e
--- /dev/null
+++ b/dom/base/test/chrome/test_range_getClientRectsAndTexts.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+<meta charset="utf-8">
+<script type="text/javascript" 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\nbre"], 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"},
+ ];
+
+ 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>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/chrome/test_registerElement_content.xul b/dom/base/test/chrome/test_registerElement_content.xul
new file mode 100644
index 000000000..9a918f2d7
--- /dev/null
+++ b/dom/base/test/chrome/test_registerElement_content.xul
@@ -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 type="application/javascript"
+ 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_registerElement_content.html"></iframe>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 1130028 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var createdCallbackCount = 0;
+
+ // Callback should be called once by element created in chrome,
+ // and once by element created in content.
+ function createdCallbackCalled() {
+ createdCallbackCount++;
+ ok(true, "Created callback called, should be called twice in test.");
+ is(this.magicNumber, 42, "Callback should be able to see the custom prototype.");
+ if (createdCallbackCount == 2) {
+ SimpleTest.finish();
+ }
+ }
+
+ function startTests() {
+ var frame = $("frame");
+
+ var c = frame.contentDocument.registerElement("x-foo");
+ var elem = new c();
+ is(elem.tagName, "X-FOO", "Constructor should create an x-foo element.");
+
+ var proto = Object.create(frame.contentWindow.HTMLElement.prototype);
+ proto.magicNumber = 42;
+ proto.createdCallback = createdCallbackCalled;
+ frame.contentDocument.registerElement("x-bar", { prototype: proto });
+
+ frame.contentDocument.createElement("x-bar");
+ }
+
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_registerElement_ep.xul b/dom/base/test/chrome/test_registerElement_ep.xul
new file mode 100644
index 000000000..6f1745268
--- /dev/null
+++ b/dom/base/test/chrome/test_registerElement_ep.xul
@@ -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=1130028
+-->
+<window title="Mozilla Bug 1130028"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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_registerElement_content.html"></iframe>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ /** Test for Bug 1130028 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function finishTest(canSeePrototype) {
+ ok(true, "createdCallback called when reigsterElement was called with an extended principal.");
+ ok(canSeePrototype, "createdCallback 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 = Components.utils.Sandbox([frame.contentWindow], { sandboxPrototype: frame.contentWindow });
+ sandbox.finishTest = finishTest;
+ Services.scriptloader.loadSubScript("chrome://mochitests/content/chrome/dom/base/test/chrome/registerElement_ep.js", sandbox);
+ }
+
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_swapFrameLoaders.xul b/dom/base/test/chrome/test_swapFrameLoaders.xul
new file mode 100644
index 000000000..2f69d390f
--- /dev/null
+++ b/dom/base/test/chrome/test_swapFrameLoaders.xul
@@ -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 type="application/javascript" 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.open("window_swapFrameLoaders.xul", "bug1242644",
+ "chrome,width=600,height=600");
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_title.xul b/dom/base/test/chrome/test_title.xul
new file mode 100644
index 000000000..6fa145bc6
--- /dev/null
+++ b/dom/base/test/chrome/test_title.xul
@@ -0,0 +1,30 @@
+<?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 type="application/javascript"
+ 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.open("title_window.xul", "bug481777",
+ "chrome,width=100,height=100");
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_windowroot.xul b/dom/base/test/chrome/test_windowroot.xul
new file mode 100644
index 000000000..c9ff7f0b5
--- /dev/null
+++ b/dom/base/test/chrome/test_windowroot.xul
@@ -0,0 +1,19 @@
+<?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 type="application/javascript"
+ 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(root instanceof WindowRoot, "windowRoot should be a WindowRoot");
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/title_window.xul b/dom/base/test/chrome/title_window.xul
new file mode 100644
index 000000000..5db180f8b
--- /dev/null
+++ b/dom/base/test/chrome/title_window.xul
@@ -0,0 +1,198 @@
+<?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="data:application/vnd.mozilla.xul+xml,&lt;window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>"/>
+ <iframe id="xul2" src="data:application/vnd.mozilla.xul+xml,&lt;window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>"/>
+ <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.opener.wrappedJSObject[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);
+ var done = false;
+
+ 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.parentNode.removeChild(t);
+ } ],
+ [ "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.parentNode.removeChild(t);
+ } ] ];
+
+ 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_groupedSHistory.xul b/dom/base/test/chrome/window_groupedSHistory.xul
new file mode 100644
index 000000000..aafa991ba
--- /dev/null
+++ b/dom/base/test/chrome/window_groupedSHistory.xul
@@ -0,0 +1,343 @@
+<?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=1276553
+-->
+<window title="Mozilla Bug 1276553"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+ Cu.import("resource://testing-common/TestUtils.jsm");
+ Cu.import("resource://testing-common/ContentTask.jsm");
+ Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+ Cu.import("resource://gre/modules/Task.jsm");
+ ContentTask.setTestScope(window.opener.wrappedJSObject);
+
+ let imports = ['SimpleTest', 'SpecialPowers', 'ok', 'is', 'info'];
+ for (let name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+ }
+
+ /** Test for Bug 1276553 **/
+ function run() {
+ new Promise(resolve => SpecialPowers.pushPrefEnv(
+ {'set' : [[ 'browser.groupedhistory.enabled', true ]]}, resolve))
+ .then(() => test(false))
+ .then(() => test(true))
+ .then(() => {
+ window.close();
+ SimpleTest.finish();
+ });
+ }
+
+ function test(remote) {
+ let act, bg1, bg2;
+ return Promise.resolve()
+
+ // create first browser with 1 entry (which will always be the active one)
+ .then(() => info('TEST-INFO | test create active browser, remote=' + remote))
+ .then(() => createBrowser('pen', remote))
+ .then(b => act = b)
+ .then(() => verifyBrowser(act, 'pen' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ false /* canGoForward */,
+ false /* partial */ ))
+
+ // create background browser 1 with 1 entry
+ .then(() => info('TEST-INFO | test create background browser 1, remote=' + remote))
+ .then(() => createBrowser('pineapple', remote))
+ .then(b => bg1 = b)
+ .then(() => verifyBrowser(bg1, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ false /* canGoForward */,
+ false /* partial */ ))
+
+ // create background browser 2 with 2 entries
+ .then(() => info('TEST-INFO | test create background browser 2, remote=' + remote))
+ .then(() => createBrowser('apple', remote))
+ .then(b => bg2 = b)
+ .then(() => verifyBrowser(bg2, 'apple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ false /* canGoForward */,
+ false /* partial */ ))
+ .then(() => loadURI(bg2, getDummyHtml('pencil')))
+ .then(() => verifyBrowser(bg2, 'pencil' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ false /* partial */ ))
+
+ // merge to 2 entries pen-pineapple
+ .then(() => info('TEST-INFO | test merge history, remote=' + remote))
+ .then(() => mergeHistory(act, bg1))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 2 /* globalLength */ ))
+
+ // merge to 4 entries pen-pineapple-apple-pencil
+ .then(() => mergeHistory(act, bg2))
+ .then(() => verifyBrowser(act, 'pencil' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+
+ // test go back
+ .then(() => info('TEST-INFO | test history go back, remote=' + remote))
+ .then(() => wrapHistoryNavFn(act, act.goBack.bind(act)))
+ .then(() => verifyBrowser(act, 'apple' /* title */,
+ 0 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+ // XXX The 2nd pageshow comes from reload as current index of the active
+ // partial history remains the same
+ .then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
+ .then(() => verifyBrowser(act, 'pen' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 0 /* offset */,
+ 4 /* globalLength */ ))
+
+ // test go forward
+ .then(() => info('TEST-INFO | test history go forward, remote=' + remote))
+ .then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
+ .then(() => verifyBrowser(act, 'apple' /* title */,
+ 0 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => wrapHistoryNavFn(act, act.goForward.bind(act)))
+ .then(() => verifyBrowser(act, 'pencil' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+
+ // test goto index
+ .then(() => info('TEST-INFO | test history goto index, remote=' + remote))
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 0), true))
+ .then(() => verifyBrowser(act, 'pen' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 0 /* offset */,
+ 4 /* globalLength */ ))
+ // expect 2 pageshow since we're also changing mIndex of the partial history
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 2), true, 2))
+ .then(() => verifyBrowser(act, 'apple' /* title */,
+ 0 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 4 /* globalLength */ ))
+ // expect 2 pageshow since we're also changing mIndex of the partial history
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 3), true, 2))
+ .then(() => verifyBrowser(act, 'pencil' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+
+ // test history change to 3 entries pen-pineapple-banana
+ .then(() => info('TEST-INFO | test history change, remote=' + remote))
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => loadURI(act, getDummyHtml('banana')))
+ .then(() => verifyBrowser(act, 'banana' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 3 /* globalLength */ ))
+ }
+
+ function getDummyHtml(title) {
+ return 'data:text/html;charset=UTF-8,' +
+ '<html><head><title>' + title + '</title></head></html>'
+ }
+
+ function createBrowser(title, remote) {
+ let browser = document.createElement('browser');
+ browser.setAttribute('type', 'content');
+ browser.setAttribute('remote', remote);
+ browser.setAttribute('src', getDummyHtml(title));
+ document.getElementById('stack').appendChild(browser);
+ return BrowserTestUtils.browserLoaded(browser)
+ .then(() => {
+ browser.messageManager.loadFrameScript('data:,' +
+ 'addEventListener("pageshow", () => sendAsyncMessage("test:pageshow", null), false);' +
+ 'addEventListener("pagehide", () => sendAsyncMessage("test:pagehide", null), false);',
+ true);
+ })
+ .then(() => {
+ // a trick to ensure webProgress object is created for e10s case
+ ok(browser.webProgress, 'check browser.webProgress exists');
+ return browser;
+ });
+ }
+
+ function loadURI(browser, uri) {
+ let promise = BrowserTestUtils.browserLoaded(browser, false, uri);
+ browser.loadURI(uri);
+ return promise;
+ }
+
+ function mergeHistory(b1, b2) {
+ let promises = [];
+ let pagehide1, pagehide2;
+
+ // For swapping there should be a pagehide followed by a pageshow.
+ promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pagehide', msg => pagehide1 = true));
+ promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pagehide', msg => pagehide2 = true));
+ promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pageshow', msg => pagehide1));
+ promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pageshow', msg => pagehide2));
+
+ // For swapping remote browsers, we'll also receive Content:LocationChange
+ if (b1.isRemoteBrowser) {
+ promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'Content:LocationChange'));
+ }
+
+ promises.push(Promise.resolve().then(() => {
+ let f1 = b1.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ let f2 = b2.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ f1.appendPartialSessionHistoryAndSwap(f2);
+ }));
+
+ return Promise.all(promises);
+ }
+
+ function wrapHistoryNavFn(browser, navFn, expectSwap = false, expectPageshowCount = 1) {
+ let promises = [];
+ let pagehide = false;
+ let pageshowCount = 0;
+
+ if (expectSwap) {
+ // For swapping there should be a pagehide followed by a pageshow.
+ promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
+ 'test:pagehide', msg => pagehide = true));
+
+ // For swapping remote browsers, we'll also receive Content:LocationChange
+ if (browser.isRemoteBrowser) {
+ promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
+ 'Content:LocationChange'));
+ }
+ }
+ promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
+ 'test:pageshow', msg => {
+ // Only count events after pagehide for swapping case.
+ if (!expectSwap || pagehide) {
+ return !--expectPageshowCount;
+ }
+ return false;
+ }));
+ promises.push(Task.spawn(navFn));
+
+ return Promise.all(promises);
+ }
+
+ function verifyBrowser(browser, title, index, length, canGoBack, canGoForward,
+ partial, offset = 0, globalLength = length) {
+ is(browser.canGoBack, canGoBack, 'check browser.canGoBack');
+ is(browser.canGoForward, canGoForward, 'check browser.canGoForward');
+ if (partial) {
+ let frameLoader = browser.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ is(frameLoader.groupedSessionHistory.count, globalLength, 'check groupedSHistory.count');
+ }
+
+ return ContentTask.spawn(browser,
+ { title, index, length, canGoBack, canGoForward, partial, offset, globalLength },
+ ({ title, index, length, canGoBack, canGoForward, partial, offset, globalLength }) => {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let shistory = webNav.sessionHistory;
+ is(content.document.title, title, 'check title');
+ is(webNav.canGoBack, canGoBack, 'check webNav.canGoBack');
+ is(webNav.canGoForward, canGoForward, 'check webNav.canGoForward');
+ is(shistory.index, index, 'check shistory.index');
+ is(shistory.count, length, 'check shistory.count');
+ is(shistory.isPartial, partial, 'check shistory.isPartial');
+ is(shistory.globalIndexOffset, offset, 'check shistory.globalIndexOffset');
+ is(shistory.globalCount, globalLength, 'check shistory.globalCount');
+ });
+ }
+
+ ]]>
+ </script>
+ <stack id="stack" flex="1" />
+</window>
diff --git a/dom/base/test/chrome/window_nsITextInputProcessor.xul b/dom/base/test/chrome/window_nsITextInputProcessor.xul
new file mode 100644
index 000000000..ce6bfe3db
--- /dev/null
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xul
@@ -0,0 +1,4027 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+<input id="input" type="text"/><br/>
+<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/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var SpecialPowers = window.opener.wrappedJSObject.SpecialPowers;
+var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+
+SimpleTest.waitForFocus(runTests, window);
+
+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 finish()
+{
+ window.close();
+}
+
+function onunload()
+{
+ SimpleTest.finish();
+}
+
+var iframe = document.getElementById("iframe");
+var childWindow = iframe.contentWindow;
+var textareaInFrame;
+var input = document.getElementById("input");
+var otherWindow = window.opener;
+var otherDocument = otherWindow.document;
+var inputInChildWindow = otherDocument.getElementById("input");
+
+function createTIP()
+{
+ return Components.classes["@mozilla.org/text-input-processor;1"].
+ createInstance(Components.interfaces.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 be committed with the last composing string");
+
+ 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);
+ 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 a call of TIP1.flushPendingComposition();");
+ }, false);
+ input.addEventListener("compositionupdate", 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 compositionupdate event handler during a call of TIP1.flushPendingComposition();");
+ }, false);
+ input.addEventListener("text", 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 text event handler during a call of TIP1.flushPendingComposition();");
+ }, false);
+ input.addEventListener("input", 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 input event handler during a call of TIP1.flushPendingComposition();");
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 4,
+ description + "compositionstart, compositionupdate, text 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, "input",
+ description + "events[3] should be input");
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("compositionend", 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 compositionend event handler during a call of TIP1.commitComposition();");
+ }, false);
+ input.addEventListener("input", 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 input event handler during a call of TIP1.commitComposition();");
+ }, false);
+ TIP1.commitComposition();
+ is(events.length, 3,
+ description + "text, 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, "compositionend",
+ description + "events[1] should be compositionend");
+ is(events[2].type, "input",
+ description + "events[2] should be input");
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
+ 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.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("compositionupdate", 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 during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("text", 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 during text event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("compositionend", 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 during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("input", 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 during input event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, 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, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("text", 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 text event handler during a call of TIP1.cancelComposition();");
+ }, false);
+ input.addEventListener("compositionend", 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 compositionend event handler during a call of TIP1.cancelComposition();");
+ }, false);
+ input.addEventListener("input", 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 input event handler during a call of TIP1.cancelComposition();");
+ }, false);
+ TIP1.cancelComposition();
+ is(events.length, 4,
+ description + "compositionupdate, text, 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, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("keypress", 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 keypress event handler during a call of TIP1.keydown();");
+ }, false);
+ input.addEventListener("input", 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 input event handler during a call of TIP1.keydown();");
+ }, false);
+ input.addEventListener("keyup", 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 keyup event handler during a call of TIP1.keyup();");
+ }, false);
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 4,
+ description + "keydown, keypress, 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, "input",
+ description + "events[2] should be input");
+ is(events[3].type, "keyup",
+ description + "events[3] 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);
+ 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 a call of TIP1.flushPendingComposition();");
+ }, false);
+ input.addEventListener("compositionupdate", 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 compositionupdate event handler during a call of TIP1.flushPendingComposition();");
+ }, false);
+ input.addEventListener("text", 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 text event handler during a call of TIP1.flushPendingComposition();");
+ }, false);
+ input.addEventListener("input", 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 input event handler during a call of TIP1.flushPendingComposition();");
+ }, false);
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 4,
+ description + "compositionstart, compositionupdate, text 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, "input",
+ description + "events[3] should be input");
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("compositionend", 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 compositionend event handler during a call of TIP1.commitComposition();");
+ }, false);
+ input.addEventListener("input", 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 input event handler during a call of TIP1.commitComposition();");
+ }, false);
+ TIP1.commitComposition();
+ is(events.length, 3,
+ description + "text, 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, "compositionend",
+ description + "events[1] should be compositionend");
+ is(events[2].type, "input",
+ description + "events[2] should be input");
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
+ 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.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("compositionupdate", 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 during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("text", 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 during text event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("compositionend", 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 during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("input", 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 during input event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, 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, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("text", 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 text event handler during a call of TIP1.cancelComposition();");
+ }, false);
+ input.addEventListener("compositionend", 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 compositionend event handler during a call of TIP1.cancelComposition();");
+ }, false);
+ input.addEventListener("input", 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 input event handler during a call of TIP1.cancelComposition();");
+ }, false);
+ TIP1.cancelComposition();
+ is(events.length, 4,
+ description + "compositionupdate, text, 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, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("keypress", 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 keypress event handler during a call of TIP1.keydown();");
+ }, false);
+ input.addEventListener("input", 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 input event handler during a call of TIP1.keydown();");
+ }, false);
+ input.addEventListener("keyup", 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 keyup event handler during a call of TIP1.keyup();");
+ }, false);
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 4,
+ description + "keydown, keypress, 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, "input",
+ description + "events[2] should be input");
+ is(events[3].type, "keyup",
+ description + "events[3] 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, 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() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition().
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 4,
+ description + "compositionstart, compositionupdate, text 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, "input",
+ description + "events[3] should be input");
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.commitComposition();
+ is(events.length, 3,
+ description + "text, 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, "compositionend",
+ description + "events[1] should be compositionend");
+ is(events[2].type, "input",
+ description + "events[2] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, 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, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.cancelComposition();
+ is(events.length, 4,
+ description + "compositionupdate, text, 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, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 4,
+ description + "keydown, keypress, 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, "input",
+ description + "events[2] should be input");
+ is(events[3].type, "keyup",
+ description + "events[3] 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 4,
+ description + "compositionstart, compositionupdate, text 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, "input",
+ description + "events[3] should be input");
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.commitComposition();
+ is(events.length, 3,
+ description + "text, 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, "compositionend",
+ description + "events[1] should be compositionend");
+ is(events[2].type, "input",
+ description + "events[2] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, 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, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.cancelComposition();
+ is(events.length, 4,
+ description + "compositionupdate, text, 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, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 4,
+ description + "keydown, keypress, 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, "input",
+ description + "events[2] should be input");
+ is(events[3].type, "keyup",
+ description + "events[3] 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.
+ SpecialPowers.gc();
+
+ 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 shoudn'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 });
+
+ SpecialPowers.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");
+
+ SpecialPowers.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)");
+
+ SpecialPowers.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 });
+
+ SpecialPowers.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");
+
+ SpecialPowers.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");
+
+ SpecialPowers.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.indexOf(aEvent.type) >= 0) {
+ 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");
+ if (SpecialPowers.getBoolPref("dom.keyboardevent.code.enabled")) {
+ 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 {
+ SpecialPowers.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");
+
+ SpecialPowers.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();
+ SpecialPowers.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");
+ if (SpecialPowers.getBoolPref("dom.keyboardevent.code.enabled")) {
+ is(aEvent.code, aCode,
+ desc + ", .code value is wrong");
+ }
+ is(aEvent.altKey, aModifiers.indexOf("Alt") >= 0,
+ desc + ", .altKey value is wrong");
+ is(aEvent.ctrlKey, aModifiers.indexOf("Control") >= 0,
+ desc + ", .ctrlKey value is wrong");
+ is(aEvent.metaKey, aModifiers.indexOf("Meta") >= 0,
+ desc + ", .metaKey value is wrong");
+ is(aEvent.shiftKey, aModifiers.indexOf("Shift") >= 0,
+ desc + ", .shiftKey value is wrong");
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aEvent.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0,
+ desc + ", .getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
+ }
+ }
+
+ function checkAllTIPModifiers(aTestDesc, aModifiers)
+ {
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(TIP.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0,
+ 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)
+ {
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aTIP.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0,
+ 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(aNextTest)
+{
+ var description = "runUnloadTests1(): ";
+
+ var TIP1 = createTIP();
+ ok(TIP1.beginInputTransactionForTests(childWindow),
+ description + "TIP1.beginInputTransactionForTests() should succeed");
+
+ var oldSrc = iframe.src;
+ var parentWindow = window;
+
+ iframe.addEventListener("load", function (aEvent) {
+ ok(true, description + "dummy page is loaded");
+ iframe.removeEventListener("load", arguments.callee, true);
+ 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.
+ iframe.removeEventListener("load", arguments.callee, true);
+ childWindow = iframe.contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ setTimeout(aNextTest, 0);
+ }, true);
+
+ // The composition should be committed internally. So, another TIP should
+ // be able to steal the rights to using TextEventDispatcher.
+ var 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;
+ }, 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(aNextTest)
+{
+ var description = "runUnloadTests2(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(childWindow),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var oldSrc = iframe.src;
+ var parentWindow = window;
+
+ iframe.addEventListener("load", function (aEvent) {
+ ok(true, description + "dummy page is loaded");
+ iframe.removeEventListener("load", arguments.callee, true);
+ 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.
+ iframe.removeEventListener("load", arguments.callee, true);
+ childWindow = iframe.contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ setTimeout(aNextTest, 0);
+ }, 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;
+ }, 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>";
+}
+
+function runCallbackTests(aForTests)
+{
+ var description = "runCallbackTests(aForTests=" + aForTests + "): ";
+
+ input.value = "";
+ input.focus();
+ input.blur();
+
+ var TIP = createTIP();
+ var notifications = [];
+ function callback(aTIP, aNotification)
+ {
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ }
+ if (aTIP == TIP) {
+ notifications.push(aNotification);
+ }
+ return true;
+ }
+
+ function dumpUnexpectedNotifications(aExpectedCount)
+ {
+ if (notifications.length <= aExpectedCount) {
+ return;
+ }
+ for (var i = aExpectedCount; i < notifications.length; i++) {
+ ok(false,
+ description + "Unexpected notification: " + notifications[i].type);
+ }
+ }
+
+ 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();
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ notifications = [];
+ synthesizeMouseAtCenter(input, {});
+ is(notifications.length, 1,
+ description + "synthesizeMouseAtCenter(input, {}) during composition should cause a notification");
+ is(notifications[0].type, "request-to-commit",
+ description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ var 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);
+}
+
+function runTests()
+{
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+
+ runBeginInputTransactionMethodTests();
+ runReleaseTests();
+ runCompositionTests();
+ runCompositionWithKeyEventTests();
+ runConsumingKeydownBeforeCompositionTests();
+ runKeyTests();
+ runErrorTests();
+ runCommitCompositionTests();
+ runCallbackTests(false);
+ runCallbackTests(true);
+ runUnloadTests1(function () {
+ runUnloadTests2(function () {
+ finish();
+ });
+ });
+}
+
+]]>
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/window_swapFrameLoaders.xul b/dom/base/test/chrome/window_swapFrameLoaders.xul
new file mode 100644
index 000000000..3deae2e63
--- /dev/null
+++ b/dom/base/test/chrome/window_swapFrameLoaders.xul
@@ -0,0 +1,258 @@
+<?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" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+
+ <script type="application/javascript"><![CDATA[
+ ["SimpleTest", "SpecialPowers", "info", "is", "ok"].forEach(key => {
+ window[key] = window.opener[key];
+ })
+ const { interfaces: Ci } = Components;
+
+ 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"],
+ ["xul", "html", "remote"],
+ ["html", "xul", "remote"],
+ ["html", "html", "remote"],
+ ];
+
+ const HEIGHTS = [
+ 200,
+ 400
+ ];
+
+ function frameScript() {
+ 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;
+ }
+ }
+ });
+ }
+
+ function* addFrame(type, remote, height) {
+ let frame = document.createElementNS(NS[type], TAG[type]);
+ frame.setAttribute("remote", remote);
+ if (remote && type == "xul") {
+ frame.setAttribute("style", "-moz-binding: none;");
+ }
+ 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;
+ yield once(mm, "test:load");
+ return frame;
+ }
+
+ add_task(function*() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { "set": [["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]] },
+ resolve);
+ });
+ });
+
+ add_task(function*() {
+ for (let scenario of SCENARIOS) {
+ let [ typeA, typeB, remote ] = scenario;
+ remote = !!remote;
+ let heightA = HEIGHTS[0];
+ info(`Adding frame A, type ${typeA}, remote ${remote}, height ${heightA}`);
+ let frameA = yield addFrame(typeA, remote, heightA);
+
+ let heightB = HEIGHTS[1];
+ info(`Adding frame B, type ${typeB}, remote ${remote}, height ${heightB}`);
+ let frameB = yield addFrame(typeB, remote, heightB);
+
+ let frameScriptFactory = function(name) {
+ 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,
+ });
+ });
+ }`;
+ }
+
+ // 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 } ] = yield 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 } ] = yield inflightB;
+ is(pongB, "B", "Frame B message manager gets reply B before swap");
+ }
+
+ // Check height before swap
+ {
+ if (frameA.getContentDimensions) {
+ let { height } = yield frameA.getContentDimensions();
+ is(height, heightA, "Frame A's content height is 200px before swap");
+ }
+ if (frameB.getContentDimensions) {
+ let { height } = yield frameB.getContentDimensions();
+ is(height, heightB, "Frame B's content height is 400px before swap");
+ }
+ }
+
+ // Ping after swap using message managers acquired before
+ {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ info("swapFrameLoaders");
+ frameA.swapFrameLoaders(frameB);
+
+ let inflightA = once(mmA, "pong");
+ let inflightB = once(mmB, "pong");
+
+ info("Ping message manager for frame A");
+ mmA.sendAsyncMessage("ping");
+ let [ { data: pongA } ] = yield 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 } ] = yield 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 } = yield frameA.getContentDimensions();
+ is(height, heightB, "Frame A's content height is 400px after swap");
+ }
+ if (frameB.getContentDimensions) {
+ let { height } = yield 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 } ] = yield 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 } ] = yield inflightB;
+ is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap");
+ }
+
+ // Verify browser API frame scripts destroyed if swapped out of browser frame
+ if (frameA.hasAttribute("mozbrowser") != frameB.hasAttribute("mozbrowser")) {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ let inflightA = once(mmA, "check-browser-api");
+ let inflightB = once(mmB, "check-browser-api");
+
+ info("Check browser API for frame A");
+ mmA.sendAsyncMessage("check-browser-api");
+ let [ { data: apiA } ] = yield inflightA;
+ if (frameA.hasAttribute("mozbrowser")) {
+ ok(apiA.exists && apiA.running, "Frame A browser API exists and is running");
+ } else {
+ ok(apiA.exists && !apiA.running, "Frame A browser API did exist but is now destroyed");
+ }
+
+ info("Check browser API for frame B");
+ mmB.sendAsyncMessage("check-browser-api");
+ let [ { data: apiB } ] = yield inflightB;
+ if (frameB.hasAttribute("mozbrowser")) {
+ ok(apiB.exists && apiB.running, "Frame B browser API exists and is running");
+ } else {
+ ok(apiB.exists && !apiB.running, "Frame B browser API did exist but is now destroyed");
+ }
+ } else {
+ info("Frames have matching mozbrowser state, skipping browser API destruction check");
+ }
+
+ frameA.remove();
+ frameB.remove();
+ }
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/copypaste.js b/dom/base/test/copypaste.js
new file mode 100644
index 000000000..6fa3cb909
--- /dev/null
+++ b/dom/base/test/copypaste.js
@@ -0,0 +1,412 @@
+/* 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.parentNode.removeChild(e);
+ g.removeAllRanges();
+ g.addRange(l);
+ }, 0)
+}
+
+function getLoadContext() {
+ var Ci = SpecialPowers.Ci;
+ return SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function testCopyPaste (isXHTML) {
+ var suppressUnicodeCheckIfHidden = !!isXHTML;
+ var suppressHTMLCheck = !!isXHTML;
+
+ var webnav = SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+
+ var docShell = webnav.QueryInterface(SpecialPowers.Ci.nsIDocShell);
+
+ var documentViewer = docShell.contentViewer
+ .QueryInterface(SpecialPowers.Ci.nsIContentViewerEdit);
+
+ var clipboard = SpecialPowers.Services.clipboard;
+
+ var textarea = SpecialPowers.wrap(document.getElementById('input'));
+
+ function copySelectionToClipboard(suppressUnicodeCheck) {
+ documentViewer.copySelection();
+ if (!suppressUnicodeCheck)
+ ok(clipboard.hasDataMatchingFlavors(["text/unicode"], 1,1), "check text/unicode");
+ if (!suppressHTMLCheck)
+ ok(clipboard.hasDataMatchingFlavors(["text/html"], 1,1), "check text/html");
+ }
+ function clear(node, suppressUnicodeCheck) {
+ textarea.blur();
+ clipboard.emptyClipboard(1);
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ }
+ function copyToClipboard(node, suppressUnicodeCheck) {
+ clear();
+ var r = document.createRange();
+ r.selectNode(node);
+ window.getSelection().addRange(r);
+ 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);
+ }
+ function copyRangeToClipboard(startNode,startIndex,endNode,endIndex,suppressUnicodeCheck) {
+ clear();
+ addRange(startNode,startIndex,endNode,endIndex);
+ copySelectionToClipboard(suppressUnicodeCheck);
+ }
+ function copyChildrenToClipboard(id) {
+ clear();
+ window.getSelection().selectAllChildren(document.getElementById(id));
+ 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.indexOf("Win") >= 0) {
+ // Windows has extra content.
+ var expectedValue = "<html><body>\n<!--StartFragment-->" +
+ expected.replace(/\n/g, '\n') +
+ "<!--EndFragment-->\n</body>\n</html>";
+ }
+ 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");
+ }
+ function testEmptyChildren(id) {
+ copyChildrenToClipboard(id);
+ testSelectionToString("");
+ testClipboardValue("text/unicode", null);
+ testClipboardValue("text/html", null);
+ testPasteText("");
+ }
+
+ copyChildrenToClipboard("draggable");
+ testSelectionToString("This is a draggable bit of text.");
+ testClipboardValue("text/unicode",
+ "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.");
+
+ copyChildrenToClipboard("alist");
+ testSelectionToString(" bla\n\n foo\n bar\n\n");
+ testClipboardValue("text/unicode", " 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");
+
+ copyChildrenToClipboard("blist");
+ testSelectionToString(" mozilla\n\n foo\n bar\n\n");
+ testClipboardValue("text/unicode", " 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");
+
+ copyChildrenToClipboard("clist");
+ testSelectionToString(" mzla\n\n foo\n bazzinga!\n bar\n\n");
+ testClipboardValue("text/unicode", " 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");
+
+ copyChildrenToClipboard("div4");
+ testSelectionToString(" Tt t t ");
+ testClipboardValue("text/unicode", " 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 ");
+
+ copyChildrenToClipboard("div5");
+ testSelectionToString(" T ");
+ testClipboardValue("text/unicode", " 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 ");
+
+ copyRangeToClipboard($("div6").childNodes[0],0, $("div6").childNodes[1],1,suppressUnicodeCheckIfHidden);
+ testSelectionToString("");
+// START Disabled due to bug 564688
+if (false) {
+ testClipboardValue("text/unicode", "");
+ testClipboardValue("text/html", "");
+}
+// END Disabled due to bug 564688
+ testInnerHTML("div6", "div6");
+
+ copyRangeToClipboard($("div7").childNodes[0],0, $("div7").childNodes[0],4,suppressUnicodeCheckIfHidden);
+ testSelectionToString("");
+// START Disabled due to bug 564688
+if (false) {
+ testClipboardValue("text/unicode", "");
+ testClipboardValue("text/html", "");
+}
+// END Disabled due to bug 564688
+ testInnerHTML("div7", "div7");
+
+ copyRangeToClipboard($("div8").childNodes[0],0, $("div8").childNodes[0],4,suppressUnicodeCheckIfHidden);
+ testSelectionToString("");
+// START Disabled due to bug 564688
+if (false) {
+ testClipboardValue("text/unicode", "");
+ testClipboardValue("text/html", "");
+}
+// END Disabled due to bug 564688
+ testInnerHTML("div8", "div8");
+
+ copyRangeToClipboard($("div9").childNodes[0],0, $("div9").childNodes[0],4,suppressUnicodeCheckIfHidden);
+ testSelectionToString("div9");
+ testClipboardValue("text/unicode", "div9");
+ testHtmlClipboardValue("text/html", "div9");
+ testInnerHTML("div9", "div9");
+
+ copyToClipboard($("div10"), suppressUnicodeCheckIfHidden);
+ testSelectionToString("");
+ testInnerHTML("div10", "div10");
+
+ copyToClipboard($("div10").firstChild, suppressUnicodeCheckIfHidden);
+ testSelectionToString("");
+
+ copyRangeToClipboard($("div10").childNodes[0],0, $("div10").childNodes[0],1,suppressUnicodeCheckIfHidden);
+ testSelectionToString("");
+
+ 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); // the end of "Copy..."
+ sel.addRange(r);
+
+ r = document.createRange();
+ r.setStart(ul, 1); // before the space inside the UL
+ r.setEnd(parent, 2); // after the UL
+ sel.addRange(r);
+ copySelectionToClipboard(true);
+ testPasteHTML('contentEditable1', 'Copy1then Paste');
+
+ // 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); // after the space
+ sel.addRange(r);
+
+ r = document.createRange();
+ r.setStart(parent.childNodes[1], 0); // the start of "Copy..."
+ r.setEnd(parent, 2);
+ sel.addRange(r);
+ copySelectionToClipboard(true);
+ testPasteHTML('contentEditable2', 'Copy2then Paste');
+
+ // 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); // after the space
+ sel.addRange(r);
+
+ r = document.createRange();
+ r.setStart(parent.childNodes[1], 0); // the start of "Copy..."
+ r.setEnd(parent, 2);
+ sel.addRange(r);
+ copySelectionToClipboard(true);
+ testPasteHTML('contentEditable3', '<ul id="ul3"><li>\n<br></li></ul>Copy3then Paste');
+
+ // 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);
+ 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);
+ 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);
+
+ r = document.createRange();
+ r.setStart(e2, 1);
+ r.setEnd(t3, 0);
+ sel.addRange(r);
+ copySelectionToClipboard(true);
+ testPasteHTML('contentEditable6', '<span id="1127835crash1"></span><div id="1127835crash2"><div>\n</div></div><br>');
+ }
+
+ // ============ copy/paste test from/to a textarea
+
+ var val = "1\n 2\n 3";
+ textarea.value=val;
+ textarea.select();
+ textarea.editor.copy();
+
+ textarea.value="";
+ textarea.editor.paste(1);
+ is(textarea.value, val);
+ textarea.value="";
+
+ // ============ NOSCRIPT should not be copied
+
+ copyChildrenToClipboard("div13");
+ testSelectionToString("__");
+ testClipboardValue("text/unicode", "__");
+ testHtmlClipboardValue("text/html", "<div id=\"div13\">__</div>");
+ testPasteText("__");
+
+ // ============ converting cell boundaries to tabs in tables
+
+ copyToClipboard($("tr1"));
+ testClipboardValue("text/unicode", "foo\tbar");
+
+ if (!isXHTML) {
+ // ============ spanning multiple rows
+
+ copyRangeToClipboard($("tr2"),0,$("tr3"),0);
+ testClipboardValue("text/unicode", "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);
+ copySelectionToClipboard();
+ testClipboardValue("text/unicode", "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
+
+ copyRangeToClipboard($("div11").childNodes[0],0, $("div11").childNodes[1],2);
+ testClipboardValue("text/unicode", "Xdiv11");
+ testHtmlClipboardValue("text/html", "<div><p>X<span>div</span>11</p></div>");
+ setTimeout(function(){testSelectionToString("div11")},0);
+
+ setTimeout(function(){
+ copyRangeToClipboard($("div12").childNodes[0],0, $("div12").childNodes[1],2);
+ testClipboardValue("text/unicode", "Xdiv12");
+ testHtmlClipboardValue("text/html", "<div><p>X<span>div</span>12</p></div>");
+ setTimeout(function(){
+ testSelectionToString("div12");
+ setTimeout(SimpleTest.finish,0);
+ },0);
+ },0);
+}
diff --git a/dom/base/test/create_file_objects.js b/dom/base/test/create_file_objects.js
new file mode 100644
index 000000000..273adc2cd
--- /dev/null
+++ b/dom/base/test/create_file_objects.js
@@ -0,0 +1,10 @@
+Components.utils.importGlobalProperties(['File']);
+
+addMessageListener("create-file-objects", function(message) {
+ let files = []
+ for (fileName of message.fileNames) {
+ files.push(File.createFromFileName(fileName));
+ }
+
+ sendAsyncMessage("created-file-objects", files);
+});
diff --git a/dom/base/test/delayedServerEvents.sjs b/dom/base/test/delayedServerEvents.sjs
new file mode 100644
index 000000000..c2d2f5dd9
--- /dev/null
+++ b/dom/base/test/delayedServerEvents.sjs
@@ -0,0 +1,42 @@
+// 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 == 0) {
+ timer.cancel();
+ resp.finish();
+ timer = null;
+ resp = null;
+ return;
+ }
+
+ resp.write(strings_to_send[0]);
+ strings_to_send = strings_to_send.splice(1, strings_to_send.length - 1);
+}
+
+function handleRequest(request, response)
+{
+ var b = 0;
+ for (var i=0; i < strings_to_send.length; ++i) {
+ b += strings_to_send[i].length;
+ }
+
+ response.seizePower();
+ response.write("HTTP/1.1 200 OK\r\n")
+ response.write("Content-Length: " + b + "\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/empty.html b/dom/base/test/empty.html
new file mode 100644
index 000000000..e69de29bb
--- /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 000000000..856e3b1af
--- /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 000000000..6a63b5341
--- /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_redirect.resource b/dom/base/test/eventsource_redirect.resource
new file mode 100644
index 000000000..d073527bf
--- /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 000000000..eb79e2f81
--- /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 000000000..1eb4081ac
--- /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 000000000..6a63b5341
--- /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/fake_plugin.tst b/dom/base/test/fake_plugin.tst
new file mode 100644
index 000000000..b3d41aed8
--- /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/file_audioLoop.html b/dom/base/test/file_audioLoop.html
new file mode 100644
index 000000000..d680c9a58
--- /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_base_xbl.xml b/dom/base/test/file_base_xbl.xml
new file mode 100644
index 000000000..bcd226f33
--- /dev/null
+++ b/dom/base/test/file_base_xbl.xml
@@ -0,0 +1,9 @@
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+<binding id="test">
+ <content>
+ <svg:g><svg:circle><xhtml:div xml:base="#shesellsseashellsbytheseashore"/></svg:circle><children/></svg:g>
+ </content>
+ </binding>
+</bindings>
diff --git a/dom/base/test/file_blobURL_expiring.html b/dom/base/test/file_blobURL_expiring.html
new file mode 100644
index 000000000..a1ae72570
--- /dev/null
+++ b/dom/base/test/file_blobURL_expiring.html
@@ -0,0 +1,4 @@
+<script>
+var blob = new Blob([123]);
+parent.postMessage(URL.createObjectURL(blob), "*");
+</script>
diff --git a/dom/base/test/file_bug1008126_worker.js b/dom/base/test/file_bug1008126_worker.js
new file mode 100644
index 000000000..4c8b8e6dd
--- /dev/null
+++ b/dom/base/test/file_bug1008126_worker.js
@@ -0,0 +1,176 @@
+/**
+ * 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: msg });
+}
+
+function is(a, b, msg) {
+ postMessage({type: "status", status: a === b, msg: msg });
+}
+
+function checkData(xhr, data, mapped, cb) {
+ var ct = xhr.getResponseHeader("Content-Type");
+ if (mapped) {
+ ok(ct.indexOf("mem-mapped") != -1, "Data is memory-mapped");
+ } else {
+ ok(ct.indexOf("mem-mapped") == -1, "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;
+ }
+
+ function test_chunked_arraybuffer() {
+ ok(true, "Test chunked arraybuffer");
+
+ var lastIndex = 0;
+ xhr.onprogress = function(event) {
+ if (xhr.response) {
+ var buf = new Uint8Array(xhr.response);
+ var allMatched = true;
+ // The content of data cycles from 0 to 9 (i.e. 01234567890123......).
+ for (var i = 0; i < buf.length; i++) {
+ if (String.fromCharCode(buf[i]) != lastIndex % 10) {
+ allMatched = false;
+ break;
+ }
+ lastIndex++;
+ }
+ ok(allMatched, "Data chunk is correct. Loaded " +
+ event.loaded + "/" + event.total + " bytes.");
+ }
+ };
+ xhr.onload = runTests;
+ xhr.open("GET", makeJarURL(gEntry3), true);
+ xhr.responseType = "moz-chunked-arraybuffer";
+ xhr.send();
+ }
+
+ 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_chunked_arraybuffer,
+ 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 000000000..71a0f5c3e
--- /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 000000000..3dec89367
--- /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 000000000..a245175ae
--- /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 000000000..f282e1eeb
--- /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 000000000..2095595fb
--- /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_bug1198095.js b/dom/base/test/file_bug1198095.js
new file mode 100644
index 000000000..40058631a
--- /dev/null
+++ b/dom/base/test/file_bug1198095.js
@@ -0,0 +1,26 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+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();
+
+ var domFile = File.createFromNsIFile(testFile);
+ return domFile;
+}
+
+addMessageListener("file.open", function (message) {
+ sendAsyncMessage("file.opened", createFileWithData(message));
+});
+
+addMessageListener("file.modify", function (message) {
+ sendAsyncMessage("file.modified", createFileWithData(message));
+});
diff --git a/dom/base/test/file_bug1250148.sjs b/dom/base/test/file_bug1250148.sjs
new file mode 100644
index 000000000..a85347896
--- /dev/null
+++ b/dom/base/test/file_bug1250148.sjs
@@ -0,0 +1,60 @@
+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(s) {
+ if (s.indexOf('=') >= 0) {
+ let [name, value] = s.split('=');
+ contentTypeParams[name] = value;
+ }
+ else {
+ contentTypeParams[''] = s;
+ }
+ });
+
+ 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(s) {
+ // We're assuming UTF8 for now
+ let [name, value] = s.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: headers, body: body});
+ });
+ }
+ }
+
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ response.write(utf8encode(JSON.stringify(result)));
+}
diff --git a/dom/base/test/file_bug1263696_frame_fail.html b/dom/base/test/file_bug1263696_frame_fail.html
new file mode 100644
index 000000000..b45561aa6
--- /dev/null
+++ b/dom/base/test/file_bug1263696_frame_fail.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/REC-html401-19991224/strict.dtd">
+<html>
+ <head>
+ <title>Bug 1263696 - iframe that should not be loaded</title>
+ </head>
+ <body>
+ <script>
+ parent.SimpleTest.ok(false, "this iframe should not load");
+ </script>
+ </body>
+</html>
diff --git a/dom/base/test/file_bug1263696_frame_pass.html b/dom/base/test/file_bug1263696_frame_pass.html
new file mode 100644
index 000000000..7f49d6541
--- /dev/null
+++ b/dom/base/test/file_bug1263696_frame_pass.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/REC-html401-19991224/strict.dtd">
+<html>
+ <head>
+ <title>Bug 1263696 - iframe that should be loaded</title>
+ </head>
+ <body>
+ <script>
+ parent.index = parent.index + 1;
+ parent.SimpleTest.ok(true, "this iframe should load");
+ </script>
+ </body>
+</html>
diff --git a/dom/base/test/file_bug1268962.sjs b/dom/base/test/file_bug1268962.sjs
new file mode 100644
index 000000000..42b1ecef3
--- /dev/null
+++ b/dom/base/test/file_bug1268962.sjs
@@ -0,0 +1,90 @@
+// 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 000000000..62bef3044
--- /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_bug28293.sjs b/dom/base/test/file_bug28293.sjs
new file mode 100644
index 000000000..0954ec105
--- /dev/null
+++ b/dom/base/test/file_bug28293.sjs
@@ -0,0 +1,5 @@
+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 000000000..d328051e7
--- /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 000000000..709fbbc61
--- /dev/null
+++ b/dom/base/test/file_bug326337_inner.html
@@ -0,0 +1,40 @@
+<!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.wrap(parent).location.hash = "#done";
+ }
+ }
+ xhr.send(null);
+}
+
+function check(attr) {
+ if (attr != "yes") {
+ SpeciaPowers.wrap(parent).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 000000000..9d63f7263
--- /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 000000000..c432052b8
--- /dev/null
+++ b/dom/base/test/file_bug357450.js
@@ -0,0 +1,64 @@
+/** 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 000000000..7702cc561
--- /dev/null
+++ b/dom/base/test/file_bug416317.xhtml
@@ -0,0 +1,1476 @@
+<?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">
+ .blox16:not(.blox15[foo="blox14"]) { background-color: red; }
+
+ /* Tests from http://www.w3.org/Style/CSS/Test/CSS3/Selectors/20060307/html/index.html */
+ div:not(:not(div)) { background-color : red }
+
+ div, { background: red; }
+ .5cm { background: red; }
+ foo &amp; address, p { 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[
+ window.onload = function() {
+ if (window.parent && window.parent.SpecialPowers) {
+ window.parent.SpecialPowers.pushPrefEnv(
+ { 'set': [[ "layout.css.scope-pseudo.enabled", true]] },
+ doTest);
+ } else {
+ doTest();
+ }
+ }
+
+ function doTest(){
+ if ( window.location.hash.indexOf("target") == -1 )
+ 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 ( var 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 ( var 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.parentNode.removeChild( root );
+
+ 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 ( var 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 ( var 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 == 0 && 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: function(){ 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, null);
+ 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.indexOf("(255, 0, 0") == -1 && bg.indexOf("#ff0000") == -1 && bg.indexOf("red") == -1;
+ //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 000000000..db0773441
--- /dev/null
+++ b/dom/base/test/file_bug426646-1.html
@@ -0,0 +1,36 @@
+<html><head>
+<title>Bug 426646, Using location.replace breaks iframe history</title>
+<script type="text/javascript">
+var url1 = "data:text/html;charset=utf-8,1st%20page";
+
+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 = 'data:text/html;charset=utf-8,2nd%20page';
+ 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 000000000..1f8fae950
--- /dev/null
+++ b/dom/base/test/file_bug426646-2.html
@@ -0,0 +1,64 @@
+<html><head>
+<title>Bug 426646, Using location.replace breaks iframe history</title>
+<script type="text/javascript">
+var url1 = "data:text/html;charset=utf-8,1st%20page";
+
+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 = 'data:text/html;charset=utf-8,2nd%20page';
+}
+
+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 000000000..b88701ece
--- /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 000000000..75c60b779
--- /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 000000000..84beffdef
--- /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 000000000..b0d36f4ca
--- /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 000000000..09b46ca4e
--- /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 000000000..5a78292fb
--- /dev/null
+++ b/dom/base/test/file_bug503473-frame.sjs
@@ -0,0 +1,23 @@
+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 000000000..917208b4b
--- /dev/null
+++ b/dom/base/test/file_bug503481.sjs
@@ -0,0 +1,43 @@
+// '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: function(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 000000000..6b34bc47b
--- /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_bug541937.html b/dom/base/test/file_bug541937.html
new file mode 100644
index 000000000..93056754f
--- /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 000000000..f4e5f3d32
--- /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 000000000..4b91ddafd
--- /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 000000000..4a21b817e
--- /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 000000000..dd0948b08
--- /dev/null
+++ b/dom/base/test/file_bug590812-ref.xhtml
@@ -0,0 +1,3 @@
+<out><div id="top" class="highlight" 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><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></div></out>
diff --git a/dom/base/test/file_bug590812.xml b/dom/base/test/file_bug590812.xml
new file mode 100644
index 000000000..759d5066c
--- /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 000000000..4432b01d3
--- /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 000000000..c39ff7219
--- /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 000000000..0363654c0
--- /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 000000000..231b4357d
--- /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 000000000..16611726c
--- /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 000000000..c11ae68f2
--- /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 000000000..2bd5b43e5
--- /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 000000000..58c638283
--- /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 000000000..4a75777d9
--- /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 000000000..8619f10b1
--- /dev/null
+++ b/dom/base/test/file_bug622088.sjs
@@ -0,0 +1,6 @@
+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 000000000..e89273d89
--- /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 000000000..45ad03d7f
--- /dev/null
+++ b/dom/base/test/file_bug675121.sjs
@@ -0,0 +1,15 @@
+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 000000000..58f82cdda
--- /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 000000000..e0d4fce5d
--- /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 000000000..7536004bb
--- /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 000000000..e0d4fce5d
--- /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 000000000..e812a1e37
--- /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 000000000..1b1456d48
--- /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 000000000..e0d4fce5d
--- /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 000000000..b83f60b2f
--- /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 000000000..2d559c3ae
--- /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_common.js b/dom/base/test/file_bug704320_preload_common.js
new file mode 100644
index 000000000..efae9f498
--- /dev/null
+++ b/dom/base/test/file_bug704320_preload_common.js
@@ -0,0 +1,34 @@
+// 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_noreuse.html b/dom/base/test/file_bug704320_preload_noreuse.html
new file mode 100644
index 000000000..908240172
--- /dev/null
+++ b/dom/base/test/file_bug704320_preload_noreuse.html
@@ -0,0 +1,33 @@
+<!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">
+ <script type="text/javascript" src="file_bug704320_preload_common.js"></script>
+
+ <script type="text/javascript">
+ // mess with parser speculation's choice of referrer policy
+ document.write("<meta name='referrer' content='origin'>");
+ </script>
+
+ <!-- preload happens with full referrer, but real load should happen with origin -->
+ <script src="/tests/dom/base/test/bug704320_counter.sjs?type=js"
+ onload="incrementLoad('script');"></script>
+
+ <!-- preload happens with full referrer, but real load should happen with origin -->
+ <link rel="stylesheet"
+ href="/tests/dom/base/test/bug704320_counter.sjs?type=css"
+ onload="incrementLoad('link');"/>
+
+</head>
+<body>
+
+ <!-- preload happens with full referrer, but real load should happen with origin -->
+<img src="/tests/dom/base/test/bug704320_counter.sjs?type=img"
+ onload="incrementLoad('img');"
+ onerror="postfail('image load caused an error in noreuse test');"/>
+</body>
+</html>
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 000000000..5643e3d8d
--- /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 000000000..09ed05d6f
--- /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 000000000..6d9590305
--- /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 000000000..40852d8ef
--- /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 000000000..eb28f6bb7
--- /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 000000000..86899201d
--- /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 000000000..a90e6aeeb
--- /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_bug769117.html b/dom/base/test/file_bug769117.html
new file mode 100644
index 000000000..424f8dff1
--- /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 000000000..3b18e512d
--- /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 000000000..84c674422
--- /dev/null
+++ b/dom/base/test/file_bug787778.sjs
@@ -0,0 +1,8 @@
+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_bug804395.jar b/dom/base/test/file_bug804395.jar
new file mode 100644
index 000000000..f8359f65f
--- /dev/null
+++ b/dom/base/test/file_bug804395.jar
Binary files differ
diff --git a/dom/base/test/file_bug869432.eventsource b/dom/base/test/file_bug869432.eventsource
new file mode 100644
index 000000000..de92d4dd8
--- /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 000000000..b8db77c58
--- /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 000000000..ce87072f3
--- /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/tests/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 000000000..183dabe25
--- /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 000000000..bbd417a16
--- /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: threw, sandboxed: sandboxed }, "*");
+</script>
diff --git a/dom/base/test/file_bug945152.jar b/dom/base/test/file_bug945152.jar
new file mode 100644
index 000000000..eb732980d
--- /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 000000000..bc0d79ba5
--- /dev/null
+++ b/dom/base/test/file_bug945152_worker.js
@@ -0,0 +1,103 @@
+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: msg });
+}
+
+function is(a, b, msg) {
+ postMessage({type: 'status', status: a === b, msg: 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.indexOf("mem-mapped") != -1, "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.indexOf("mem-mapped") != -1, "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.indexOf("mem-mapped") == -1, "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 000000000..e48386d97
--- /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_empty.html b/dom/base/test/file_empty.html
new file mode 100644
index 000000000..495c23ec8
--- /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 000000000..7a5a595b9
--- /dev/null
+++ b/dom/base/test/file_explicit_user_agent.sjs
@@ -0,0 +1,8 @@
+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_general_document.html b/dom/base/test/file_general_document.html
new file mode 100644
index 000000000..2539669de
--- /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_htmlserializer_1.html b/dom/base/test/file_htmlserializer_1.html
new file mode 100644
index 000000000..9576b5d7d
--- /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 000000000..848167c62
--- /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 000000000..09f80467e
--- /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 000000000..8194b8b41
--- /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 000000000..f0864c940
--- /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 000000000..94f67547e
--- /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 000000000..9c749721b
--- /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 000000000..8194b8b41
--- /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 000000000..aba95b62c
--- /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 000000000..c646f2696
--- /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 000000000..f533e6679
--- /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 000000000..97c162515
--- /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 000000000..fc641e3f9
--- /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 000000000..2156b1610
--- /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 000000000..56ac95dfd
--- /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_2_enthtml.html b/dom/base/test/file_htmlserializer_2_enthtml.html
new file mode 100644
index 000000000..09bb761e9
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_2_enthtml.html
@@ -0,0 +1,47 @@
+<!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 &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_entw3c.html b/dom/base/test/file_htmlserializer_2_entw3c.html
new file mode 100644
index 000000000..09bb761e9
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_2_entw3c.html
@@ -0,0 +1,47 @@
+<!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 &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_latin1.html b/dom/base/test/file_htmlserializer_2_latin1.html
new file mode 100644
index 000000000..b41e9964f
--- /dev/null
+++ b/dom/base/test/file_htmlserializer_2_latin1.html
@@ -0,0 +1,34 @@
+<!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 &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_htmlserializer_ipv6.html b/dom/base/test/file_htmlserializer_ipv6.html
new file mode 100644
index 000000000..298493e71
--- /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 000000000..675a406d8
--- /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_lock_orientation.html b/dom/base/test/file_lock_orientation.html
new file mode 100644
index 000000000..e370ab91c
--- /dev/null
+++ b/dom/base/test/file_lock_orientation.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="orientationcommon.js"></script>
+<script>
+window.addEventListener("message", function(event) {
+ var p = specialPowersLock(event.data);
+ p.then(function() {
+ event.source.postMessage("success", "*");
+ }).catch(function(err) {
+ event.source.postMessage("error", "*");
+ });
+}, false);
+</script>
diff --git a/dom/base/test/file_messagemanager_unload.html b/dom/base/test/file_messagemanager_unload.html
new file mode 100644
index 000000000..3df810d50
--- /dev/null
+++ b/dom/base/test/file_messagemanager_unload.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <iframe id="frame" src="about:robots"></iframe>
+ </body>
+</html>
diff --git a/dom/base/test/file_mozfiledataurl_audio.ogg b/dom/base/test/file_mozfiledataurl_audio.ogg
new file mode 100644
index 000000000..88b2c1b5b
--- /dev/null
+++ b/dom/base/test/file_mozfiledataurl_audio.ogg
Binary files differ
diff --git a/dom/base/test/file_mozfiledataurl_doc.html b/dom/base/test/file_mozfiledataurl_doc.html
new file mode 100644
index 000000000..763b20a0f
--- /dev/null
+++ b/dom/base/test/file_mozfiledataurl_doc.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<html>
+<body>
+<p>This here is a document!</p>
+<img id=img src="file_mozfiledataurl_img.jpg">
+</html>
diff --git a/dom/base/test/file_mozfiledataurl_img.jpg b/dom/base/test/file_mozfiledataurl_img.jpg
new file mode 100644
index 000000000..dcd99b967
--- /dev/null
+++ b/dom/base/test/file_mozfiledataurl_img.jpg
Binary files differ
diff --git a/dom/base/test/file_mozfiledataurl_inner.html b/dom/base/test/file_mozfiledataurl_inner.html
new file mode 100644
index 000000000..960be3d9f
--- /dev/null
+++ b/dom/base/test/file_mozfiledataurl_inner.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+<script type="application/javascript;version=1.8">
+var img;
+var audio;
+var iframe;
+
+addEventListener("message", function(e) {
+ mess = JSON.parse(e.data);
+
+ if ("img" in mess)
+ img.src = mess.img;
+ else if ("audio" in mess)
+ audio.src = mess.audio
+ else if ("iframe" in mess)
+ iframe.src = mess.iframe;
+ else if ("xhr" in mess) {
+ let xhr = new XMLHttpRequest();
+ xhr.onerror = function() {
+ sendItUp({ didError: true });
+ }
+ xhr.onload = function() {
+ sendItUp({ text: xhr.responseText });
+ }
+ try {
+ xhr.open("GET", mess.xhr);
+ xhr.send();
+ }
+ catch (ex) {
+ sendItUp({ didThrow: true });
+ }
+ }
+
+}, false);
+
+function sendItUp(obj) {
+ window.parent.postMessage(JSON.stringify(obj), "*");
+}
+
+function audioNotifyParent(e) {
+ sendItUp({ type: e.type });
+}
+
+function imgNotifyParent(e) {
+ sendItUp({ type: e.type,
+ width: e.target.width,
+ height: e.target.height });
+}
+
+function iframeNotifyParent(e) {
+ res = { type: e.type };
+ try {
+ res.text = e.target.contentDocument.getElementsByTagName("p")[0].textContent;
+ } catch (ex) {}
+ try {
+ res.imgWidth = e.target.contentDocument.getElementById("img").width;
+ } catch (ex) {}
+
+ sendItUp(res);
+}
+
+onload = function() {
+ img = document.getElementById('img');
+ img.onerror = img.onload = imgNotifyParent;
+ iframe = document.getElementById('iframe');
+ iframe.onerror = iframe.onload = iframeNotifyParent;
+ audio = document.getElementById('audio');
+ audio.onerror = audio.onloadeddata = audioNotifyParent;
+}
+
+</script>
+<body>
+<img id=img>
+<audio id=audio>
+<iframe id=iframe></iframe>
+</html>
diff --git a/dom/base/test/file_mozfiledataurl_text.txt b/dom/base/test/file_mozfiledataurl_text.txt
new file mode 100644
index 000000000..315338aa9
--- /dev/null
+++ b/dom/base/test/file_mozfiledataurl_text.txt
@@ -0,0 +1 @@
+Yarr, here be plaintext file, ya landlubber
diff --git a/dom/base/test/file_navigator_resolve_identity_xrays.xul b/dom/base/test/file_navigator_resolve_identity_xrays.xul
new file mode 100644
index 000000000..4b2873637
--- /dev/null
+++ b/dom/base/test/file_navigator_resolve_identity_xrays.xul
@@ -0,0 +1,30 @@
+<?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 type="application/javascript"
+ 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(Components.utils.isXrayWrapper(nav), "Should have an Xray here");
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/file_nonascii_blob_url.html b/dom/base/test/file_nonascii_blob_url.html
new file mode 100644
index 000000000..89183f461
--- /dev/null
+++ b/dom/base/test/file_nonascii_blob_url.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test blob URL for non-ascii domain</title>
+</head>
+<body>
+ <p id="result"></p>
+ <script type="application/javascript">
+
+window.onmessage = function(e) {
+ var blob = new Blob([e.data]);
+ var url = URL.createObjectURL(blob);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, false);
+ xhr.send(null);
+
+ parent.postMessage(xhr.responseText, '*');
+}
+
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/file_pluginAudio.html b/dom/base/test/file_pluginAudio.html
new file mode 100644
index 000000000..02ef8bd02
--- /dev/null
+++ b/dom/base/test/file_pluginAudio.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<embed type="application/x-test" width="200" height="200"></embed>
+<script>
+var plugin = document.querySelector("embed");
+onload = function() {
+ plugin.startAudioPlayback();
+};
+function stopAudio() {
+ plugin.stopAudioPlayback();
+}
+function pluginMuted() {
+ return plugin.audioMuted();
+}
+function toggleMuteState(muted) {
+ var Ci = SpecialPowers.Ci;
+ var utils = SpecialPowers.wrap(window).top
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.audioMuted = muted;
+}
+</script>
diff --git a/dom/base/test/file_pluginAudioNonAutoStart.html b/dom/base/test/file_pluginAudioNonAutoStart.html
new file mode 100644
index 000000000..5a5c9e0c2
--- /dev/null
+++ b/dom/base/test/file_pluginAudioNonAutoStart.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<embed type="application/x-test" width="200" height="200"></embed>
+<script>
+var plugin = document.querySelector("embed");
+
+function startAudio() {
+ plugin.startAudioPlayback();
+}
+
+function stopAudio() {
+ plugin.stopAudioPlayback();
+}
+
+function pluginMuted() {
+ return plugin.audioMuted();
+}
+
+function toggleMuteState(muted) {
+ var Ci = SpecialPowers.Ci;
+ var utils = SpecialPowers.wrap(window).top
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.audioMuted = muted;
+}
+</script>
diff --git a/dom/base/test/file_receiveMessage.html b/dom/base/test/file_receiveMessage.html
new file mode 100644
index 000000000..66f421270
--- /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_record_orientation.html b/dom/base/test/file_record_orientation.html
new file mode 100644
index 000000000..a01587507
--- /dev/null
+++ b/dom/base/test/file_record_orientation.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+var recordedOrientations = [];
+
+screen.orientation.onchange = function() {
+ recordedOrientations.push(screen.orientation.type);
+};
+
+window.addEventListener("message", function(event) {
+ if (event.data == "report") {
+ event.source.postMessage(recordedOrientations, "*");
+ }
+}, false);
+</script>
diff --git a/dom/base/test/file_restrictedEventSource.sjs b/dom/base/test/file_restrictedEventSource.sjs
new file mode 100644
index 000000000..a48eb3900
--- /dev/null
+++ b/dom/base/test/file_restrictedEventSource.sjs
@@ -0,0 +1,48 @@
+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");
+ }
+} \ No newline at end of file
diff --git a/dom/base/test/file_setname.html b/dom/base/test/file_setname.html
new file mode 100644
index 000000000..0830feb53
--- /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_simplecontentpolicy.js b/dom/base/test/file_simplecontentpolicy.js
new file mode 100644
index 000000000..1f9606c49
--- /dev/null
+++ b/dom/base/test/file_simplecontentpolicy.js
@@ -0,0 +1,73 @@
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function executeSoon(f)
+{
+ Services.tm.mainThread.dispatch(f, Ci.nsIThread.DISPATCH_NORMAL);
+}
+
+var urlSuffix = "/this/is/the/test/url";
+
+// Content policy / factory implementation for the test
+var policyID = Components.ID("{6aadacff-f1f2-46f4-a6db-6d429f884a30}");
+var policyName = "@mozilla.org/simpletestpolicy;1";
+var policy = {
+ // nsISupports implementation
+ QueryInterface:
+ XPCOMUtils.generateQI([
+ Ci.nsISupports,
+ Ci.nsIFactory,
+ Ci.nsISimpleContentPolicy]),
+
+ // nsIFactory implementation
+ createInstance: function(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad: function(contentType,
+ contentLocation,
+ requestOrigin,
+ frame,
+ isTopLevel,
+ mimeTypeGuess,
+ extra)
+ {
+ // Remember last content type seen for the test url
+ if (contentLocation.spec.endsWith(urlSuffix)) {
+ assert.ok(frame === browserElement, "correct <browser> element");
+ sendAsyncMessage("shouldLoad", {contentType: contentType, isTopLevel: isTopLevel});
+ return Ci.nsIContentPolicy.REJECT_REQUEST;
+ }
+
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+
+ shouldProcess: function() {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+}
+
+// Register content policy
+var componentManager = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+componentManager.registerFactory(policyID, "Test simple content policy", policyName, policy);
+
+var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+categoryManager.addCategoryEntry("simple-content-policy", policyName, policyName, false, true);
+
+addMessageListener("finished", () => {
+ // Unregister content policy
+ categoryManager.deleteCategoryEntry("simple-content-policy", policyName, false);
+
+ executeSoon(function() {
+ // Component must be unregistered delayed, otherwise other content
+ // policy will not be removed from the category correctly
+ componentManager.unregisterFactory(policyID, policy);
+ });
+});
+
+sendAsyncMessage("ready");
diff --git a/dom/base/test/file_timer_flood.html b/dom/base/test/file_timer_flood.html
new file mode 100644
index 000000000..dc729d7e4
--- /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_use_counter_outer.html b/dom/base/test/file_use_counter_outer.html
new file mode 100644
index 000000000..1a7eb90f7
--- /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_svg_currentScale.svg b/dom/base/test/file_use_counter_svg_currentScale.svg
new file mode 100644
index 000000000..ea9721a59
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_currentScale.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" standalone="no"?>
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<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"/>
+</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 000000000..ec8059a3d
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_fill_pattern.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<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(http://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" />
+</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 000000000..21f475314
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_data.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<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 000000000..ffc2f1bed
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<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 000000000..3b0670cd0
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<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" />
+</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 000000000..3393f4375
--- /dev/null
+++ b/dom/base/test/file_use_counter_svg_getElementById.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" standalone="no"?>
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<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>
+</svg>
diff --git a/dom/base/test/file_webaudioLoop.html b/dom/base/test/file_webaudioLoop.html
new file mode 100644
index 000000000..c255bc541
--- /dev/null
+++ b/dom/base/test/file_webaudioLoop.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script>
+var ac = new AudioContext();
+var runningPromise = new Promise(resolve => {
+ ac.onstatechange = event => {
+ if (ac.state == "running") {
+ resolve();
+ }
+ };
+});
+fetch("audio.ogg").then(response => {
+ return response.arrayBuffer();
+}).then(ab => {
+ return ac.decodeAudioData(ab);
+}).then(ab => {
+ var src = ac.createBufferSource();
+ src.buffer = ab;
+ src.loop = true;
+ src.loopStart = 0;
+ src.loopEnd = ab.duration;
+ src.start();
+ src.connect(ac.destination);
+});
+
+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_webaudioLoop2.html b/dom/base/test/file_webaudioLoop2.html
new file mode 100644
index 000000000..8453a436d
--- /dev/null
+++ b/dom/base/test/file_webaudioLoop2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script>
+var ac = new AudioContext();
+fetch("audio.ogg").then(response => {
+ return response.arrayBuffer();
+}).then(ab => {
+ return ac.decodeAudioData(ab);
+}).then(ab => {
+ var src = ac.createBufferSource();
+ src.buffer = ab;
+ src.loop = true;
+ src.start();
+ src.connect(ac.destination);
+});
+</script>
diff --git a/dom/base/test/file_websocket_basic_wsh.py b/dom/base/test/file_websocket_basic_wsh.py
new file mode 100644
index 000000000..498e27843
--- /dev/null
+++ b/dom/base/test/file_websocket_basic_wsh.py
@@ -0,0 +1,29 @@
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ # must set request.ws_protocol to the selected version from ws_requested_protocols
+ request.ws_protocol = request.ws_requested_protocols[0]
+
+ if (request.ws_protocol == 'error'):
+ raise ValueError('Error')
+ pass
+
+def web_socket_transfer_data(request):
+ while True:
+ line = msgutil.receive_message(request)
+ if line == 'protocol':
+ msgutil.send_message(request, request.ws_protocol)
+ continue
+
+ if line == 'resource':
+ msgutil.send_message(request, request.ws_resource)
+ continue
+
+ if line == 'origin':
+ msgutil.send_message(request, request.ws_origin)
+ continue
+
+ msgutil.send_message(request, line)
+
+ if line == 'end':
+ return
diff --git a/dom/base/test/file_websocket_hello_wsh.py b/dom/base/test/file_websocket_hello_wsh.py
new file mode 100644
index 000000000..9f7575439
--- /dev/null
+++ b/dom/base/test/file_websocket_hello_wsh.py
@@ -0,0 +1,10 @@
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+def web_socket_transfer_data(request):
+ resp = "Test"
+ if msgutil.receive_message(request) == "data":
+ resp = "Hello world!"
+ msgutil.send_message(request, resp)
diff --git a/dom/base/test/file_websocket_http_resource.txt b/dom/base/test/file_websocket_http_resource.txt
new file mode 100644
index 000000000..35dc67f08
--- /dev/null
+++ b/dom/base/test/file_websocket_http_resource.txt
@@ -0,0 +1 @@
+server data
diff --git a/dom/base/test/file_websocket_permessage_deflate_disabled_wsh.py b/dom/base/test/file_websocket_permessage_deflate_disabled_wsh.py
new file mode 100644
index 000000000..98dedcd0c
--- /dev/null
+++ b/dom/base/test/file_websocket_permessage_deflate_disabled_wsh.py
@@ -0,0 +1,17 @@
+from mod_pywebsocket import msgutil
+from mod_pywebsocket import common
+
+def web_socket_do_extra_handshake(request):
+ if request.ws_requested_extensions is not None:
+ for extension_request in request.ws_requested_extensions:
+ if extension_request.name() == "permessage-deflate":
+ raise ValueError('permessage-deflate should not be offered')
+
+def web_socket_transfer_data(request):
+ while True:
+ rcvd = msgutil.receive_message(request)
+ opcode = request.ws_stream.get_last_received_opcode()
+ if (opcode == common.OPCODE_BINARY):
+ msgutil.send_message(request, rcvd, binary=True)
+ elif (opcode == common.OPCODE_TEXT):
+ msgutil.send_message(request, rcvd)
diff --git a/dom/base/test/file_websocket_permessage_deflate_params_wsh.py b/dom/base/test/file_websocket_permessage_deflate_params_wsh.py
new file mode 100644
index 000000000..36169f4c2
--- /dev/null
+++ b/dom/base/test/file_websocket_permessage_deflate_params_wsh.py
@@ -0,0 +1,23 @@
+from mod_pywebsocket import msgutil
+from mod_pywebsocket import common
+
+def web_socket_do_extra_handshake(request):
+ deflate_found = False
+
+ if request.ws_extension_processors is not None:
+ for extension_processor in request.ws_extension_processors:
+ if extension_processor.name() == "deflate":
+ extension_processor.set_client_no_context_takeover(True)
+ deflate_found = True
+
+ if deflate_found is False:
+ raise ValueError('deflate extension processor not found')
+
+def web_socket_transfer_data(request):
+ while True:
+ rcvd = msgutil.receive_message(request)
+ opcode = request.ws_stream.get_last_received_opcode()
+ if (opcode == common.OPCODE_BINARY):
+ msgutil.send_message(request, rcvd, binary=True)
+ elif (opcode == common.OPCODE_TEXT):
+ msgutil.send_message(request, rcvd)
diff --git a/dom/base/test/file_websocket_permessage_deflate_rejected_wsh.py b/dom/base/test/file_websocket_permessage_deflate_rejected_wsh.py
new file mode 100644
index 000000000..b21da97cd
--- /dev/null
+++ b/dom/base/test/file_websocket_permessage_deflate_rejected_wsh.py
@@ -0,0 +1,23 @@
+from mod_pywebsocket import msgutil
+from mod_pywebsocket import common
+
+def web_socket_do_extra_handshake(request):
+ deflate_removed = False
+
+ if request.ws_extension_processors is not None:
+ for extension_processor in request.ws_extension_processors:
+ if extension_processor.name() == "deflate":
+ request.ws_extension_processors.remove(extension_processor)
+ deflate_removed = True
+
+ if deflate_removed is False:
+ raise ValueError('deflate extension processor not found')
+
+def web_socket_transfer_data(request):
+ while True:
+ rcvd = msgutil.receive_message(request)
+ opcode = request.ws_stream.get_last_received_opcode()
+ if (opcode == common.OPCODE_BINARY):
+ msgutil.send_message(request, rcvd, binary=True)
+ elif (opcode == common.OPCODE_TEXT):
+ msgutil.send_message(request, rcvd)
diff --git a/dom/base/test/file_websocket_permessage_deflate_wsh.py b/dom/base/test/file_websocket_permessage_deflate_wsh.py
new file mode 100644
index 000000000..65752dee1
--- /dev/null
+++ b/dom/base/test/file_websocket_permessage_deflate_wsh.py
@@ -0,0 +1,22 @@
+from mod_pywebsocket import msgutil
+from mod_pywebsocket import common
+
+def web_socket_do_extra_handshake(request):
+ pmce_offered = False
+
+ if request.ws_requested_extensions is not None:
+ for extension_request in request.ws_requested_extensions:
+ if extension_request.name() == "permessage-deflate":
+ pmce_offered = True
+
+ if pmce_offered is False:
+ raise ValueError('permessage-deflate not offered')
+
+def web_socket_transfer_data(request):
+ while True:
+ rcvd = msgutil.receive_message(request)
+ opcode = request.ws_stream.get_last_received_opcode()
+ if (opcode == common.OPCODE_BINARY):
+ msgutil.send_message(request, rcvd, binary=True)
+ elif (opcode == common.OPCODE_TEXT):
+ msgutil.send_message(request, rcvd)
diff --git a/dom/base/test/file_websocket_wsh.py b/dom/base/test/file_websocket_wsh.py
new file mode 100644
index 000000000..ef440580e
--- /dev/null
+++ b/dom/base/test/file_websocket_wsh.py
@@ -0,0 +1,159 @@
+from mod_pywebsocket import msgutil
+
+import time
+import sys
+import struct
+
+# see the list of tests in test_websocket.html
+
+def web_socket_do_extra_handshake(request):
+ # must set request.ws_protocol to the selected version from ws_requested_protocols
+ for x in request.ws_requested_protocols:
+ if x != "test-does-not-exist":
+ request.ws_protocol = x
+ break
+
+ if request.ws_protocol == "test-2.1":
+ time.sleep(3)
+ elif request.ws_protocol == "test-9":
+ time.sleep(3)
+ elif request.ws_protocol == "test-10":
+ time.sleep(3)
+ elif request.ws_protocol == "test-19":
+ raise ValueError('Aborting (test-19)')
+ elif request.ws_protocol == "test-20" or request.ws_protocol == "test-17":
+ time.sleep(3)
+ elif request.ws_protocol == "test-22":
+ # The timeout is 5 seconds
+ time.sleep(13)
+ elif request.ws_protocol == "test-41b":
+ request.sts = "max-age=100"
+ else:
+ pass
+
+# Behave according to recommendation of RFC 6455, section # 5.5.1:
+# "When sending a Close frame in response, the endpoint typically echos the
+# status code it received."
+# - Without this, pywebsocket replies with 1000 to any close code.
+#
+# Note that this function is only called when the client initiates the close
+def web_socket_passive_closing_handshake(request):
+ if request.ws_close_code == 1005:
+ return None, None
+ else:
+ return request.ws_close_code, request.ws_close_reason
+
+
+def web_socket_transfer_data(request):
+ if request.ws_protocol == "test-2.1" or request.ws_protocol == "test-2.2":
+ msgutil.close_connection(request)
+ elif request.ws_protocol == "test-6":
+ resp = "wrong message"
+ if msgutil.receive_message(request) == "1":
+ resp = "2"
+ msgutil.send_message(request, resp.decode('utf-8'))
+ resp = "wrong message"
+ if msgutil.receive_message(request) == "3":
+ resp = "4"
+ msgutil.send_message(request, resp.decode('utf-8'))
+ resp = "wrong message"
+ if msgutil.receive_message(request) == "5":
+ resp = "ã‚ã„ã†ãˆãŠ"
+ msgutil.send_message(request, resp.decode('utf-8'))
+ msgutil.close_connection(request)
+ elif request.ws_protocol == "test-7":
+ msgutil.send_message(request, "test-7 data")
+ elif request.ws_protocol == "test-10":
+ msgutil.close_connection(request)
+ elif request.ws_protocol == "test-11":
+ resp = "wrong message"
+ if msgutil.receive_message(request) == "client data":
+ resp = "server data"
+ msgutil.send_message(request, resp.decode('utf-8'))
+ elif request.ws_protocol == "test-12":
+ msg = msgutil.receive_message(request)
+ if msg == u'a\ufffdb':
+ # converted unpaired surrogate in UTF-16 to UTF-8 OK
+ msgutil.send_message(request, "SUCCESS")
+ else:
+ msgutil.send_message(request, "FAIL got '" + msg
+ + "' instead of string with replacement char'")
+ elif request.ws_protocol == "test-13":
+ # first one binary message containing the byte 0x61 ('a')
+ request.connection.write('\xff\x01\x61')
+ # after a bad utf8 message
+ request.connection.write('\x01\x61\xff')
+ msgutil.close_connection(request)
+ elif request.ws_protocol == "test-14":
+ msgutil.close_connection(request)
+ msgutil.send_message(request, "server data")
+ elif request.ws_protocol == "test-15":
+ # DISABLED: close_connection hasn't supported 2nd 'abort' argument for a
+ # long time. Passing extra arg was causing exception, which conveniently
+ # caused abort :) but as of pywebsocket v606 raising an exception here no
+ # longer aborts, and there's no obvious way to close TCP connection w/o
+ # sending websocket CLOSE.
+ raise RuntimeError("test-15 should be disabled for now")
+ #msgutil.close_connection(request, True) # OBSOLETE 2nd arg
+ return
+ elif request.ws_protocol == "test-17" or request.ws_protocol == "test-21":
+ time.sleep(2)
+ resp = "wrong message"
+ if msgutil.receive_message(request) == "client data":
+ resp = "server data"
+ msgutil.send_message(request, resp.decode('utf-8'))
+ time.sleep(2)
+ msgutil.close_connection(request)
+ elif request.ws_protocol == "test-20":
+ msgutil.send_message(request, "server data")
+ msgutil.close_connection(request)
+ elif request.ws_protocol == "test-34":
+ request.ws_stream.close_connection(1001, "going away now")
+ elif request.ws_protocol == "test-35a":
+ while not request.client_terminated:
+ msgutil.receive_message(request)
+ global test35code
+ test35code = request.ws_close_code
+ global test35reason
+ test35reason = request.ws_close_reason
+ elif request.ws_protocol == "test-35b":
+ request.ws_stream.close_connection(test35code + 1, test35reason)
+ elif request.ws_protocol == "test-37b":
+ while not request.client_terminated:
+ msgutil.receive_message(request)
+ global test37code
+ test37code = request.ws_close_code
+ global test37reason
+ test37reason = request.ws_close_reason
+ elif request.ws_protocol == "test-37c":
+ request.ws_stream.close_connection(test37code, test37reason)
+ elif request.ws_protocol == "test-42":
+ # Echo back 3 messages
+ msgutil.send_message(request,
+ msgutil.receive_message(request))
+ msgutil.send_message(request,
+ msgutil.receive_message(request))
+ msgutil.send_message(request,
+ msgutil.receive_message(request))
+ elif request.ws_protocol == "test-44":
+ rcv = msgutil.receive_message(request)
+ # check we received correct binary msg
+ if len(rcv) == 3 \
+ and ord(rcv[0]) == 5 and ord(rcv[1]) == 0 and ord(rcv[2]) == 7:
+ # reply with binary msg 0x04
+ msgutil.send_message(request, struct.pack("cc", chr(0), chr(4)), True, True)
+ else:
+ msgutil.send_message(request, "incorrect binary msg received!")
+ elif request.ws_protocol == "test-45":
+ rcv = msgutil.receive_message(request)
+ # check we received correct binary msg
+ if rcv == "flob":
+ # send back same blob as binary msg
+ msgutil.send_message(request, rcv, True, True)
+ else:
+ msgutil.send_message(request, "incorrect binary msg received: '" + rcv + "'")
+ elif request.ws_protocol == "test-46":
+ msgutil.send_message(request, "client must drop this if close was called")
+
+ while not request.client_terminated:
+ msgutil.receive_message(request)
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 000000000..63a918b99
--- /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, false);
+</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 000000000..24ebbe1a8
--- /dev/null
+++ b/dom/base/test/file_x-frame-options_page.sjs
@@ -0,0 +1,60 @@
+// 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['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 000000000..64271ce2c
--- /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 000000000..fbefe91d6
--- /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 000000000..d62cc367b
--- /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 000000000..a0aecdd2c
--- /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 000000000..0c2814311
--- /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 000000000..120f8e7dc
--- /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 000000000..6f5055bd5
--- /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 000000000..a0aecdd2c
--- /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 000000000..a5eb6e969
--- /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 000000000..d13fce2cc
--- /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 000000000..9ef4840e3
--- /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 000000000..f9f92a067
--- /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 000000000..6b156278a
--- /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 000000000..2a9c55b95
--- /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 000000000..c35cc48cf
--- /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 000000000..0ba1c8421
--- /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 000000000..0ba1c8421
--- /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 000000000..59b564ca7
--- /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 000000000..47bf52ede
--- /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: function() {
+ parent.postMessage(JSON.stringify({fn: "finish"}), "*");
+ }
+ };
+ ["ok", "is", "info"].forEach(fn => {
+ self[fn] = function (...args) {
+ parent.postMessage(JSON.stringify({fn: fn, args: 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/fileapi_chromeScript.js b/dom/base/test/fileapi_chromeScript.js
new file mode 100644
index 000000000..614b556ed
--- /dev/null
+++ b/dom/base/test/fileapi_chromeScript.js
@@ -0,0 +1,29 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.importGlobalProperties(["File"]);
+
+var fileNum = 1;
+
+function createFileWithData(fileData) {
+ var willDelete = fileData === null;
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+ var testFile = dirSvc.get("ProfD", Ci.nsIFile);
+ testFile.append("fileAPItestfile" + fileNum);
+ fileNum++;
+ var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0o666, 0);
+ if (willDelete) {
+ fileData = "some irrelevant test data\n";
+ }
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+ var domFile = File.createFromNsIFile(testFile);
+ if (willDelete) {
+ testFile.remove(/* recursive: */ false);
+ }
+ return domFile;
+}
+
+addMessageListener("files.open", function (message) {
+ sendAsyncMessage("files.opened", message.map(createFileWithData));
+});
diff --git a/dom/base/test/fileutils.js b/dom/base/test/fileutils.js
new file mode 100644
index 000000000..8a6a7334a
--- /dev/null
+++ b/dom/base/test/fileutils.js
@@ -0,0 +1,254 @@
+// Utility functions
+var testRanCounter = 0;
+var expectedTestCount = 0;
+
+function testHasRun() {
+ //alert(testRanCounter);
+ ++testRanCounter;
+ if (testRanCounter == expectedTestCount) {
+ SimpleTest.finish();
+ }
+}
+
+
+function testFile(file, contents, test) {
+ SimpleTest.requestLongerTimeout(2);
+
+ // Load file using FileReader
+ var r = new FileReader();
+ r.onload = getFileReaderLoadHandler(contents, contents.length, "FileReader.readAsBinaryString of " + test);
+ r.readAsBinaryString(file);
+ expectedTestCount++;
+
+ // Load file using URL.createObjectURL and XMLHttpRequest
+ var xhr = new XMLHttpRequest;
+ xhr.open("GET", URL.createObjectURL(file));
+ xhr.onload = getXHRLoadHandler(contents, contents.length,
+ "XMLHttpRequest load of " + test);
+ xhr.overrideMimeType('text/plain; charset=x-user-defined');
+ xhr.send();
+ expectedTestCount++;
+
+ // Send file to server using FormData and XMLHttpRequest
+ xhr = new XMLHttpRequest();
+ xhr.onload = function(event) {
+ checkMPSubmission(JSON.parse(event.target.responseText),
+ [{ name: "hello", value: "world"},
+ { name: "myfile",
+ value: contents,
+ fileName: file.name || "blob",
+ contentType: file.type || "application/octet-stream" }]);
+ testHasRun();
+ }
+ xhr.open("POST", "../../../dom/html/test/form_submit_server.sjs");
+ var fd = new FormData;
+ fd.append("hello", "world");
+ fd.append("myfile", file);
+ xhr.send(fd);
+ expectedTestCount++;
+
+ // Send file to server using plain XMLHttpRequest
+ var xhr = new XMLHttpRequest;
+ xhr.open("POST", "../../../dom/xhr/tests/file_XHRSendData.sjs");
+ xhr.onload = function (event) {
+ is(event.target.getResponseHeader("Result-Content-Type"),
+ file.type ? file.type : null,
+ "request content-type in XMLHttpRequest send of " + test);
+ is(event.target.getResponseHeader("Result-Content-Length"),
+ String(file.size),
+ "request content-length in XMLHttpRequest send of " + test);
+ };
+ xhr.addEventListener("load",
+ getXHRLoadHandler(contents, contents.length,
+ "XMLHttpRequest send of " + test),
+ false);
+ xhr.overrideMimeType('text/plain; charset=x-user-defined');
+ xhr.send(file);
+ expectedTestCount++;
+}
+
+function getFileReaderLoadHandler(expectedResult, expectedLength, testName) {
+ return function (event) {
+ is(event.target.readyState, FileReader.DONE,
+ "[FileReader] readyState in test " + testName);
+ is(event.target.error, null,
+ "[FileReader] no error in test " + testName);
+ // Do not use |is(event.target.result, expectedResult, "...");| that may output raw binary data.
+ is(event.target.result.length, expectedResult.length,
+ "[FileReader] Length of result in test " + testName);
+ ok(event.target.result == expectedResult,
+ "[FileReader] Content of result in test " + testName);
+ is(event.lengthComputable, true,
+ "[FileReader] lengthComputable in test " + testName);
+ is(event.loaded, expectedLength,
+ "[FileReader] Loaded length in test " + testName);
+ is(event.total, expectedLength,
+ "[FileReader] Total length in test " + testName);
+ testHasRun();
+ }
+}
+
+function getXHRLoadHandler(expectedResult, expectedLength, testName) {
+ return function (event) {
+ is(event.target.readyState, 4,
+ "[XHR] readyState in test " + testName);
+ is(event.target.status, 200,
+ "[XHR] no error in test " + testName);
+ // Do not use |is(convertXHRBinary(event.target.responseText), expectedResult, "...");| that may output raw binary data.
+ var convertedData = convertXHRBinary(event.target.responseText);
+ is(convertedData.length, expectedResult.length,
+ "[XHR] Length of result in test " + testName);
+ ok(convertedData == expectedResult,
+ "[XHR] Content of result in test " + testName);
+ is(event.lengthComputable, event.total != 0,
+ "[XHR] lengthComputable in test " + testName);
+ is(event.loaded, expectedLength,
+ "[XHR] Loaded length in test " + testName);
+ is(event.total, expectedLength,
+ "[XHR] Total length in test " + testName);
+
+ testHasRun();
+ }
+}
+
+function convertXHRBinary(s) {
+ var res = "";
+ for (var i = 0; i < s.length; ++i) {
+ res += String.fromCharCode(s.charCodeAt(i) & 255);
+ }
+ return res;
+}
+
+function testHasRun() {
+ //alert(testRanCounter);
+ ++testRanCounter;
+ if (testRanCounter == expectedTestCount) {
+ SimpleTest.finish();
+ }
+}
+
+function gc() {
+ window.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils)
+ .garbageCollect();
+}
+
+function checkMPSubmission(sub, expected) {
+ function getPropCount(o) {
+ var x, l = 0;
+ for (x in o) ++l;
+ return l;
+ }
+
+ is(sub.length, expected.length,
+ "Correct number of items");
+ var i;
+ for (i = 0; i < expected.length; ++i) {
+ if (!("fileName" in expected[i])) {
+ is(sub[i].headers["Content-Disposition"],
+ "form-data; name=\"" + expected[i].name + "\"",
+ "Correct name (A)");
+ is (getPropCount(sub[i].headers), 1,
+ "Wrong number of headers (A)");
+ }
+ else {
+ is(sub[i].headers["Content-Disposition"],
+ "form-data; name=\"" + expected[i].name + "\"; filename=\"" +
+ expected[i].fileName + "\"",
+ "Correct name (B)");
+ is(sub[i].headers["Content-Type"],
+ expected[i].contentType,
+ "Correct content type (B)");
+ is (getPropCount(sub[i].headers), 2,
+ "Wrong number of headers (B)");
+ }
+ // Do not use |is(sub[i].body, expected[i].value, "...");| that may output raw binary data.
+ is(sub[i].body.length, expected[i].value.length,
+ "Length of correct value");
+ ok(sub[i].body == expected[i].value,
+ "Content of correct value");
+ }
+}
+
+function testSlice(file, size, type, contents, fileType) {
+ is(file.type, type, fileType + " file is correct type");
+ is(file.size, size, fileType + " file is correct size");
+ ok(file instanceof File, fileType + " file is a File");
+ ok(file instanceof Blob, fileType + " file is also a Blob");
+
+ var slice = file.slice(0, size);
+ ok(slice instanceof Blob, fileType + " fullsize slice is a Blob");
+ ok(!(slice instanceof File), fileType + " fullsize slice is not a File");
+
+ slice = file.slice(0, 1234);
+ ok(slice instanceof Blob, fileType + " sized slice is a Blob");
+ ok(!(slice instanceof File), fileType + " sized slice is not a File");
+
+ slice = file.slice(0, size, "foo/bar");
+ is(slice.type, "foo/bar", fileType + " fullsize slice foo/bar type");
+
+ slice = file.slice(0, 5432, "foo/bar");
+ is(slice.type, "foo/bar", fileType + " sized slice foo/bar type");
+
+ is(slice.slice(0, 10).type, "", fileType + " slice-slice type");
+ is(slice.slice(0, 10).size, 10, fileType + " slice-slice size");
+ is(slice.slice(0, 10, "hello/world").type, "hello/world", fileType + " slice-slice hello/world type");
+ is(slice.slice(0, 10, "hello/world").size, 10, fileType + " slice-slice hello/world size");
+
+ // Start, end, expected size
+ var indexes = [[0, size, size],
+ [0, 1234, 1234],
+ [size-500, size, 500],
+ [size-500, size+500, 500],
+ [size+500, size+1500, 0],
+ [0, 0, 0],
+ [1000, 1000, 0],
+ [size, size, 0],
+ [undefined, undefined, size],
+ [0, undefined, size],
+ [100, undefined, size-100],
+ [-100, undefined, 100],
+ [100, -100, size-200],
+ [-size-100, undefined, size],
+ [-2*size-100, 500, 500],
+ [0, -size-100, 0],
+ [100, -size-100, 0],
+ [50, -size+100, 50],
+ [0, 33000, 33000],
+ [1000, 34000, 33000],
+ ];
+
+ for (var i = 0; i < indexes.length; ++i) {
+ var sliceContents;
+ var testName;
+ if (indexes[i][0] == undefined) {
+ slice = file.slice();
+ sliceContents = contents.slice();
+ testName = fileType + " slice()";
+ }
+ else if (indexes[i][1] == undefined) {
+ slice = file.slice(indexes[i][0]);
+ sliceContents = contents.slice(indexes[i][0]);
+ testName = fileType + " slice(" + indexes[i][0] + ")";
+ }
+ else {
+ slice = file.slice(indexes[i][0], indexes[i][1]);
+ sliceContents = contents.slice(indexes[i][0], indexes[i][1]);
+ testName = fileType + " slice(" + indexes[i][0] + ", " + indexes[i][1] + ")";
+ }
+ is(slice.type, "", testName + " type");
+ is(slice.size, indexes[i][2], testName + " size");
+ is(sliceContents.length, indexes[i][2], testName + " data size");
+ testFile(slice, sliceContents, testName);
+ }
+
+ // Slice of slice
+ var slice = file.slice(0, 40000);
+ testFile(slice.slice(5000, 42000), contents.slice(5000, 40000), "file slice slice");
+
+ // ...of slice of slice
+ slice = slice.slice(5000, 42000).slice(400, 700);
+ SpecialPowers.gc();
+ testFile(slice, contents.slice(5400, 5700), "file slice slice slice");
+}
+
diff --git a/dom/base/test/forRemoval.resource b/dom/base/test/forRemoval.resource
new file mode 100644
index 000000000..09e882b3b
--- /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 000000000..6a63b5341
--- /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 000000000..faee1e5b4
--- /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/gtest/TestNativeXMLHttpRequest.cpp b/dom/base/test/gtest/TestNativeXMLHttpRequest.cpp
new file mode 100644
index 000000000..920d80e96
--- /dev/null
+++ b/dom/base/test/gtest/TestNativeXMLHttpRequest.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 "gtest/gtest.h"
+
+#include "nsContentUtils.h"
+#include "nsIDOMDocument.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIXMLHttpRequest.h"
+
+#define TEST_URL_PREFIX "data:text/xml,"
+#define TEST_URL_CONTENT "<foo><bar></bar></foo>"
+#define TEST_URL TEST_URL_PREFIX TEST_URL_CONTENT
+
+TEST(NativeXMLHttpRequest, Test)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIXMLHttpRequest> xhr =
+ do_CreateInstance(NS_XMLHTTPREQUEST_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Couldn't create nsIXMLHttpRequest instance";
+
+ NS_NAMED_LITERAL_CSTRING(getString, "GET");
+ NS_NAMED_LITERAL_CSTRING(testURL, TEST_URL);
+ const nsAString& empty = EmptyString();
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Couldn't get script security manager";
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Couldn't get system principal";
+
+ rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Couldn't initialize the XHR";
+
+ rv = xhr->Open(getString, testURL, false, empty, empty);
+ ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Open failed";
+
+ rv = xhr->Send(nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv)) << "Send failed";
+
+ nsAutoString response;
+ rv = xhr->GetResponseText(response);
+ ASSERT_TRUE(NS_SUCCEEDED(rv)) << "GetResponse failed";
+ ASSERT_TRUE(response.EqualsLiteral(TEST_URL_CONTENT)) <<
+ "Response text does not match";
+
+ nsCOMPtr<nsIDOMDocument> dom;
+ rv = xhr->GetResponseXML(getter_AddRefs(dom));
+ ASSERT_TRUE(NS_SUCCEEDED(rv)) << "GetResponseXML failed";
+ ASSERT_TRUE(dom) << "No DOM document constructed";
+}
diff --git a/dom/base/test/gtest/TestParserDialogOptions.cpp b/dom/base/test/gtest/TestParserDialogOptions.cpp
new file mode 100644
index 000000000..055f9ebfd
--- /dev/null
+++ b/dom/base/test/gtest/TestParserDialogOptions.cpp
@@ -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/. */
+
+#include "gtest/gtest.h"
+#include "nsGlobalWindow.h"
+
+struct dialog_test {
+ const char* input;
+ const char* output;
+};
+
+void runTokenizeTest(dialog_test& test)
+{
+ NS_ConvertUTF8toUTF16 input(test.input);
+
+ nsAString::const_iterator end;
+ input.EndReading(end);
+
+ nsAString::const_iterator iter;
+ input.BeginReading(iter);
+
+ nsAutoString result;
+ nsAutoString token;
+
+ while (nsGlobalWindow::TokenizeDialogOptions(token, iter, end)) {
+ if (!result.IsEmpty()) {
+ result.Append(',');
+ }
+
+ result.Append(token);
+ }
+
+ ASSERT_STREQ(test.output, NS_ConvertUTF16toUTF8(result).get()) << "Testing " << test.input;
+}
+
+void runTest(dialog_test& test)
+{
+ NS_ConvertUTF8toUTF16 input(test.input);
+
+ nsAutoString result;
+ nsGlobalWindow::ConvertDialogOptions(input, result);
+
+ ASSERT_STREQ(test.output, NS_ConvertUTF16toUTF8(result).get()) << "Testing " << test.input;
+}
+
+TEST(GlobalWindowDialogOptions, TestDialogTokenize)
+{
+ dialog_test tests[] = {
+ /// Empty strings
+ { "", "" },
+ { " ", "" },
+ { " ", "" },
+
+ // 1 token
+ { "a", "a" },
+ { " a", "a" },
+ { " a ", "a" },
+ { "aa", "aa" },
+ { " aa", "aa" },
+ { " aa ", "aa" },
+ { ";", ";" },
+ { ":", ":" },
+ { "=", "=" },
+
+ // 2 tokens
+ { "a=", "a,=" },
+ { " a= ", "a,=" },
+ { " a = ", "a,=" },
+ { "aa=", "aa,=" },
+ { " aa= ", "aa,=" },
+ { " aa = ", "aa,=" },
+ { ";= ", ";,=" },
+ { "==", "=,=" },
+ { "::", ":,:" },
+
+ // 3 tokens
+ { "a=2", "a,=,2" },
+ { "===", "=,=,=" },
+ { ";:=", ";,:,=" },
+
+ // more
+ { "aaa;bbb:ccc", "aaa,;,bbb,:,ccc" },
+
+ // sentinel
+ { nullptr, nullptr }
+ };
+
+ for (uint32_t i = 0; tests[i].input; ++i) {
+ runTokenizeTest(tests[i]);
+ }
+}
+TEST(GlobalWindowDialogOptions, TestDialogOptions)
+{
+ dialog_test tests[] = {
+ /// Empty strings
+ { "", "" },
+ { " ", "" },
+ { " ", "" },
+
+ // Name without params
+ { "a", "" },
+ { " a", "" },
+ { " a ", "" },
+ { "a=", "" },
+ { " a= ", "" },
+ { " a = ", "" },
+
+ // 1 unknown value
+ { "a=2", "" },
+ { " a=2 ", "" },
+ { " a = 2 ", "" },
+ { "a:2", "" },
+ { " a:2 ", "" },
+ { " a : 2 ", "" },
+
+ // 1 known value, wrong value
+ { "center=2", "" },
+ { "center:2", "" },
+
+ // 1 known value, good value
+ { "center=on", ",centerscreen=1" },
+ { "center:on", ",centerscreen=1" },
+ { " center : on ", ",centerscreen=1" },
+
+ // nonsense stuff
+ { " ; ", "" },
+
+ // sentinel
+ { nullptr, nullptr }
+ };
+
+ for (uint32_t i = 0; tests[i].input; ++i) {
+ runTest(tests[i]);
+ }
+}
diff --git a/dom/base/test/gtest/TestPlainTextSerializer.cpp b/dom/base/test/gtest/TestPlainTextSerializer.cpp
new file mode 100644
index 000000000..c29340209
--- /dev/null
+++ b/dom/base/test/gtest/TestPlainTextSerializer.cpp
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsStringGlue.h"
+#include "nsIDocumentEncoder.h"
+#include "nsCRT.h"
+#include "nsIParserUtils.h"
+
+void
+ConvertBufToPlainText(nsString &aConBuf, int aFlag)
+{
+ nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID);
+ utils->ConvertToPlainText(aConBuf, aFlag, 72, 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);
+
+ // 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);
+
+ // 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);
+
+ // 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 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;\">"
+ "&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);
+
+ // 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);
+ 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);
+ 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);
+ 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; width: 10ch;\">" 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>");
+
+ ConvertBufToPlainText(test, nsIDocumentEncoder::OutputWrap);
+ // "\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);
+ ASSERT_TRUE(test.EqualsLiteral("basespanbody")) <<
+ "Wrong html to text serialization";
+}
+
diff --git a/dom/base/test/gtest/moz.build b/dom/base/test/gtest/moz.build
new file mode 100644
index 000000000..a211184e8
--- /dev/null
+++ b/dom/base/test/gtest/moz.build
@@ -0,0 +1,19 @@
+# -*- 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 += [
+ 'TestNativeXMLHttpRequest.cpp',
+ 'TestParserDialogOptions.cpp',
+ 'TestPlainTextSerializer.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base'
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/dom/base/test/iframe_bug962251.html b/dom/base/test/iframe_bug962251.html
new file mode 100644
index 000000000..1ecad0fd4
--- /dev/null
+++ b/dom/base/test/iframe_bug962251.html
@@ -0,0 +1,26 @@
+<!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 onFocus(aEvent) {
+ input.removeEventListener("focus", onFocus);
+ is(aEvent.target.id, "textinput", "Input should be focused.");
+ ok(aEvent.relatedTarget === null, "The relatedTarget should be null.");
+ parent.postMessage("runNextTest", "*");
+ });
+ 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 000000000..9b8bd9c20
--- /dev/null
+++ b/dom/base/test/iframe_bug976673.html
@@ -0,0 +1,25 @@
+<!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();
+ case "check":
+ aEvent.source.postMessage("input-value: " + input.value, "*");
+ break;
+ }
+ }, false);
+ </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 000000000..4a725f925
--- /dev/null
+++ b/dom/base/test/iframe_main_bug1022229.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+ // Uncomment this definition of SimpleTest (and comment out the one below) to
+ // debug in mozBrowser mode.
+ /*
+ var SimpleTest = { ok: function(c, m) { dump(m + ": " + c + "\n"); },
+ info: function(m) { dump(m + "\n"); },
+ finish: function() { dump("Test done\n");} };
+ */
+ 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_postMessage_solidus.html b/dom/base/test/iframe_postMessage_solidus.html
new file mode 100644
index 000000000..b5cf33b40
--- /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, false);
+ 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 000000000..b34462f5e
--- /dev/null
+++ b/dom/base/test/iframe_postMessages.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<script>
+onmessage = function(e) {
+ parent.postMessage(e.data, '*', e.ports);
+}
+</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 000000000..3d70e9d7a
--- /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_webSocket_sandbox.html b/dom/base/test/iframe_webSocket_sandbox.html
new file mode 100644
index 000000000..d889a79b0
--- /dev/null
+++ b/dom/base/test/iframe_webSocket_sandbox.html
@@ -0,0 +1,65 @@
+<html><body>
+<iframe id="frame" sandbox="allow-scripts allow-popups"></iframe>
+<script type="application/javascript;version=1.8">
+onmessage = function(e) {
+ parent.postMessage(e.data, '*');
+}
+
+var ifr = document.getElementById('frame');
+
+if (location.search == '?nested') {
+ var url = new URL(location);
+ url.search = "";
+ ifr.src = url.href;
+} else if (location.search == '?popup') {
+ var url = new URL(location);
+ url.search = "?opener";
+
+ ifr.srcdoc = "<html><script>" +
+ "window.open('" + url.href + "', 'foobar');" +
+ "onmessage = function(e) { " +
+ " parent.postMessage(e.data, '*'); " +
+ "}" +
+ "</scr" + "ipt></html>";
+} else if (location.search == '?opener') {
+ try{
+ var socket = new WebSocket('ws://mochi.test:8888/tests/dom/base/test/file_websocket_basic');
+ socket.onerror = function(e) {
+ opener.postMessage('WS onerror', '*');
+ close();
+ };
+ socket.onopen = function(event) {
+ opener.postMessage('WS onopen', '*');
+ close();
+ };
+ } catch(e) {
+ if (e.name == 'SecurityError') {
+ opener.postMessage('WS Throws!', '*');
+ } else {
+ opener.postMessage('WS Throws something else!', '*');
+ }
+ close();
+ }
+} else {
+ ifr.srcdoc = `
+ <html><script>
+ try{
+ var socket = new WebSocket('ws://mochi.test:8888/tests/dom/base/test/file_websocket_basic');
+ socket.onerror = function(e) {
+ parent.postMessage('WS onerror', '*');
+ };
+ socket.onopen = function(event) {
+ parent.postMessage('WS onopen', '*');
+ };
+ } catch(e) {
+ if (e.name == 'SecurityError') {
+ parent.postMessage('WS Throws!', '*');
+ } else {
+ parent.postMessage('WS Throws something else!', '*');
+ }
+ }
+ </scr`+`ipt>
+ </html>`;
+}
+</script>
+</body></html>
diff --git a/dom/base/test/img_referrer_testserver.sjs b/dom/base/test/img_referrer_testserver.sjs
new file mode 100644
index 000000000..f0ee1b963
--- /dev/null
+++ b/dom/base/test/img_referrer_testserver.sjs
@@ -0,0 +1,298 @@
+var BASE_URL = 'example.com/tests/dom/base/test/img_referrer_testserver.sjs';
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
+
+function createTestUrl(aPolicy, aAction, aName, aContent) {
+ var content = aContent || 'text';
+ return 'http://' + BASE_URL + '?' +
+ 'action=' + aAction + '&' +
+ 'policy=' + aPolicy + '&' +
+ 'name=' + aName + '&' +
+ 'content=' + content;
+}
+
+function createTestPage(aHead, aImgPolicy, aName) {
+ var _createTestUrl = createTestUrl.bind(null, aImgPolicy, 'test', aName);
+
+ return '<!DOCTYPE HTML>\n\
+ <html>'+
+ aHead +
+ '<body>\n\
+ <img src="' + _createTestUrl('img') + '" referrerpolicy="' + aImgPolicy + '" id="image"></img>\n\
+ <script>' +
+
+ // LOAD EVENT (of the test)
+ // fires when the img resource for the page is loaded
+ 'window.addEventListener("load", function() {\n\
+ parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+ }.bind(window), false);' +
+
+ '</script>\n\
+ </body>\n\
+ </html>';
+}
+
+// Creates the following test cases for the specified referrer
+// policy combination:
+// <img> with referrer
+function createTest(aPolicy, aImgPolicy, aName) {
+ var headString = '<head>';
+ if (aPolicy) {
+ headString += '<meta name="referrer" content="' + aPolicy + '">';
+ }
+
+ headString += '<script></script>';
+
+ return createTestPage(headString, aImgPolicy, aName);
+}
+
+// testing regular load img with referrer policy
+// speculative parser should not kick in here
+function createTest2(aImgPolicy, name) {
+ return createTestPage('', aImgPolicy, name);
+}
+
+function createTest3(aImgPolicy1, aImgPolicy2, aImgPolicy3, aName) {
+ return '<!DOCTYPE HTML>\n\
+ <html>\n\
+ <body>\n\
+ <img src="' + createTestUrl(aImgPolicy1, 'test', aName + aImgPolicy1) + '" referrerpolicy="' + aImgPolicy1 + '" id="image"></img>\n\
+ <img src="' + createTestUrl(aImgPolicy2, 'test', aName + aImgPolicy2) + '" referrerpolicy="' + aImgPolicy2 + '" id="image"></img>\n\
+ <img src="' + createTestUrl(aImgPolicy3, 'test', aName + aImgPolicy3) + '" referrerpolicy="' + aImgPolicy3 + '" id="image"></img>\n\
+ <script>\n\
+ var _numLoads = 0;' +
+
+ // LOAD EVENT (of the test)
+ // fires when the img resource for the page is loaded
+ 'window.addEventListener("load", function() {\n\
+ parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+ }.bind(window), false);' +
+
+ '</script>\n\
+ </body>\n\
+ </html>';
+}
+
+function createTestPage2(aHead, aPolicy, aName) {
+ return '<!DOCTYPE HTML>\n\
+ <html>'+
+ aHead +
+ '<body>\n\
+ <img src="' + createTestUrl(aPolicy, "test", aName) + '" id="image"></img>\n\
+ <script>' +
+
+ // LOAD EVENT (of the test)
+ // fires when the img resource for the page is loaded
+ 'window.addEventListener("load", function() {\n\
+ parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+ }.bind(window), false);' +
+
+ '</script>\n\
+ </body>\n\
+ </html>';
+}
+
+function createTestPage3(aHead, aPolicy, aName) {
+ return '<!DOCTYPE HTML>\n\
+ <html>'+
+ aHead +
+ '<body>\n\
+ <script>' +
+ 'var image = new Image();\n\
+ image.src = "' + createTestUrl(aPolicy, "test", aName, "image") + '";\n\
+ image.referrerPolicy = "' + aPolicy + '";\n\
+ image.onload = function() {\n\
+ window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+ }\n\
+ document.body.appendChild(image);' +
+
+ '</script>\n\
+ </body>\n\
+ </html>';
+}
+
+function createTestPage4(aHead, aPolicy, aName) {
+ return '<!DOCTYPE HTML>\n\
+ <html>'+
+ aHead +
+ '<body>\n\
+ <script>' +
+ 'var image = new Image();\n\
+ image.referrerPolicy = "' + aPolicy + '";\n\
+ image.src = "' + createTestUrl(aPolicy, "test", aName, "image") + '";\n\
+ image.onload = function() {\n\
+ window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+ }\n\
+ document.body.appendChild(image);' +
+
+ '</script>\n\
+ </body>\n\
+ </html>';
+}
+
+function createSetAttributeTest1(aPolicy, aImgPolicy, aName) {
+ var headString = '<head>';
+ headString += '<meta name="referrer" content="' + aPolicy + '">';
+ headString += '<script></script>';
+
+ return createTestPage3(headString, aImgPolicy, aName);
+}
+
+function createSetAttributeTest2(aPolicy, aImgPolicy, aName) {
+ var headString = '<head>';
+ headString += '<meta name="referrer" content="' + aPolicy + '">';
+ headString += '<script></script>';
+
+ return createTestPage4(headString, aImgPolicy, aName);
+}
+
+
+function createTest4(aPolicy, aName) {
+ var headString = '<head>';
+ headString += '<meta name="referrer" content="' + aPolicy + '">';
+ headString += '<script></script>';
+
+ return createTestPage2(headString, aPolicy, aName);
+}
+
+function createTest5(aPolicy, aName) {
+ var headString = '<head>';
+ headString += '<meta name="referrer" content="' + aPolicy + '">';
+
+ return createTestPage2(headString, aPolicy, aName);
+}
+
+function handleRequest(request, response) {
+ var sharedKey = 'img_referrer_testserver.sjs';
+ var params = request.queryString.split('&');
+ var action = params[0].split('=')[1];
+
+ response.setHeader('Cache-Control', 'no-cache', false);
+ response.setHeader('Content-Type', 'text/html; charset=utf-8', false);
+
+ if (action === 'resetState') {
+ var state = getSharedState(sharedKey);
+ state = {};
+ setSharedState(sharedKey, JSON.stringify(state));
+ response.write("");
+ return;
+ }
+ if (action === 'test') {
+ // ?action=test&policy=origin&name=name&content=content
+ var policy = params[1].split('=')[1];
+ var name = params[2].split('=')[1];
+ var content = 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("img_referrer_testserver") > 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));
+
+ if (content === 'image') {
+ response.setHeader("Content-Type", "image/png");
+ response.write(IMG_BYTES);
+ }
+ return;
+ }
+ if (action === 'get-test-results') {
+ // ?action=get-result
+ response.write(getSharedState(sharedKey));
+ return;
+ }
+ if (action === 'generate-img-policy-test') {
+ // ?action=generate-img-policy-test&imgPolicy=b64-encoded-string&name=name&policy=b64-encoded-string
+ var imgPolicy = unescape(params[1].split('=')[1]);
+ var name = unescape(params[2].split('=')[1]);
+ var metaPolicy = '';
+ if (params[3]) {
+ metaPolicy = params[3].split('=')[1];
+ }
+
+ response.write(createTest(metaPolicy, imgPolicy, name));
+ return;
+ }
+ if (action === 'generate-img-policy-test2') {
+ // ?action=generate-img-policy-test2&imgPolicy=b64-encoded-string&name=name
+ var imgPolicy = unescape(params[1].split('=')[1]);
+ var name = unescape(params[2].split('=')[1]);
+
+ response.write(createTest2(imgPolicy, name));
+ return;
+ }
+ if (action === 'generate-img-policy-test3') {
+ // ?action=generate-img-policy-test3&imgPolicy1=b64-encoded-string&imgPolicy2=b64-encoded-string&imgPolicy3=b64-encoded-string&name=name
+ var imgPolicy1 = unescape(params[1].split('=')[1]);
+ var imgPolicy2 = unescape(params[2].split('=')[1]);
+ var imgPolicy3 = unescape(params[3].split('=')[1]);
+ var name = unescape(params[4].split('=')[1]);
+
+ response.write(createTest3(imgPolicy1, imgPolicy2, imgPolicy3, name));
+ return;
+ }
+ if (action === 'generate-img-policy-test4') {
+ // ?action=generate-img-policy-test4&imgPolicy=b64-encoded-string&name=name
+ var policy = unescape(params[1].split('=')[1]);
+ var name = unescape(params[2].split('=')[1]);
+
+ response.write(createTest4(policy, name));
+ return;
+ }
+ if (action === 'generate-img-policy-test5') {
+ // ?action=generate-img-policy-test5&policy=b64-encoded-string&name=name
+ var policy = unescape(params[1].split('=')[1]);
+ var name = unescape(params[2].split('=')[1]);
+
+ response.write(createTest5(policy, name));
+ return;
+ }
+
+ if (action === 'generate-setAttribute-test1') {
+ // ?action=generate-setAttribute-test1&policy=b64-encoded-string&name=name
+ var imgPolicy = unescape(params[1].split('=')[1]);
+ var policy = unescape(params[2].split('=')[1]);
+ var name = unescape(params[3].split('=')[1]);
+
+ response.write(createSetAttributeTest1(policy, imgPolicy, name));
+ return;
+ }
+
+ if (action === 'generate-setAttribute-test2') {
+ // ?action=generate-setAttribute-test2&policy=b64-encoded-string&name=name
+ var imgPolicy = unescape(params[1].split('=')[1]);
+ var policy = unescape(params[2].split('=')[1]);
+ var name = unescape(params[3].split('=')[1]);
+
+ response.write(createSetAttributeTest2(policy, imgPolicy, name));
+ return;
+ }
+
+ response.write("I don't know action "+action);
+ return;
+}
diff --git a/dom/base/test/intersectionobserver_iframe.html b/dom/base/test/intersectionobserver_iframe.html
new file mode 100644
index 000000000..8a4098e74
--- /dev/null
+++ b/dom/base/test/intersectionobserver_iframe.html
@@ -0,0 +1,23 @@
+<!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) {
+ 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_window.html b/dom/base/test/intersectionobserver_window.html
new file mode 100644
index 000000000..c2b1b023a
--- /dev/null
+++ b/dom/base/test/intersectionobserver_window.html
@@ -0,0 +1,34 @@
+<!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 passed = 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;
+ window.opener.postMessage(passed, '*');
+ });
+ 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 000000000..aca66f6f8
--- /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 000000000..d5bed552c
--- /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/chrome.ini b/dom/base/test/jsmodules/chrome.ini
new file mode 100644
index 000000000..02a37a0ec
--- /dev/null
+++ b/dom/base/test/jsmodules/chrome.ini
@@ -0,0 +1,51 @@
+[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_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]
diff --git a/dom/base/test/jsmodules/iframe_extractIntroType.html b/dom/base/test/jsmodules/iframe_extractIntroType.html
new file mode 100644
index 000000000..692da0c5f
--- /dev/null
+++ b/dom/base/test/jsmodules/iframe_extractIntroType.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script type="application/javascript" 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.
+ Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+ addDebuggerToGlobal(this);
+ 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/mochitest.ini b/dom/base/test/jsmodules/mochitest.ini
new file mode 100644
index 000000000..aa2b8652c
--- /dev/null
+++ b/dom/base/test/jsmodules/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ module_setRan.js
+
+[test_moduleScriptsNotRun.html]
diff --git a/dom/base/test/jsmodules/module_badImport.js b/dom/base/test/jsmodules/module_badImport.js
new file mode 100644
index 000000000..ab1824915
--- /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 000000000..744158108
--- /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 000000000..efcfb9a4e
--- /dev/null
+++ b/dom/base/test/jsmodules/module_cyclic1.js
@@ -0,0 +1,7 @@
+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 000000000..68a9c6124
--- /dev/null
+++ b/dom/base/test/jsmodules/module_cyclic2.js
@@ -0,0 +1,7 @@
+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 000000000..964949399
--- /dev/null
+++ b/dom/base/test/jsmodules/module_cyclic3.js
@@ -0,0 +1,7 @@
+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 000000000..be5685770
--- /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 000000000..d6933d298
--- /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 000000000..e1b6da4c9
--- /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 000000000..c966a8eb2
--- /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 000000000..28cf608de
--- /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 000000000..0587ac5ca
--- /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 000000000..ddc5792d0
--- /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 000000000..4804382fd
--- /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 000000000..7594ac699
--- /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 000000000..f92a1c9d6
--- /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 000000000..71979926e
--- /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 000000000..9714d6d0a
--- /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 000000000..cfc12bb72
--- /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 000000000..3d647ae0b
--- /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 000000000..f16b6dd80
--- /dev/null
+++ b/dom/base/test/jsmodules/moz.build
@@ -0,0 +1,13 @@
+# -*- 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'
+]
diff --git a/dom/base/test/jsmodules/script_simple2.js b/dom/base/test/jsmodules/script_simple2.js
new file mode 100644
index 000000000..f92a1c9d6
--- /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 000000000..62e411bfe
--- /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 type="application/javascript" 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 000000000..f70ae1d6b
--- /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 type="application/javascript" 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_importIntroType.html b/dom/base/test/jsmodules/test_importIntroType.html
new file mode 100644
index 000000000..3f8538282
--- /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 type="application/javascript" 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 000000000..f4e12dfe7
--- /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 type="application/javascript" 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 000000000..7df521d05
--- /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 type="application/javascript" 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_importedModuleMemoization.html b/dom/base/test/jsmodules/test_importedModuleMemoization.html
new file mode 100644
index 000000000..56c1188af
--- /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 type="application/javascript" 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 000000000..3d1cb172f
--- /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 type="application/javascript" 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 000000000..4bf10b1d0
--- /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 type="application/javascript" 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 000000000..448292968
--- /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 type="application/javascript" 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 000000000..72eb1149e
--- /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 type="application/javascript" 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_moduleScriptsNotRun.html b/dom/base/test/jsmodules/test_moduleScriptsNotRun.html
new file mode 100644
index 000000000..1ce5377d9
--- /dev/null
+++ b/dom/base/test/jsmodules/test_moduleScriptsNotRun.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test script elements with type="module" are not run for content HTML</title>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var inlineModuleRan = false;
+ var moduleRan = false;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function testLoaded() {
+ ok(!moduleRan, "Check module script was not run");
+ ok(!inlineModuleRan, "Check inline module script was not 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_moduleScriptsRun.html b/dom/base/test/jsmodules/test_moduleScriptsRun.html
new file mode 100644
index 000000000..be2cbb2af
--- /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 type="application/javascript" 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 000000000..d2f5e42ab
--- /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 type="application/javascript" 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 000000000..f7abd7cee
--- /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 type="application/javascript" 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 000000000..14f6c76c3
--- /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 type="application/javascript" 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 000000000..583087b4f
--- /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 type="application/javascript" 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 000000000..6fd237c86
--- /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 type="application/javascript" 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 000000000..0afc259f8
--- /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 type="application/javascript" 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 000000000..ffc135006
--- /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 type="application/javascript" 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 000000000..c83290776
--- /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 type="application/javascript" 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 000000000..4c836cd31
--- /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 type="application/javascript" 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 000000000..53f95c96c
--- /dev/null
+++ b/dom/base/test/jsmodules/test_syntaxError.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test syntax errors parsing a module are reported</title>
+<script type="application/javascript" 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 type="module" src="module_badSyntax.js"></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 000000000..35d923755
--- /dev/null
+++ b/dom/base/test/jsmodules/test_syntaxErrorAsync.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test syntax errors parsing an async module are reported</title>
+<script type="application/javascript" 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 type="module" src="module_badSyntax.js" async></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 000000000..705bc5902
--- /dev/null
+++ b/dom/base/test/jsmodules/test_syntaxErrorInline.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test syntax errors parsing an inline module are reported</title>
+<script type="application/javascript" 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 type="module">
+// 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 000000000..5e7992823
--- /dev/null
+++ b/dom/base/test/jsmodules/test_syntaxErrorInlineAsync.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test syntax errors parsing an inline async module are reported</title>
+<script type="application/javascript" 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 type="module" async>
+// 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 000000000..ab4c80815
--- /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 type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+ var introType;
+ var extractIntroType;
+
+ info('start');
+ SimpleTest.waitForExplicitFinish();
+
+ function testIntroductionType() {
+ extractIntroType();
+ ok(introType == "scriptElement", '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 000000000..0e1ee728e
--- /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 type="application/javascript" 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 000000000..7a3f93740
--- /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 type="application/javascript" 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/mochitest.ini b/dom/base/test/mochitest.ini
new file mode 100644
index 000000000..ea86290d9
--- /dev/null
+++ b/dom/base/test/mochitest.ini
@@ -0,0 +1,820 @@
+[DEFAULT]
+support-files =
+ audio.ogg
+ audioEndedDuringPlaying.webm
+ 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
+ chrome/bug418986-1.js
+ copypaste.js
+ create_file_objects.js
+ delayedServerEvents.sjs
+ eventsource.resource
+ eventsource.resource^headers^
+ eventsource_redirect.resource
+ eventsource_redirect.resource^headers^
+ eventsource_redirect_to.resource
+ eventsource_redirect_to.resource^headers^
+ file_base_xbl.xml
+ 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_common.js
+ file_bug704320_preload_reuse.html
+ file_bug704320_preload_noreuse.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_bug769117.html
+ file_bug782342.txt
+ file_bug787778.sjs
+ file_bug804395.jar
+ file_bug869432.eventsource
+ file_bug869432.eventsource^headers^
+ file_bug902350.html
+ file_bug902350_frame.html
+ file_bug907892.html
+ file_bug945152.jar
+ file_bug1263696_frame_pass.html
+ file_bug1263696_frame_fail.html
+ file_bug1274806.html
+ file_general_document.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_2_enthtml.html
+ file_htmlserializer_2_entw3c.html
+ file_htmlserializer_2_latin1.html
+ file_htmlserializer_ipv6.html
+ file_htmlserializer_ipv6_out.html
+ file_lock_orientation.html
+ file_mozfiledataurl_audio.ogg
+ file_mozfiledataurl_doc.html
+ file_mozfiledataurl_img.jpg
+ file_mozfiledataurl_inner.html
+ file_mozfiledataurl_text.txt
+ file_record_orientation.html
+ file_restrictedEventSource.sjs
+ file_simplecontentpolicy.js
+ file_timer_flood.html
+ file_websocket_basic_wsh.py
+ file_websocket_hello_wsh.py
+ file_websocket_http_resource.txt
+ file_websocket_permessage_deflate_wsh.py
+ file_websocket_permessage_deflate_disabled_wsh.py
+ file_websocket_permessage_deflate_rejected_wsh.py
+ file_websocket_permessage_deflate_params_wsh.py
+ file_websocket_wsh.py
+ 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
+ fileapi_chromeScript.js
+ fileutils.js
+ forRemoval.resource
+ forRemoval.resource^headers^
+ formReset.html
+ invalid_accesscontrol.resource
+ invalid_accesscontrol.resource^headers^
+ mutationobserver_dialog.html
+ orientationcommon.js
+ script-1_bug597345.sjs
+ script-2_bug597345.js
+ script_bug602838.sjs
+ send_gzip_content.sjs
+ somedatas.resource
+ somedatas.resource^headers^
+ variable_style_sheet.sjs
+ viewport_helpers.js
+ w3element_traversal.svg
+ wholeTexty-helper.xml
+ file_nonascii_blob_url.html
+ referrerHelper.js
+ img_referrer_testserver.sjs
+ file_audioLoop.html
+ file_webaudioLoop.html
+ file_webaudioLoop2.html
+ file_pluginAudio.html
+ file_pluginAudioNonAutoStart.html
+ noaudio.webm
+ referrer_helper.js
+ referrer_testserver.sjs
+ script_postmessages_fileList.js
+ iframe_postMessages.html
+ 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
+ mozbrowser_api_utils.js
+ websocket_helpers.js
+ websocket_tests.js
+ !/dom/html/test/form_submit_server.sjs
+ !/dom/security/test/cors/file_CrossSiteXHR_server.sjs
+ !/image/test/mochitest/blue.png
+ !/dom/xhr/tests/file_XHRSendData.sjs
+ script_bug1238440.js
+ file_blobURL_expiring.html
+ intersectionobserver_iframe.html
+ intersectionobserver_window.html
+
+[test_anchor_area_referrer.html]
+[test_anchor_area_referrer_changing.html]
+[test_anchor_area_referrer_invalid.html]
+[test_anchor_area_referrer_rel.html]
+[test_anonymousContent_api.html]
+[test_anonymousContent_append_after_reflow.html]
+[test_anonymousContent_canvas.html]
+[test_anonymousContent_insert.html]
+[test_anonymousContent_manipulate_content.html]
+[test_anonymousContent_style_csp.html]
+[test_applet_alternate_content.html]
+[test_appname_override.html]
+[test_async_setTimeout_stack.html]
+[test_async_setTimeout_stack_across_globals.html]
+[test_audioWindowUtils.html]
+[test_audioNotification.html]
+tags = audiochannel
+[test_audioNotificationSilent_audioFile.html]
+tags = audiochannel
+[test_audioNotificationSilent_webAudio.html]
+tags = audiochannel
+[test_audioNotificationStream.html]
+tags = audiochannel
+[test_audioNotificationStopOnNavigation.html]
+tags = audiochannel
+[test_audioNotificationWithEarlyPlay.html]
+tags = audiochannel
+[test_base.xhtml]
+[test_blob_fragment_and_query.html]
+[test_blobconstructor.html]
+[test_blobURL_expiring.html]
+[test_bug5141.html]
+[test_bug28293.html]
+[test_bug28293.xhtml]
+[test_bug51034.html]
+[test_bug116083.html]
+subsuite = clipboard
+[test_bug166235.html]
+subsuite = clipboard
+skip-if = toolkit == 'android'
+[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]
+[test_bug330925.xhtml]
+[test_bug331959.html]
+[test_bug333064.html]
+skip-if = toolkit == 'android'
+[test_bug333198.html]
+[test_bug333673.html]
+[test_bug337631.html]
+[test_bug338541.xhtml]
+[test_bug338583.html]
+skip-if = toolkit == 'android'
+[test_bug338679.html]
+[test_bug339494.html]
+[test_bug339494.xhtml]
+[test_bug340571.html]
+[test_bug343596.html]
+[test_bug345339.html]
+[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_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]
+[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_bug419527.xhtml]
+[test_bug420609.xhtml]
+[test_bug420700.html]
+[test_bug421602.html]
+[test_bug422403-1.html]
+[test_bug422403-2.xhtml]
+[test_bug422537.html]
+[test_bug424212.html]
+[test_bug424359-1.html]
+[test_bug424359-2.html]
+[test_bug426308.html]
+[test_bug426646.html]
+[test_bug428847.html]
+[test_bug429157.html]
+[test_bug431082.html]
+[test_bug431701.html]
+[test_bug431833.html]
+[test_bug433533.html]
+[test_bug433662.html]
+[test_bug435425.html]
+[test_bug444030.xhtml]
+[test_bug444322.html]
+[test_bug444546.html]
+disabled = Disabled for now. Mochitest isn't 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]
+[test_bug456262.html]
+[test_bug457746.html]
+[test_bug459424.html]
+[test_bug461555.html]
+[test_bug461735.html]
+[test_bug465767.html]
+[test_bug466080.html]
+[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 = toolkit == 'android' #bug 855762
+[test_bug482935.html]
+skip-if = toolkit == 'android' #bug 855762
+[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]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug503481b.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug505783.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug513194.html]
+[test_bug514487.html]
+[test_bug515401.html]
+[test_bug518104.html]
+[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]
+skip-if = (os == "android") # Failure with AccessibleCarets on Android, bug 1230229
+[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]
+[test_bug585978.html]
+[test_bug587931.html]
+[test_bug588990.html]
+[test_bug590812.html]
+skip-if = toolkit == 'android' #bug 687032
+[test_bug590870.html]
+[test_bug592366.html]
+[test_bug592829.html]
+[test_bug597345.html]
+[test_bug599295.html]
+[test_bug599588.html]
+[test_bug601803.html]
+[test_bug602838.html]
+[test_bug604592.html]
+[test_bug604660.html]
+[test_bug605982.html]
+[test_bug606729.html]
+[test_bug614058.html]
+[test_bug614583.html]
+[test_bug622088.html]
+[test_bug622117.html]
+[test_bug622246.html]
+[test_bug625722.html]
+[test_bug626262.html]
+[test_bug628938.html]
+[test_bug631615.html]
+[test_bug638112.html]
+[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]
+[test_bug696301-2.html]
+[test_bug698381.html]
+[test_bug698384.html]
+[test_bug704063.html]
+[test_bug704320.html]
+[test_bug704320_policyset.html]
+[test_bug704320_policyset2.html]
+[test_bug704320_preload.html]
+[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]
+[test_bug782342.html]
+[test_bug787778.html]
+[test_bug789315.html]
+[test_bug789856.html]
+[test_bug793311.html]
+[test_bug804395.html]
+[test_bug809003.html]
+[test_bug810494.html]
+[test_bug811701.html]
+[test_bug811701.xhtml]
+[test_bug813919.html]
+[test_bug814576.html]
+[test_bug819051.html]
+[test_bug820909.html]
+[test_bug840098.html]
+[test_bug864595.html]
+[test_bug868999.html]
+[test_bug869000.html]
+[test_bug869002.html]
+[test_bug869006.html]
+[test_bug876282.html]
+[test_bug890580.html]
+[test_bug891952.html]
+[test_bug894874.html]
+[test_bug895239.html]
+[test_bug895974.html]
+[test_bug902847.html]
+[test_bug907892.html]
+[test_bug913761.html]
+[test_bug922681.html]
+[test_bug927196.html]
+[test_bug962251.html]
+[test_bug976673.html]
+[test_bug982153.html]
+[test_bug999456.html]
+[test_bug1022229.html]
+[test_bug1025933.html]
+[test_bug1037687.html]
+[test_bug1043106.html]
+[test_bug1057176.html]
+[test_bug1060938.html]
+[test_bug1064481.html]
+[test_bug1070015.html]
+[test_bug1075702.html]
+[test_bug1081686.html]
+skip-if = toolkit == 'android'
+[test_bug1091883.html]
+[test_bug1101364.html]
+skip-if = toolkit == 'android'
+[test_bug1118689.html]
+[test_bug1126851.html]
+[test_bug1163743.html]
+[test_bug1165501.html]
+[test_bug1187157.html]
+[test_bug1198095.html]
+[test_bug1238440.html]
+[test_bug1250148.html]
+[test_bug1259588.html]
+[test_bug1263696.html]
+[test_bug1268962.html]
+[test_bug1274806.html]
+[test_bug1281963.html]
+[test_bug1295852.html]
+[test_bug1307730.html]
+[test_bug1308069.html]
+[test_bug1314032.html]
+[test_caretPositionFromPoint.html]
+[test_change_policy.html]
+[test_classList.html]
+[test_clearTimeoutIntervalNoArg.html]
+[test_constructor-assignment.html]
+[test_constructor.html]
+[test_copyimage.html]
+subsuite = clipboard
+skip-if = toolkit == 'android' #bug 904183
+[test_copypaste.html]
+subsuite = clipboard
+skip-if = toolkit == 'android' #bug 904183
+[test_copypaste.xhtml]
+subsuite = clipboard
+skip-if = toolkit == 'android' #bug 904183
+[test_createHTMLDocument.html]
+[test_declare_stylesheet_obsolete.html]
+[test_dialogArguments.html]
+tags = openwindow
+skip-if = toolkit == 'android' || e10s # showmodaldialog
+[test_document.all_iteration.html]
+[test_document.all_unqualified.html]
+[test_document_constructor.html]
+[test_document_importNode_document.html]
+[test_document_register.html]
+[test_domcursor.html]
+[test_domparser_null_char.html]
+[test_domparsing.html]
+[test_domrequest.html]
+[test_domwindowutils.html]
+[test_e4x_for_each.html]
+[test_element.matches.html]
+[test_element_closest.html]
+[test_elementTraversal.html]
+[test_encodeToStringWithMaxLength.html]
+[test_error.html]
+[test_EventSource_redirects.html]
+[test_explicit_user_agent.html]
+[test_file_from_blob.html]
+[test_file_negative_date.html]
+[test_fileapi.html]
+[test_fileapi_slice.html]
+skip-if = (toolkit == 'android') # Android: Bug 775227
+[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_referrer.html]
+[test_iframe_referrer_changing.html]
+[test_iframe_referrer_invalid.html]
+[test_Image_constructor.html]
+[test_img_referrer.html]
+[test_innersize_scrollport.html]
+[test_integer_attr_with_leading_zero.html]
+[test_intersectionobservers.html]
+skip-if = true # Track Bug 1320704
+[test_ipc_messagemanager_blob.html]
+[test_link_prefetch.html]
+skip-if = !e10s # Track Bug 1281415
+[test_link_stylesheet.html]
+[test_messagemanager_targetchain.html]
+[test_meta_viewport0.html]
+skip-if = (os != 'android') # meta-viewport tag support is mobile-only
+[test_meta_viewport1.html]
+skip-if = (os != 'android') # meta-viewport tag support is mobile-only
+[test_meta_viewport2.html]
+skip-if = (os != 'android') # meta-viewport tag support is mobile-only
+[test_meta_viewport3.html]
+skip-if = (os != 'android') # meta-viewport tag support is mobile-only
+[test_meta_viewport4.html]
+skip-if = (os != 'android') # meta-viewport tag support is mobile-only
+[test_meta_viewport5.html]
+skip-if = (os != 'android') # meta-viewport tag support is mobile-only
+[test_meta_viewport6.html]
+skip-if = (os != 'android') # meta-viewport tag support is mobile-only
+[test_meta_viewport7.html]
+skip-if = (os != 'android') # meta-viewport tag support is mobile-only
+[test_mozbrowser_apis_blocked.html]
+[test_mozfiledataurl.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_mozMatchesSelector.html]
+[test_mutationobserver_anonymous.html]
+[test_mutationobservers.html]
+[test_named_frames.html]
+[test_navigator_hardwareConcurrency.html]
+[test_navigator_language.html]
+[test_navigatorPrefOverride.html]
+[test_noAudioNotification.html]
+tags = audiochannel
+[test_noAudioNotificationOnMutedElement.html]
+tags = audiochannel
+[test_noAudioNotificationOnMutedOrVolume0Element.html]
+tags = audiochannel
+[test_noAudioNotificationOnVolume0Element.html]
+tags = audiochannel
+[test_NodeIterator_basics_filters.xhtml]
+[test_NodeIterator_mutations_1.xhtml]
+[test_NodeIterator_mutations_2.html]
+[test_NodeIterator_mutations_3.html]
+[test_nodelist_holes.html]
+[test_nonascii_blob_url.html]
+[test_noWebAudioNotification.html]
+tags = audiochannel
+[test_open_null_features.html]
+[test_openDialogChromeOnly.html]
+tags = openwindow
+[test_orientation_alternate.html]
+skip-if = true # bug 1312417
+[test_orientation_frame.html]
+skip-if = true # bug 1312417
+[test_orientation_frame_lock.html]
+skip-if = true # bug 1312417
+[test_orientation_sandbox_no_lock.html]
+skip-if = true # bug 1312417
+[test_plugin_freezing.html]
+skip-if = toolkit == 'android' #CLICK_TO_PLAY
+[test_pluginAudioNotification.html]
+tags = audiochannel
+skip-if = toolkit == 'android' # Plugins don't work on Android
+[test_pluginMutedBeforePlay.html]
+tags = audiochannel
+skip-if = toolkit == 'android' # Plugins don't work on Android
+[test_postMessage_solidus.html]
+[test_postMessages.html]
+support-files = worker_postMessages.js
+[test_postMessage_originAttributes.html]
+support-files = file_receiveMessage.html
+[test_processing_instruction_update_stylesheet.xhtml]
+[test_progress_events_for_gzip_data.html]
+[test_range_bounds.html]
+skip-if = toolkit == 'android'
+[test_reentrant_flush.html]
+skip-if = toolkit == 'android'
+[test_referrer_redirect.html]
+[test_root_iframe.html]
+[test_screen_orientation.html]
+[test_script_loader_crossorigin_data_url.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_simplecontentpolicy.html]
+skip-if = e10s # Bug 1156489.
+[test_text_wholeText.html]
+[test_textnode_normalize_in_selection.html]
+[test_textnode_split_in_selection.html]
+[test_timer_flood.html]
+[test_title.html]
+[test_treewalker_nextsibling.xml]
+[test_user_select.html]
+skip-if = toolkit == 'android'
+[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]
+[test_webaudioNotification.html]
+tags = audiochannel
+[test_webaudioNotificationStopOnNavigation.html]
+tags = audiochannel
+[test_websocket_basic.html]
+skip-if = toolkit == 'android'
+[test_websocket_hello.html]
+skip-if = toolkit == 'android'
+[test_websocket_permessage_deflate.html]
+skip-if = toolkit == 'android'
+[test_webSocket_sandbox.html]
+skip-if = toolkit == 'android'
+support-files = iframe_webSocket_sandbox.html
+[test_websocket1.html]
+skip-if = toolkit == 'android'
+[test_websocket2.html]
+skip-if = toolkit == 'android'
+[test_websocket3.html]
+skip-if = toolkit == 'android'
+[test_websocket4.html]
+skip-if = toolkit == 'android'
+[test_websocket5.html]
+skip-if = toolkit == 'android'
+[test_window_constructor.html]
+[test_window_cross_origin_props.html]
+[test_window_define_nonconfigurable.html]
+skip-if = release_or_beta
+[test_window_define_symbol.html]
+[test_window_element_enumeration.html]
+[test_window_enumeration.html]
+[test_window_extensible.html]
+[test_window_indexing.html]
+[test_window_named_frame_enumeration.html]
+[test_window_orientation.html]
+skip-if = true # bug 1312417
+[test_window_proto.html]
+[test_writable-replaceable.html]
+[test_x-frame-options.html]
+[test_xbl_userdata.xhtml]
+[test_youtube_flash_embed.html]
+# Please keep alphabetical order.
diff --git a/dom/base/test/moz.build b/dom/base/test/moz.build
new file mode 100644
index 000000000..5e60632e4
--- /dev/null
+++ b/dom/base/test/moz.build
@@ -0,0 +1,37 @@
+# -*- 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',
+ 'websocket_hybi/mochitest.ini',
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ 'chrome.ini',
+ 'chrome/chrome.ini',
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ 'browser.ini',
+]
+
+TEST_DIRS += [
+ 'gtest',
+ 'jsmodules'
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.dom.base.test.chrome += [
+ 'chrome/bug421622-referer.sjs',
+ 'chrome/bug884693.sjs',
+ 'chrome/nochrome_bug765993.html',
+ 'chrome/nochrome_bug765993.js',
+ 'chrome/nochrome_bug765993.js^headers^',
+]
diff --git a/dom/base/test/mozbrowser_api_utils.js b/dom/base/test/mozbrowser_api_utils.js
new file mode 100644
index 000000000..92b080939
--- /dev/null
+++ b/dom/base/test/mozbrowser_api_utils.js
@@ -0,0 +1,72 @@
+const FRAME_URL = "http://example.org/";
+
+const METHODS = {
+ setVisible: {},
+ getVisible: {},
+ setActive: {},
+ getActive: {},
+ addNextPaintListener: {},
+ removeNextPaintListener: {},
+ sendMouseEvent: {},
+ sendTouchEvent: {},
+ goBack: {},
+ goForward: {},
+ reload: {},
+ stop: {},
+ download: {},
+ purgeHistory: {},
+ getScreenshot: {},
+ zoom: {},
+ getCanGoBack: {},
+ getCanGoForward: {},
+ getContentDimensions: {},
+ setInputMethodActive: {},
+ findAll: {},
+ findNext: {},
+ clearMatch: {},
+ executeScript: {},
+ getWebManifest: {},
+ mute: {},
+ unmute: {},
+ getMuted: {},
+ setVolume: {},
+ getVolume: {},
+};
+
+const ATTRIBUTES = [
+ "allowedAudioChannels",
+];
+
+function once(target, eventName, useCapture = false) {
+ info("Waiting for event: '" + JSON.stringify(eventName) + "' on " + target + ".");
+
+ return new Promise(resolve => {
+ for (let [add, remove] of [
+ ["addEventListener", "removeEventListener"],
+ ["addMessageListener", "removeMessageListener"],
+ ]) {
+ if ((add in target) && (remove in target)) {
+ eventName.forEach(evName => {
+ target[add](evName, function onEvent(...aArgs) {
+ info("Got event: '" + evName + "' on " + target + ".");
+ target[remove](evName, onEvent, useCapture);
+ resolve(aArgs);
+ }, useCapture);
+ });
+ break;
+ }
+ }
+ });
+}
+
+function* loadFrame(attributes = {}) {
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", FRAME_URL);
+ for (let key in attributes) {
+ iframe.setAttribute(key, attributes[key]);
+ }
+ let loaded = once(iframe, [ "load", "mozbrowserloadend" ]);
+ document.body.appendChild(iframe);
+ yield loaded;
+ return iframe;
+}
diff --git a/dom/base/test/mutationobserver_dialog.html b/dom/base/test/mutationobserver_dialog.html
new file mode 100644
index 000000000..2cc815309
--- /dev/null
+++ b/dom/base/test/mutationobserver_dialog.html
@@ -0,0 +1,62 @@
+<html>
+ <head>
+ <title></title>
+ <script>
+
+ 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;
+ }
+
+ var didCall1 = false;
+ var didCall2 = false;
+ function testMutationObserverInDialog() {
+ div.innerHTML = "<span>1</span><span>2</span>";
+ m = new M(function(records, observer) {
+ opener.is(records[0].type, "childList", "Should have got childList");
+ opener.is(records[0].removedNodes.length, 2, "Should have got removedNodes");
+ opener.is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ observer.disconnect();
+ m = null;
+ didCall1 = true;
+ });
+ m.observe(div, { childList: true });
+ div.innerHTML = "<span><span>foo</span></span>";
+ }
+
+ function testMutationObserverInDialog2() {
+ div.innerHTML = "<span>1</span><span>2</span>";
+ m = new M(function(records, observer) {
+ opener.is(records[0].type, "childList", "Should have got childList");
+ opener.is(records[0].removedNodes.length, 2, "Should have got removedNodes");
+ opener.is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ observer.disconnect();
+ m = null;
+ didCall2 = true;
+ });
+ m.observe(div, { childList: true });
+ div.innerHTML = "<span><span>foo</span></span>";
+ }
+
+ window.addEventListener("load", testMutationObserverInDialog);
+ window.addEventListener("load", testMutationObserverInDialog2);
+ window.addEventListener("load",
+ function() {
+ opener.ok(didCall1, "Should have called 1st mutation callback");
+ opener.ok(didCall2, "Should have called 2nd mutation callback");
+ window.close();
+ });
+ </script>
+ <style>
+ </style>
+ </head>
+ <body>
+ <input type="button" onclick="window.close()" value="close">
+ </body>
+</html>
diff --git a/dom/base/test/noaudio.webm b/dom/base/test/noaudio.webm
new file mode 100644
index 000000000..9207017fb
--- /dev/null
+++ b/dom/base/test/noaudio.webm
Binary files differ
diff --git a/dom/base/test/orientationcommon.js b/dom/base/test/orientationcommon.js
new file mode 100644
index 000000000..2d87cf720
--- /dev/null
+++ b/dom/base/test/orientationcommon.js
@@ -0,0 +1,21 @@
+function specialPowersLock(orientation) {
+ return new Promise(function(resolve, reject) {
+ SpecialPowers.pushPrefEnv({
+ 'set': [ ["dom.screenorientation.testing.non_fullscreen_lock_allow", true] ]
+ }, function() {
+ var p = screen.orientation.lock(orientation);
+ resolve(p);
+ });
+ });
+}
+
+function specialPowersUnlock() {
+ return new Promise(function(resolve, reject) {
+ SpecialPowers.pushPrefEnv({
+ 'set': [ ["dom.screenorientation.testing.non_fullscreen_lock_allow", true] ]
+ }, function() {
+ screen.orientation.unlock();
+ resolve();
+ });
+ });
+}
diff --git a/dom/base/test/referrerHelper.js b/dom/base/test/referrerHelper.js
new file mode 100644
index 000000000..207bf5f15
--- /dev/null
+++ b/dom/base/test/referrerHelper.js
@@ -0,0 +1,286 @@
+/**
+ * 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 if (xhr.status == 418) {
+ // Ignore HSTS priming responses
+ return;
+ } 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.indexOf(ref) >= 0,
+ 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() {
+ 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();
+ },
+ 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 000000000..527d1c31a
--- /dev/null
+++ b/dom/base/test/referrer_change_server.sjs
@@ -0,0 +1,139 @@
+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_helper.js b/dom/base/test/referrer_helper.js
new file mode 100644
index 000000000..c46bdea7a
--- /dev/null
+++ b/dom/base/test/referrer_helper.js
@@ -0,0 +1,102 @@
+/*
+ * 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 anchor and area referrer attributes are honoured (1174913)
+ */
+var tests = (function() {
+
+ // enable referrer attribute
+ yield SpecialPowers.pushPrefEnv({"set": [['network.http.enablePerElementReferrer', true]]}, advance);
+ yield SpecialPowers.pushPrefEnv({"set": [['security.mixed_content.block_active_content', 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++) {
+ var actions = testCases[j].ACTION;
+ var tests = testCases[j].TESTS;
+ for (var k = 0; k < actions.length; k++) {
+ var actionString = actions[k];
+ for (var i = 0; i < tests.length; i++) {
+ yield resetState();
+ var searchParams = new URLSearchParams();
+ searchParams.append("ACTION", actionString);
+ searchParams.append("NAME", tests[i].NAME);
+ for (var l of PARAMS) {
+ if (tests[i][l]) {
+ searchParams.append(l, tests[i][l]);
+ }
+ }
+ var schemeFrom = tests[i].SCHEME_FROM || "http";
+ yield iframe.src = schemeFrom + SJS + searchParams.toString();
+ yield checkIndividualResults(tests[i].DESC, tests[i].RESULT, tests[i].NAME);
+ };
+ };
+ };
+
+ // complete. Be sure to yield so we don't call this twice.
+ yield SimpleTest.finish();
+})(); \ No newline at end of file
diff --git a/dom/base/test/referrer_testserver.sjs b/dom/base/test/referrer_testserver.sjs
new file mode 100644
index 000000000..484680a15
--- /dev/null
+++ b/dom/base/test/referrer_testserver.sjs
@@ -0,0 +1,391 @@
+/*
+* 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 BASE_URL = "example.com/tests/dom/base/test/" + SJS;
+const SHARED_KEY = SJS;
+const SAME_ORIGIN = "mochi.test:8888/tests/dom/base/test/" + SJS;
+const CROSS_ORIGIN = "test1.example.com/tests/dom/base/test/" + SJS;
+
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
+
+function createTestUrl(aPolicy, aAction, aName, aType, aSchemeFrom, aSchemeTo) {
+ var schemeTo = aSchemeTo || "http";
+ var schemeFrom = aSchemeFrom || "http";
+ return schemeTo + "://" + BASE_URL +
+ "ACTION=" + aAction + "&" +
+ "policy=" + aPolicy + "&" +
+ "NAME=" + aName + "&" +
+ "type=" + aType + "&" +
+ "SCHEME_FROM=" + schemeFrom;
+}
+
+// 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 + 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>`;
+}
+
+// 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 + 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 relString = "";
+ if (aRel) {
+ relString = `rel=${aRel}`;
+ }
+
+ var changeString = "";
+ var policy = aAttributePolicy ? aAttributePolicy : aMetaPolicy;
+ var elementString = aStringBuilder(policy, aName, relString, 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", aSchemeFrom, aSchemeTo)}";`;
+ } else if (aTestType === "property") {
+ changeString = `var link = document.getElementById("test_link");
+ link.referrerPolicy = "${aNewAttributePolicy}";
+ link.href = "${createTestUrl(policy, "test", aName, "link_element", aSchemeFrom, aSchemeTo)}";`;
+ }
+
+ return `<!DOCTYPE HTML>
+ <html>
+ <head>
+ ${metaString}
+ ${elementString}
+ </head>
+ <body>
+ <script>
+ ${changeString}
+ </script>
+ </body>
+ </html>`;
+}
+
+function buildLinkString(aPolicy, aName, aRelString, aSchemeFrom, aSchemeTo, aTestType) {
+ var result;
+ var href = '';
+ var onChildComplete = `window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");`
+ if (!aTestType) {
+ href = `href=${createTestUrl(aPolicy, "test", aName, "link_element", aSchemeFrom, aSchemeTo)}`;
+ }
+ if (!aPolicy) {
+ result = `<link ${aRelString} ${href} id="test_link" onload='${onChildComplete}' onerror='${onChildComplete}'>`;
+ } else {
+ result = `<link ${aRelString} ${href} referrerpolicy=${aPolicy} id="test_link" onload='${onChildComplete}' onerror='${onChildComplete}'>`;
+ }
+
+ return result;
+}
+
+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";
+
+ 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 + 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 + 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") {
+ // 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;
+ }
+
+ // 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;
+ }
+
+ 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 000000000..32e2c4995
--- /dev/null
+++ b/dom/base/test/reftest/mixed-bmp-png.ico
Binary files differ
diff --git a/dom/base/test/reftest/reftest-stylo.list b/dom/base/test/reftest/reftest-stylo.list
new file mode 100644
index 000000000..8fd85a02f
--- /dev/null
+++ b/dom/base/test/reftest/reftest-stylo.list
@@ -0,0 +1,2 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+== test_bug920877.html test_bug920877.html
diff --git a/dom/base/test/reftest/reftest.list b/dom/base/test/reftest/reftest.list
new file mode 100644
index 000000000..f8a7a81d5
--- /dev/null
+++ b/dom/base/test/reftest/reftest.list
@@ -0,0 +1 @@
+== test_bug920877.html test_bug920877-ref.html
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 000000000..1a593e584
--- /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 000000000..18bae4009
--- /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/script-1_bug597345.sjs b/dom/base/test/script-1_bug597345.sjs
new file mode 100644
index 000000000..50d23b712
--- /dev/null
+++ b/dom/base/test/script-1_bug597345.sjs
@@ -0,0 +1,16 @@
+// 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 000000000..54d33e654
--- /dev/null
+++ b/dom/base/test/script-2_bug597345.js
@@ -0,0 +1 @@
+document.write("Räksmörgås"); \ No newline at end of file
diff --git a/dom/base/test/script_bug1238440.js b/dom/base/test/script_bug1238440.js
new file mode 100644
index 000000000..3499cd2ce
--- /dev/null
+++ b/dom/base/test/script_bug1238440.js
@@ -0,0 +1,31 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+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();
+
+ sendAsyncMessage(answer, {
+ file: File.createFromNsIFile(tmpFile)
+ });
+}
+
+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.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 000000000..c60c3ebab
--- /dev/null
+++ b/dom/base/test/script_bug602838.sjs
@@ -0,0 +1,37 @@
+function setOurState(data) {
+ x = { data: data, QueryInterface: function(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 000000000..05b4d9d1b
--- /dev/null
+++ b/dom/base/test/script_postmessages_fileList.js
@@ -0,0 +1,25 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+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");
+
+ sendAsyncMessage("file.opened", {
+ file: File.createFromNsIFile(testFile)
+ });
+});
+
+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 000000000..6a13440e6
--- /dev/null
+++ b/dom/base/test/send_gzip_content.sjs
@@ -0,0 +1,48 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+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, 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: function(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/somedatas.resource b/dom/base/test/somedatas.resource
new file mode 100644
index 000000000..bc277681d
--- /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 000000000..6a63b5341
--- /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 000000000..8d73fedba
--- /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 type="text/javascript" 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 000000000..d17634b32
--- /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 type="application/javascript" 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 000000000..5d4a11ad3
--- /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 type="text/javascript" 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 iterator =
+ document.createNodeIterator(document, NodeFilter.SHOW_ALL,
+ function() {
+ if (n < 0)
+ iterator.detach();
+ return NodeFilter.FILTER_ACCEPT;
+ });
+ while (--n >= 0)
+ iterator.nextNode();
+ try {
+ iterator[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 iterator = (function(filter) {
+ 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,
+ filter);
+ })(function filter(n) {
+ if (n.nodeName != "img")
+ return NodeFilter.FILTER_ACCEPT;
+
+ iterator.detach();
+
+ n.parentNode.parentNode.removeChild(n.parentNode);
+ // 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(iterator.nextNode().nodeName, "div",
+ "iterator.nextNode() returned the wrong node");
+ is(iterator.nextNode().nodeName, "span",
+ "iterator.nextNode() returned the wrong node");
+ try {
+ var p = iterator.nextNode();
+ ok(false, "iterator.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 000000000..028734b6a
--- /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 type="text/javascript" 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 000000000..a032f8a5b
--- /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 type="text/javascript" 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 000000000..d3951541d
--- /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 type="text/javascript" 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.parentNode.removeChild(n);
+ }
+ 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 000000000..acf96080a
--- /dev/null
+++ b/dom/base/test/test_anchor_area_referrer.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test anchor and area policy attribute for Bug 1174913</title>
+ <script type="application/javascript" 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;version=1.8">
+
+ 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;version=1.7" 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 000000000..167d2ef63
--- /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 type="application/javascript" 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;version=1.8">
+
+ 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;version=1.7" 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 000000000..ed56aa399
--- /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 type="application/javascript" 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;version=1.8">
+
+ 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;version=1.7" 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 000000000..859cf4eee
--- /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 type="application/javascript" 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;version=1.8">
+
+ 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;version=1.7" 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_anonymousContent_api.html b/dom/base/test/test_anonymousContent_api.html
new file mode 100644
index 000000000..4c6aa51ac
--- /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 type="application/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=1020244">Mozilla Bug 1020244</a>
+ <script type="application/javascript;version=1.8">
+
+ // 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"];
+ 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 000000000..b88de89d5
--- /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 type="application/javascript" 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;version=1.8">
+ 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 000000000..9ee1eca20
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_canvas.html
@@ -0,0 +1,57 @@
+<!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 type="application/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=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;version=1.8">
+ 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"
+ );
+
+ 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 000000000..c502ed254
--- /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 type="application/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=1020244">Mozilla Bug 1020244</a>
+<div>
+ <div id="id" class="test">text content</div>
+</div>
+ <script type="application/javascript;version=1.8">
+ 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 000000000..ef4c00287
--- /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 type="application/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=1020244">Mozilla Bug 1020244</a>
+<div>
+ <div id="test-element" class="test-class" test="test">text content</div>
+</div>
+ <script type="application/javascript;version=1.8">
+
+ // 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, "pending", "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_style_csp.html b/dom/base/test/test_anonymousContent_style_csp.html
new file mode 100644
index 000000000..6f4192699
--- /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 type="application/javascript" 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;version=1.8">
+ 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 000000000..b7b3c8a4f
--- /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.xul b/dom/base/test/test_anonymousContent_xul_window.xul
new file mode 100644
index 000000000..1dcb2d201
--- /dev/null
+++ b/dom/base/test/test_anonymousContent_xul_window.xul
@@ -0,0 +1,30 @@
+<?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 aren't supported, that the API throws, but doesn't crash.
+-->
+<window title="Anonymous content in a XUL window"
+xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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;version=1.8">
+ // Insert content
+ let testElement = document.querySelector("box");
+
+ let didThrow = false;
+ try {
+ document.insertAnonymousContent(testElement);
+ } catch (e) {
+ didThrow = true;
+ }
+
+ ok(didThrow,
+ "Inserting anonymous content in a XUL document did throw an exception")
+ </script>
+</window>
diff --git a/dom/base/test/test_applet_alternate_content.html b/dom/base/test/test_applet_alternate_content.html
new file mode 100644
index 000000000..88cb12f1f
--- /dev/null
+++ b/dom/base/test/test_applet_alternate_content.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1200602
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1200602</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" 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=1200602">Mozilla Bug 1200602</a>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+
+function test() {
+ "use strict";
+
+ const objLC = SpecialPowers.Ci.nsIObjectLoadingContent;
+ let obj = document.createElement("applet");
+ obj.appendChild(document.createTextNode("alternate content"));
+ 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");
+ ok(obj.pluginFallbackType == objLC.PLUGIN_ALTERNATE,
+ "expected alternate fallback mode");
+}
+
+// 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();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_appname_override.html b/dom/base/test/test_appname_override.html
new file mode 100644
index 000000000..d94977a53
--- /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 type="application/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=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 000000000..fb3c74b29
--- /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 type="application/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=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', true]]},
+ 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 000000000..5b44072d6
--- /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 type="application/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=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', true]]},
+ a);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_audioNotification.html b/dom/base/test/test_audioNotification.html
new file mode 100644
index 000000000..a7f42f106
--- /dev/null
+++ b/dom/base/test/test_audioNotification.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.src = "audio.ogg";
+
+var tests = [
+ function() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ audio.pause();
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_audioNotificationSilent_audioFile.html b/dom/base/test/test_audioNotificationSilent_audioFile.html
new file mode 100644
index 000000000..0b3b89580
--- /dev/null
+++ b/dom/base/test/test_audioNotificationSilent_audioFile.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Audio-playback should be inactive when input file is silent</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.7">
+
+SimpleTest.waitForExplicitFinish();
+
+var generator = runTest();
+var expectedPlaybackActive = null;
+
+var audio = new Audio();
+audio.src = "audioEndedDuringPlaying.webm";
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedPlaybackActive, "Corrrect audible state");
+ continueTest();
+ }
+};
+
+function continueTest() {
+ try {
+ generator.next();
+ } catch (e if e instanceof StopIteration) {
+ error("Stop test because of exception!");
+ }
+}
+
+function audioPlayingStart() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+
+ expectedPlaybackActive = 'active';
+
+ info("Audio playing start");
+ audio.play();
+}
+
+function audioBecomeSilentDuringPlaying() {
+ info("Audio would become silent during playing");
+
+ expectedPlaybackActive = 'inactive-nonaudible';
+}
+
+function finish() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+
+ SimpleTest.finish();
+}
+
+function runTest() {
+ yield audioPlayingStart();
+
+ yield audioBecomeSilentDuringPlaying();
+
+ yield finish();
+}
+
+continueTest();
+
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/test_audioNotificationSilent_webAudio.html b/dom/base/test/test_audioNotificationSilent_webAudio.html
new file mode 100644
index 000000000..de4165f8e
--- /dev/null
+++ b/dom/base/test/test_audioNotificationSilent_webAudio.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Audio-playback should be inactive when web-audio is silent</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.7">
+
+SimpleTest.waitForExplicitFinish();
+
+var generator = runTest();
+var expectedPlaybackActive = null;
+var expectedPlaying = null;
+
+var ac = new AudioContext();
+var audibleDuration = 3;
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedPlaybackActive, "Corrrect audible state");
+ is(ac.state, expectedPlaying, "Corrrect playing state");
+ continueTest();
+ }
+};
+
+function continueTest() {
+ try {
+ generator.next();
+ } catch (e if e instanceof StopIteration) {
+ error("Stop test because of exception!");
+ }
+}
+
+function playOscillatorNode() {
+ var dest = ac.destination;
+ var osc = ac.createOscillator();
+ osc.connect(dest);
+ osc.start(0);
+ osc.stop(ac.currentTime + audibleDuration);
+}
+
+function audioPlayingStart() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+
+ expectedPlaybackActive = 'active';
+ expectedPlaying = "running";
+
+ info("Audio playing start");
+ playOscillatorNode();
+}
+
+function audioBecomeSilentDuringPlaying() {
+ info("Audio would become silent during playing");
+
+ expectedPlaybackActive = 'inactive-pause';
+ expectedPlaying = "running";
+}
+
+function finish() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+
+ SimpleTest.finish();
+}
+
+function startAudioContext() {
+ if (ac.state != "running") {
+ ac.resume();
+ ac.onstatechange = function() {
+ if (ac.state == "running") {
+ ok(true, "AudioContext starts running!");
+ ac.onstatechange = null;
+ continueTest();
+ }
+ }
+ } else {
+ ok(true, "AudioContext is running!");
+ continueTest();
+ }
+}
+
+function runTest() {
+ yield startAudioContext();
+
+ yield audioPlayingStart();
+
+ yield audioBecomeSilentDuringPlaying();
+
+ yield finish();
+}
+
+continueTest();
+
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/test_audioNotificationStopOnNavigation.html b/dom/base/test/test_audioNotificationStopOnNavigation.html
new file mode 100644
index 000000000..2ca0d7778
--- /dev/null
+++ b/dom/base/test/test_audioNotificationStopOnNavigation.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+<iframe></iframe>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+var iframe = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var tests = [
+ function() {
+ iframe = document.querySelector("iframe");
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ iframe.src = "file_audioLoop.html";
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ iframe.src = "data:text/html,page without audio";
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+onload = runTest;
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_audioNotificationStream.html b/dom/base/test/test_audioNotificationStream.html
new file mode 100644
index 000000000..f27f99316
--- /dev/null
+++ b/dom/base/test/test_audioNotificationStream.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.srcObject = (new AudioContext()).createMediaStreamDestination().stream;
+
+var tests = [
+ function() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ audio.pause();
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_audioNotificationWithEarlyPlay.html b/dom/base/test/test_audioNotificationWithEarlyPlay.html
new file mode 100644
index 000000000..23b051250
--- /dev/null
+++ b/dom/base/test/test_audioNotificationWithEarlyPlay.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.loop = true;
+audio.preload = "metadata";
+
+var tests = [
+ function() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ audio.src = "audio.ogg";
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ audio.pause();
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_audioWindowUtils.html b/dom/base/test/test_audioWindowUtils.html
new file mode 100644
index 000000000..a6067cfd6
--- /dev/null
+++ b/dom/base/test/test_audioWindowUtils.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<iframe src="about:blank" id="iframe"></iframe>
+<script type="application/javascript">
+
+function runTest() {
+ var utils = SpecialPowers.wrap(window).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ ok(utils, "nsIDOMWindowUtils");
+
+ is(utils.audioMuted, false, "By default utils.audioMuted is false");
+ utils.audioMuted = true;
+ is(utils.audioMuted, true, "utils.audioMuted is true");
+ utils.audioMuted = false;
+ is(utils.audioMuted, false, "utils.audioMuted is true");
+
+ is(utils.audioVolume, 1.0, "By default utils.audioVolume is 1.0");
+ utils.audioVolume = 0.4;
+ is(utils.audioVolume.toFixed(2), "0.40", "utils.audioVolume is ok");
+ utils.audioMuted = true;
+ is(utils.audioMuted, true, "utils.audioMuted is true");
+ is(utils.audioVolume.toFixed(2), "0.40", "utils.audioVolume is ok");
+ utils.audioMuted = false;
+
+ utils.audioVolume = 2.0;
+ is(utils.audioVolume, 2.0, "utils.audioVolume is ok");
+
+ try {
+ utils.audioVolume = -42;
+ ok(false, "This should throw");
+ } catch(e) {
+ ok(true, "This should throw");
+ }
+
+ utils.audioVolume = 0;
+ is(utils.audioVolume, 0.0, "utils.audioVolume is ok");
+ utils.audioVolume = 1.0;
+ is(utils.audioVolume, 1.0, "utils.audioVolume is ok");
+
+ var iframe = document.getElementById("iframe");
+ ok(iframe, "IFrame exists");
+
+ utils = SpecialPowers.wrap(iframe.contentWindow).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ ok(utils, "nsIDOMWindowUtils");
+
+ is(utils.audioMuted, false, "By default utils.audioMuted is false");
+ utils.audioMuted = true;
+ is(utils.audioMuted, true, "utils.audioMuted is true");
+ utils.audioMuted = false;
+ is(utils.audioMuted, false, "utils.audioMuted is true");
+
+ is(utils.audioVolume, 1.0, "By default utils.audioVolume is 1.0");
+ utils.audioVolume = 0.4;
+ is(utils.audioVolume.toFixed(2), "0.40", "utils.audioVolume is ok");
+ utils.audioMuted = true;
+ is(utils.audioMuted, true, "utils.audioMuted is true");
+ is(utils.audioVolume.toFixed(2), "0.40", "utils.audioVolume is ok");
+ utils.audioMuted = false;
+
+ utils.audioVolume = 2.0;
+ is(utils.audioVolume, 2.0, "utils.audioVolume is ok");
+
+ try {
+ utils.audioVolume = -42;
+ ok(false, "This should throw");
+ } catch(e) {
+ ok(true, "This should throw");
+ }
+
+ utils.audioVolume = 0;
+ is(utils.audioVolume, 0.0, "utils.audioVolume is ok");
+ utils.audioVolume = 0.6;
+ is(utils.audioVolume.toFixed(2), "0.60", "utils.audioVolume is ok");
+ utils.audioMuted = true;
+
+ // Navigate the iframe to another URL, and verify that the volume and muted
+ // information is preserved.
+ iframe.onload = function() {
+ utils = SpecialPowers.wrap(iframe.contentWindow).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+ ok(utils, "nsIDOMWindowUtils");
+
+ ok(utils.audioMuted, "Audio should still be muted");
+ utils.audioMuted = false;
+ ok(utils.audioVolume.toFixed(2), "0.60", "Volume should be preserved");
+
+ SimpleTest.finish();
+ };
+ iframe.src = "data:text/html,page";
+}
+
+onload = runTest;
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_base.xhtml b/dom/base/test/test_base.xhtml
new file mode 100644
index 000000000..e4aba6ebe
--- /dev/null
+++ b/dom/base/test/test_base.xhtml
@@ -0,0 +1,55 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for base URIs</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <base href="/tests/dom/base/" />
+ <style>
+ #bound { -moz-binding: url("test/file_base_xbl.xml#test"); }
+ </style>
+</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>
+<div id="bound"/>
+<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/supercalifragilisticexpialidocious",
+ "http://mochi.test:8888/tests/dom/base/supercalifragilisticexpialidocious",
+ "http://mochi.test:8888/tests/dom/base/hello/",
+ "http://mochi.test:8888/tests/dom/base/hello/world",
+ "http://mochi.test:8888/tests/dom/base/hello/world#iamtheverymodelofamodernmajorgeneral",
+ "http://mochi.test:8888/tests/dom/base/hello/world#iamtheverymodelofamodernmajorgeneral",
+ ];
+ 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");
+
+ var svgExpected =
+ ["http://mochi.test:8888/tests/dom/base/test/file_base_xbl.xml",
+ "http://mochi.test:8888/tests/dom/base/test/file_base_xbl.xml",
+ "http://mochi.test:8888/tests/dom/base/test/file_base_xbl.xml#shesellsseashellsbytheseashore",
+ ];
+ node = SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById("bound"))[0];
+ while(node) {
+ is(node.baseURI, svgExpected.shift(), "node base");
+ node = node.firstChild;
+ }
+ is(svgExpected.length, 0, "found all expected nodes");
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_blobURL_expiring.html b/dom/base/test/test_blobURL_expiring.html
new file mode 100644
index 000000000..a4a796849
--- /dev/null
+++ b/dom/base/test/test_blobURL_expiring.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Blob URI expiration</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script>
+
+onmessage = function(e) {
+ var blobURL = e.data;
+
+ (new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", blobURL);
+ xhr.send();
+ xhr.onload = function() {
+ is(xhr.response, "123", "Response matches!");
+ resolve();
+ }
+ })).then(function() {
+ document.body.removeChild(iframe);
+ }).then(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", blobURL);
+ try {
+ xhr.send();
+ ok(false, "The URL should be done!");
+ } catch(e) {
+ ok(true, "The URL should be done!");
+ }
+
+ SimpleTest.finish();
+ });
+}
+
+var iframe = document.createElement('iframe');
+iframe.src = 'file_blobURL_expiring.html';
+document.body.appendChild(iframe);
+
+SimpleTest.waitForExplicitFinish();
+
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_blob_fragment_and_query.html b/dom/base/test/test_blob_fragment_and_query.html
new file mode 100644
index 000000000..bc454e69c
--- /dev/null
+++ b/dom/base/test/test_blob_fragment_and_query.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Blob URI with fragments</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script>
+
+var blob = new Blob(['hello world']);
+ok(blob, "We have a blob.");
+
+var tests = [
+ { part: "", revoke: true },
+ { part: "?aa", revoke: false },
+ { part: "#bb", revoke: false },
+ { part: "?cc#dd", revoke: false },
+ { part: "#ee?ff", revoke: false }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var url = URL.createObjectURL(blob);
+ ok(url, "We have a URI");
+
+ var test = tests.shift();
+
+ URL.revokeObjectURL(url + test.part);
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url + test.part);
+
+ xhr.onload = function() {
+ is(xhr.responseText, 'hello world', 'URL: ' + url + test.part);
+ runTest();
+ }
+
+ try {
+ xhr.send();
+ } catch(e) {
+ ok(test.revoke, "This should fail!");
+ runTest();
+ return;
+ }
+ ok(!test.revoke, "This should succeed!");
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_blobconstructor.html b/dom/base/test/test_blobconstructor.html
new file mode 100644
index 000000000..1245d1e39
--- /dev/null
+++ b/dom/base/test/test_blobconstructor.html
@@ -0,0 +1,246 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=721569
+-->
+<head>
+ <title>Test for Blob constructor (Bug 721569)</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="fileutils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=721569">Mozilla Bug 721569</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+"use strict";
+/** Test for Bug 721569 **/
+var blob = new Blob();
+ok(blob, "Blob should exist");
+
+ok(blob.size !== undefined, "Blob should have a size property");
+ok(blob.type !== undefined, "Blob should have a type property");
+ok(blob.slice, "Blob should have a slice method");
+
+blob = new Blob([], {type: null});
+ok(blob, "Blob should exist");
+is(blob.type, "null", "Blob type should be stringified");
+
+blob = new Blob([], {type: undefined});
+ok(blob, "Blob should exist");
+is(blob.type, "", "Blob type should be treated as missing");
+
+try {
+blob = new Blob([]);
+ok(true, "an empty blobParts argument should not throw");
+} catch(e) {
+ok(false, "NOT REACHED");
+}
+
+try {
+blob = new Blob(null);
+ok(false, "NOT REACHED");
+} catch(e) {
+ok(true, "a null blobParts member should throw");
+}
+
+try {
+blob = new Blob([], null);
+ok(true, "a null options member should not throw");
+} catch(e) {
+ok(false, "NOT REACHED");
+}
+
+try {
+blob = new Blob([], undefined);
+ok(true, "an undefined options member should not throw");
+} catch(e) {
+ok(false, "NOT REACHED");
+}
+
+try {
+blob = new Blob([], false);
+ok(false, "NOT REACHED");
+} catch(e) {
+ok(true, "a boolean options member should throw");
+}
+
+try {
+blob = new Blob([], 0);
+ok(false, "NOT REACHED");
+} catch(e) {
+ok(true, "a numeric options member should throw");
+}
+
+try {
+blob = new Blob([], "");
+ok(false, "NOT REACHED");
+} catch(e) {
+ok(true, "a string options member should throw");
+}
+
+/** Test for dictionary initialization order **/
+(function() {
+ var o = {};
+ var p = {type: "text/plain", endings: "transparent"};
+ var called = [];
+ function add_to_called(n) {
+ called.push(n);
+ return p[n];
+ }
+ ["type", "endings"].forEach(function(n) {
+ Object.defineProperty(o, n, { get: add_to_called.bind(null, n) });
+ });
+ var b = new Blob([], o);
+ is(JSON.stringify(called), JSON.stringify(["endings", "type"]), "dictionary members should be get in lexicographical order");
+})();
+
+let blob1 = new Blob(["squiggle"]);
+ok(blob1 instanceof Blob, "Blob constructor should produce Blobs");
+ok(!(blob1 instanceof File), "Blob constructor should not produce Files");
+is(blob1.type, "", "Blob constructor with no options should return Blob with empty type");
+is(blob1.size, 8, "Blob constructor should return Blob with correct size");
+
+let blob2 = new Blob(["steak"], {type: "content/type"});
+ok(blob2 instanceof Blob, "Blob constructor should produce Blobs");
+ok(!(blob2 instanceof File), "Blob constructor should not produce Files");
+is(blob2.type, "content/type", "Blob constructor with a type option should return Blob with the type");
+is(blob2.size, 5, "Blob constructor should return Blob with correct size");
+
+
+let aB = new ArrayBuffer(16);
+var int8View = new Int8Array(aB);
+for (var i = 0; i < 16; i++) {
+ int8View[i] = i+65;
+}
+
+let testData =
+ [
+ // Test 3 strings
+ [["foo", "bar", "baz"], {},
+ [{start: 0, length: 9, contents: "foobarbaz"},
+ {start: 0, length: 3, contents: "foo"},
+ {start: 3, length:6, contents: "barbaz"},
+ {start: 6, length: 3, contents: "baz"},
+ {start: 6, length: 6, contents: "baz"},
+ {start: 0, length: 9, contents: "foobarbaz"},
+ {start: 0, length: 11, contents: "foobarbaz"},
+ {start: 10, length: 5, contents: ""}]],
+ // Test string, Blob, string
+ [["foo", blob1, "baz"], {},
+ [{start: 0, length: 3, contents: "foo"},
+ {start: 3, length: 8, contents: "squiggle"},
+ {start: 2, length: 2, contents: "os"},
+ {start: 10, length: 2, contents: "eb"}]],
+ // Test blob, string, blob
+ [[blob1, "foo", blob1], {},
+ [{start: 0, length: 8, contents: "squiggle"},
+ {start: 7, length: 2, contents: "ef"},
+ {start: 10, length: 2, contents: "os"},
+ {start: 1, length: 3, contents: "qui"},
+ {start: 12, length: 3, contents: "qui"},
+ {start: 40, length: 20, contents: ""}]],
+ // Test blobs all the way down
+ [[blob2, blob1, blob2], {},
+ [{start: 0, length: 5, contents: "steak"},
+ {start: 5, length: 8, contents: "squiggle"},
+ {start: 13, length: 5, contents: "steak"},
+ {start: 1, length: 2, contents: "te"},
+ {start: 6, length: 4, contents: "quig"}]],
+ // Test an array buffer
+ [[aB, blob1, "foo"], {},
+ [{start: 0, length: 8, contents: "ABCDEFGH"},
+ {start: 8, length:10, contents: "IJKLMNOPsq"},
+ {start: 17, length: 3, contents: "qui"},
+ {start: 4, length: 8, contents: "EFGHIJKL"}]],
+ // Test an ArrayBufferView
+ [[int8View, blob1, "foo"], {},
+ [{start: 0, length: 8, contents: "ABCDEFGH"},
+ {start: 8, length:10, contents: "IJKLMNOPsq"},
+ {start: 17, length: 3, contents: "qui"},
+ {start: 4, length: 8, contents: "EFGHIJKL"}]],
+ // Test a partial ArrayBufferView
+ [[new Uint8Array(aB, 3, 5), blob1, "foo"], {},
+ [{start: 0, length: 8, contents: "DEFGHsqu"},
+ {start: 8, length:10, contents: "igglefoo"},
+ {start: 4, length: 8, contents: "Hsquiggl"}]],
+ // Test transparent line endings
+ [["foo\r\n", "bar\r", "baz\n"], { endings: "transparent" },
+ [{start: 0, length: 5, contents: "foo\r\n"},
+ {start: 5, length: 4, contents: "bar\r"},
+ {start: 9, length: 4, contents: "baz\n"}]],
+ // Test transparent line endings when the second argument is omitted
+ [["foo\r\n", "bar\r", "baz\n"], undefined,
+ [{start: 0, length: 5, contents: "foo\r\n"},
+ {start: 5, length: 4, contents: "bar\r"},
+ {start: 9, length: 4, contents: "baz\n"}]],
+ // Test native line endings
+ [["foo\r\n", "bar\r", "baz\n"], { endings: "native" },
+ navigator.platform.indexOf("Win") != -1 ?
+ [{start: 0, length: 5, contents: "foo\r\n"},
+ {start: 5, length: 5, contents: "bar\r\n"},
+ {start: 10, length: 5, contents: "baz\r\n"}] :
+ [{start: 0, length: 4, contents: "foo\n"},
+ {start: 4, length: 4, contents: "bar\n"},
+ {start: 8, length: 4, contents: "baz\n"}]],
+ // Test type coercion of a number
+ [[3, int8View, "foo"], {},
+ [{start: 0, length: 8, contents: "3ABCDEFG"},
+ {start: 8, length:10, contents: "HIJKLMNOPf"},
+ {start: 17, length: 4, contents: "foo"},
+ {start: 4, length: 8, contents: "DEFGHIJK"}]]
+ ];
+
+let testCounter = 0;
+
+function doTest(data) {
+ testCounter++;
+
+ var [blobs, options, tests] = data;
+
+ function runTest(test) {
+
+ let blob;
+ if (options !== undefined) {
+ blob = new Blob(blobs, options);
+ } else {
+ blob = new Blob(blobs);
+ }
+ ok(blob, "Test " + testCounter + " got blob");
+ ok(blob instanceof Blob, "Test " + testCounter + " blob is a Blob");
+ ok(!(blob instanceof File), "Test " + testCounter + " blob is not a File");
+
+ let slice = blob.slice(test.start, test.start + test.length);
+ ok(slice, "Test " + testCounter + " got slice");
+ ok(slice instanceof Blob, "Test " + testCounter + " slice is a Blob");
+ ok(!(slice instanceof File), "Test " + testCounter + " slice is not a File");
+ is(slice.size, test.contents.length,
+ "Test " + testCounter + " slice is correct size");
+
+ testFile(slice, test.contents, "Test " + testCounter);
+ }
+ if (Array.isArray(tests)) {
+ tests.forEach(runTest);
+ } else {
+ try {
+ let blob = new Blob(blobs, options);
+ ok(false, "NOT REACHED");
+ } catch (e) {
+ is(e.name, tests, "Blob constructor should throw " + tests);
+ }
+ }
+ SpecialPowers.gc();
+}
+
+SimpleTest.waitForExplicitFinish();
+testData.forEach(doTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug1008126.html b/dom/base/test/test_bug1008126.html
new file mode 100644
index 000000000..cd196de09
--- /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 type="application/javascript" 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;version=1.7">
+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, null, null));
+ 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 000000000..399542d10
--- /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 type="application/javascript" 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.open("chrome://mochitests/content/chrome/dom/base/test/file_empty.html", "1016960", "chrome");
+ ok(chromeWindow instanceof ChromeWindow, "A chrome window should return true for |instanceof ChromeWindow|.");
+ 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 000000000..356cfe085
--- /dev/null
+++ b/dom/base/test/test_bug1022229.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1022229
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1022229</title>
+ <script type="application/javascript" 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.
+ This test is particularly interesting on b2g where we're in a mozBrowser.
+
+ We set the test up with an extra iframe so that we can easily run it in
+ an artificial mozbrowser for desktop builds.
+ **/
+ SimpleTest.waitForExplicitFinish();
+ function go() {
+ var ifr = document.createElement('iframe');
+
+ /* Uncomment this chunk to run in a mozBrowser. Make sure to uncomment the
+ chunk in iframe_main as well. */
+ /*
+ SpecialPowers.Services.prefs.setBoolPref("dom.mozBrowserFramesEnabled", true);
+ SpecialPowers.Services.prefs.setBoolPref("network.disable.ipc.security", true);
+ SpecialPowers.Services.prefs.setBoolPref("dom.ipc.browser_frames.oop_by_default", false);
+ SpecialPowers.addPermission("browser", true, document);
+ SpecialPowers.wrap(ifr).mozbrowser = true;
+ */
+
+ 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 000000000..b48fbf3ef
--- /dev/null
+++ b/dom/base/test/test_bug1025933.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1025933
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1025933</title>
+ <script type="application/javascript" 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 s = document.getElementById("host").createShadowRoot();
+ 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();
+ }
+
+ </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>
+<div id="content">
+ <div id="host"></div>
+</div>
+<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 000000000..097a3d1e6
--- /dev/null
+++ b/dom/base/test/test_bug1037687.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1037687
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1037687</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1037687 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var host;
+ var sr;
+ 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");
+ sr = host.createShadowRoot();
+ embed = createResource(sr, "embed");
+ object = createResource(sr, "object");
+ iframe = createResource(sr, "iframe");
+ }
+
+ </script>
+</head>
+<body onload="test()">
+<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>
+<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 000000000..a16c81378
--- /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 type="application/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=1043106">Mozilla Bug 1043106</a>
+ <iframe id="iframe"></iframe>
+ <script type="application/javascript">
+
+var storage;
+
+window.addEventListener("storage", function (event) {
+ ok(event.storageArea, storage, "The storageArea is correct");
+ storage.removeItem("a");
+ runTests();
+}, false);
+
+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.src = "data:text/html,<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 000000000..e746180dd
--- /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 type="application/javascript" 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 000000000..a93627e57
--- /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 type="application/javascript" 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=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 000000000..e5a3cec26
--- /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 type="application/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=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 000000000..824d2bdf7
--- /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 type="application/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=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 000000000..ccee454df
--- /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 type="application/javascript" 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=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_bug1081686.html b/dom/base/test/test_bug1081686.html
new file mode 100644
index 000000000..bd00c3f64
--- /dev/null
+++ b/dom/base/test/test_bug1081686.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>bug 1081686</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var ws;
+
+function forcegc()
+{
+ SpecialPowers.forceGC();
+ SpecialPowers.gc();
+ setTimeout(function()
+ {
+ SpecialPowers.gc();
+ }, 0);
+}
+
+function testWebSocket () {
+ ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_hello");
+ ws.onopen = function(e) {
+ ws.send("data");
+ }
+ ws.onclose = function(e) {
+ forcegc();
+ setTimeout(function() {
+ is(ws.readyState, 3, 'WebSocket is closed');
+ is(ws.bufferedAmount, 0, 'WebSocket.bufferedAmount should be empty.');
+ is(ws.binaryType, 'blob', 'WebSocket.binaryType is blob');
+ ws.binaryType = 'arraybuffer';
+ is(ws.binaryType, 'arraybuffer', 'WebSocket.binaryType is arraybuffer');
+ is(ws.url, 'ws://mochi.test:8888/tests/dom/base/test/file_websocket_hello', 'WebSocket.url is correct');
+ ws.close();
+ ws.send('foobar');
+ SimpleTest.finish();
+ }, 1000);
+ }
+
+ ws.onerror = function(e) {
+ ok(false, "onerror called!");
+ SimpleTest.finish();
+ }
+ ws.onmessage = function(e) {
+ is(e.data, "Hello world!", "Wrong data");
+ ws.close();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+</script>
+</pre>
+<div>
+
+
+</div>
+
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug1091883.html b/dom/base/test/test_bug1091883.html
new file mode 100644
index 000000000..c7ed948d9
--- /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 type="application/javascript" 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 (var 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 (var 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,
+ "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_bug1101364.html b/dom/base/test/test_bug1101364.html
new file mode 100644
index 000000000..dfcb26a49
--- /dev/null
+++ b/dom/base/test/test_bug1101364.html
@@ -0,0 +1,73 @@
+<!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 {
+ -moz-user-select: none;
+ }
+
+ #testDiv, #test2 {
+ -moz-user-select: text;
+ }
+ </style>
+</head>
+<body id='body'>
+
+<iframe id="test1" src="data:text/html,<h1 id='test1' style='-moz-user-select:none'>Header</h1><div id='testDiv'>test1</div>"></iframe>
+<iframe id="test2" src="data:text/html,<div contenteditable id='test2'>AAA<span id='test2Inner'>BBB</span></div>"></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function test()
+{
+ var iframe1 = document.getElementById('test1');
+ iframe1.focus();
+ var Ci = SpecialPowers.Ci;
+ var webnav = SpecialPowers.wrap(iframe1.contentWindow).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ var docShell = webnav.QueryInterface(Ci.nsIDocShell);
+
+ // test1
+ docShell.doCommand("cmd_selectAll");
+ var withoutContenteditable = snapshotWindow(iframe1.contentWindow);
+
+ iframe1.contentDocument.getElementById('testDiv').setAttribute('contentEditable', true);
+ docShell.doCommand("cmd_selectAll");
+ var withContenteditable = snapshotWindow(iframe1.contentWindow);
+ dump(withoutContenteditable.toDataURL());
+ dump(withContenteditable.toDataURL());
+
+ ok(compareSnapshots(withoutContenteditable, withContenteditable, true)[0], 'Select all should look identical');
+
+ // test2
+ var iframe2 = document.getElementById('test2');
+ iframe2.focus();
+ var webnav = SpecialPowers.wrap(iframe2.contentWindow).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ var docShell = webnav.QueryInterface(Ci.nsIDocShell);
+ var test2Inner = iframe2.contentDocument.getElementById('test2Inner');
+ test2Inner.style.MozUserSelect = 'text';
+ docShell.doCommand("cmd_selectAll");
+ var withoutUserSelect = snapshotWindow(iframe2.contentWindow);
+
+ test2Inner.style.MozUserSelect = 'none';
+ docShell.doCommand("cmd_selectAll");
+ var withUserSelect = snapshotWindow(iframe2.contentWindow);
+ ok(compareSnapshots(withoutUserSelect, withUserSelect, true)[0], 'Editable fields should ignore user select style');
+
+ SimpleTest.finish();
+}
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+</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 000000000..46639d087
--- /dev/null
+++ b/dom/base/test/test_bug1118689.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1118689
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1118689</title>
+ <script type="application/javascript" 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 test1() {
+ // test 1, check that error handling in data documents is still the same
+ // as it has been for ages.
+ var d = document.implementation.createHTMLDocument();
+ d.body.innerHTML = "<img onerror='ok(false, \"EventHandler shouldn't be called in data document\")'>";
+ d.body.firstChild.addEventListener("error",
+ function() {
+ ok(true, "EventListener should be called in data document");
+ test2();
+ });
+ d.body.firstChild.addEventListener("load",
+ function() {
+ ok(false, "Images in data document shouldn't be loaded");
+ });
+ d.body.firstChild.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP";
+ }
+
+ function test2() {
+ // test 2, 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="test1();">
+<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_bug1126851.html b/dom/base/test/test_bug1126851.html
new file mode 100644
index 000000000..d6d7bf4a2
--- /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 type="application/javascript" 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 000000000..0d37242f8
--- /dev/null
+++ b/dom/base/test/test_bug116083.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=116083
+-->
+<head>
+ <title>Test for Bug 116083</title>
+ <script type="application/javascript" 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=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 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/unicode"], 1, cb.kGlobalClipboard),
+ "The clipboard has text/unicode");
+
+ // Android only supports plain text
+ if (navigator.appVersion.indexOf("Android") == -1) {
+ ok(cb.hasDataMatchingFlavors(["text/html"], 1, cb.kGlobalClipboard),
+ "The clipboard has text/html");
+ }
+
+ if (navigator.appVersion.indexOf("Win") >= 0) {
+ ok(cb.hasDataMatchingFlavors(["application/x-moz-nativehtml"], 1, cb.kGlobalClipboard),
+ "The clipboard has application/x-moz-nativehtml");
+ }
+}
+
+function nextTest() {
+ var div = document.querySelector("#content>div");
+ if (!div) {
+ SimpleTest.finish();
+ return;
+ }
+ getSelection().selectAllChildren(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.parentNode.removeChild(div);
+ nextTest();
+ }, function() {
+ ok(false, "failed to copy the expected content to the clipboard");
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set": [["clipboard.plainTextOnly", false]]}, 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 000000000..69d1c8c33
--- /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 type="application/javascript" 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;version=1.7">
+
+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. Be sure to yield so we don't call this twice.
+ yield 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 000000000..fe932d4ea
--- /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 type="application/javascript" 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;version=1.7">
+
+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. Be sure to yield so we don't call this twice.
+ yield 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 000000000..37942f005
--- /dev/null
+++ b/dom/base/test/test_bug1187157.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=789315
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 789315</title>
+ <script type="application/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=789315">Mozilla Bug 789315</a>
+<form id="a"><input name="b" type="file"/></form>
+
+<script type="text/javascript">
+ var obj = new FormData(document.getElementById('a')).get('b');
+ is(obj, "", "We want an empty string.");
+
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug1198095.html b/dom/base/test/test_bug1198095.html
new file mode 100644
index 000000000..cebf56888
--- /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 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=1198095">Mozilla Bug 1198095</a>
+
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+
+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");
+ ok(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_bug1238440.html b/dom/base/test/test_bug1238440.html
new file mode 100644
index 000000000..27cd2c0cd
--- /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 type="application/javascript" 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 000000000..158436044
--- /dev/null
+++ b/dom/base/test/test_bug1250148.html
@@ -0,0 +1,52 @@
+<!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 type="application/javascript" 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);
+is(fd.get("test"), "", "We want an empty string.");
+
+var getAll = fd.getAll("test");
+ok(Array.isArray(getAll), "We want an array with 1 empty string.");
+is(getAll.length, 1, "We want an array with 1 empty string.");
+is(getAll[0], "", "We want an array with 1 empty string.");
+
+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 000000000..40a272f90
--- /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_bug1263696.html b/dom/base/test/test_bug1263696.html
new file mode 100644
index 000000000..289a26ba4
--- /dev/null
+++ b/dom/base/test/test_bug1263696.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta><charset="utf-8"/>
+ <title>Test Embed/Object Node Conflicts</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="plugin-utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var index = 0;
+ function startTest() {
+ is(index, 12, "Should have loaded all passing frames.");
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="startTest()">
+ <object data="file_bug1263696_frame_pass.html" style="width: 100px; height: 100px">
+ <embed type="text/html" src="file_bug1263696_frame_fail.html" />
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <embed type="text/html" src="file_bug1263696_frame_pass.html" />
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <div></div>
+ <embed type="text/html" src="file_bug1263696_frame_pass.html" />
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <div>
+ <embed type="text/html" src="file_bug1263696_frame_pass.html" />
+ </div>
+ </object>
+ <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test">
+ <embed type="text/html" src="file_bug1263696_frame_pass.html" />
+ <embed type="text/html" src="file_bug1263696_frame_pass.html" />
+ <object data="file_bug1263696_frame_pass.html">
+ <embed type="text/html" src="file_bug1263696_frame_fail.html" />
+ </object>
+ <object data="data:application/x-does-not-exist,test">
+ <embed type="text/html" src="file_bug1263696_frame_pass.html" />
+ </object>
+ </object>
+ <div>
+ <object data="file_bug1263696_frame_pass.html" style="width: 100px; height: 100px"></object>
+ <embed type="text/html" src="file_bug1263696_frame_pass.html" />
+ </div>
+ <div>
+ <embed type="text/html" src="file_bug1263696_frame_pass.html" />
+ <object data="file_bug1263696_frame_pass.html" style="width: 100px; height: 100px"></object>
+ </div>
+ </body>
+</html>
diff --git a/dom/base/test/test_bug1268962.html b/dom/base/test/test_bug1268962.html
new file mode 100644
index 000000000..c346d4374
--- /dev/null
+++ b/dom/base/test/test_bug1268962.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1268962
+-->
+<head>
+ <title>Test for Bug 1268962</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=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 cancelPrefetch
+ // won't cause crash.
+ prefetch.cancelPrefetchURI(ios.newURI(url, null, null), 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();
+
+new Promise(resolve =>
+ SpecialPowers.pushPrefEnv({"set": [["network.prefetch-next.aggressive", true]]}, resolve))
+
+// 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 000000000..e49fbf628
--- /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 type="application/javascript" 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 000000000..013a03864
--- /dev/null
+++ b/dom/base/test/test_bug1281963.html
@@ -0,0 +1,68 @@
+<!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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.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;version=1.7">
+
+// __setPref(key, value)__.
+// Set a pref value asynchronously, returning a promise that resolves
+// when it succeeds.
+let setPref = function* (key, value) {
+ return new Promise(function(resolve, reject) {
+ SpecialPowers.pushPrefEnv({"set": [[key, value]]}, resolve);
+ });
+};
+
+// Run a test to see that we don't expose the supported mimeTypes
+// or installed plugins when "privacy.resistFingerprinting" is active.
+add_task(function* () {
+ let exampleMimeType = undefined,
+ examplePlugin = undefined;
+ // Disable fingerprinting resistance.
+ yield setPref("privacy.resistFingerprinting", false);
+ // Depending on the testing platform, we may have at least
+ // one mimeType and plugin available.
+ exampleMimeType = navigator.mimeTypes[0];
+ examplePlugin = navigator.plugins[0];
+
+ // First check that we can retrieve mimeType or plugin by name and that
+ // the array length is nonzero.
+ if (exampleMimeType) {
+ isnot(navigator.mimeTypes[exampleMimeType.type], undefined, "Should reveal mime type");
+ isnot(navigator.mimeTypes.length, 0, "navigator.mimeTypes.length should be nonzero");
+ }
+ if (examplePlugin) {
+ isnot(navigator.plugins[examplePlugin.name], undefined, "Should reveal plugin");
+ isnot(navigator.plugins.length, 0, "navigator.plugins.length should be nonzero");
+ }
+
+ // Now test with fingerprinting resistance enabled
+ yield setPref("privacy.resistFingerprinting", true);
+ if (exampleMimeType) {
+ is(navigator.mimeTypes[exampleMimeType.type], undefined, "Don't reveal mime type");
+ }
+ is(navigator.mimeTypes[0], undefined, "Don't reveal mime type");
+ is(navigator.mimeTypes.length, 0, "navigator.mimeTypes.length should be 0");
+ if (examplePlugin) {
+ is(navigator.plugins[examplePlugin.name], undefined, "Don't reveal plugin");
+ }
+ is(navigator.plugins[0], undefined, "Don't reveal plugin");
+ is(navigator.plugins.length, 0, "navigator.plugins.length should be 0");
+});
+
+</script>
+
+</body>
+</html>
diff --git a/dom/base/test/test_bug1295852.html b/dom/base/test/test_bug1295852.html
new file mode 100644
index 000000000..ca73549c7
--- /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.core.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 000000000..54ba8ac99
--- /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 type="application/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=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 000000000..2775ff1b7
--- /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 000000000..ba12b6101
--- /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 type="application/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=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_bug166235.html b/dom/base/test/test_bug166235.html
new file mode 100644
index 000000000..8ef7dc4e3
--- /dev/null
+++ b/dom/base/test/test_bug166235.html
@@ -0,0 +1,160 @@
+<!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 type="application/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=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="-moz-user-select: none;"> NOT</span> be copied.</p>
+<p id="test2">This<span style="-moz-user-select: none;"><span style="-moz-user-select: text"> text should</span> NOT</span> be copied.</p>
+<p id="test3">This text should<span style="-moz-user-select: -moz-none;"> NOT</span> be copied.</p>
+<p id="test4">This<span style="-moz-user-select: -moz-none;"><span style="-moz-user-select: text"> text should</span> NOT</span> be copied.</p>
+<p id="test5">This<span style="-moz-user-select: all"> text<span style="-moz-user-select: none"> should</span></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 webnav = SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+
+ var docShell = webnav.QueryInterface(SpecialPowers.Ci.nsIDocShell);
+
+ 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).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+ }
+
+ function copyChildrenToClipboard(id) {
+ textarea.blur();
+ clipboard.emptyClipboard(1);
+ window.getSelection().selectAllChildren(document.getElementById(id));
+ documentViewer.copySelection();
+
+ is(clipboard.hasDataMatchingFlavors(["text/unicode"], 1,1), true);
+ is(clipboard.hasDataMatchingFlavors(["text/html"], 1,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.indexOf("Win") >= 0) {
+ expectedValue = "<html><body>\n<!--StartFragment-->" + expected + "<!--EndFragment-->\n</body>\n</html>";
+ }
+ 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=\"-moz-user-select: text\"> text should</span> be copied.</p>',
+ '<p id=\"test3\">This text should be copied.</p>',
+ '<p id=\"test4\">This<span style=\"-moz-user-select: text\"> text should</span> be copied.</p>',
+ '<p id=\"test5\">This<span style=\"-moz-user-select: all\"> text<span style=\"-moz-user-select: none\"> should</span></span> be copied.</p>',
+];
+
+// expected results for clipboard text/unicode
+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=\"-moz-user-select: none;\"> NOT</span> be copied.',
+ 'This<span style=\"-moz-user-select: none;\"><span style=\"-moz-user-select: text\"> text should</span> NOT</span> be copied.',
+ 'This text should<span style=\"-moz-user-select: -moz-none;\"> NOT</span> be copied.',
+ 'This<span style=\"-moz-user-select: -moz-none;\"><span style=\"-moz-user-select: text\"> text should</span> NOT</span> be copied.',
+ 'This<span style=\"-moz-user-select: all\"> text<span style=\"-moz-user-select: none\"> should</span></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/unicode", clipboardUnicode[i], id);
+ testInnerHTML(id, innerHTMLStrings[i]);
+ testPasteText(textareaStrings[i], id + '.innerHTML');
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug199959.html b/dom/base/test/test_bug199959.html
new file mode 100644
index 000000000..ed5766ef3
--- /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 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=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 000000000..a8247aeb9
--- /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 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=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, 2, 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 > 0) {
+ 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 000000000..88cda1d78
--- /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 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=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 000000000..f4dfe004a
--- /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 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=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 000000000..9cc6e53db
--- /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 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=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 000000000..3a6fc0f2b
--- /dev/null
+++ b/dom/base/test/test_bug270145.xhtml
@@ -0,0 +1,53 @@
+<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 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=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 () {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ var encoder = SpecialPowers.Cc["@mozilla.org/layout/htmlCopyEncoder;1"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+ 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> \ No newline at end of file
diff --git a/dom/base/test/test_bug276037-1.html b/dom/base/test/test_bug276037-1.html
new file mode 100644
index 000000000..4995479f4
--- /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 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=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 000000000..432d57e71
--- /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 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=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 000000000..054785abe
--- /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 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=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 000000000..61c14cbd8
--- /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 type="text/javascript" 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);
+}, false);
+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 000000000..7ea4eff20
--- /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 type="text/javascript" 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);
+}, false);
+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 000000000..377c9e19b
--- /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 type="application/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=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 000000000..ae426b31c
--- /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 type="application/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=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');
+}, false);
+window.addEventListener('readystatechange', function(event) {
+ ok(false, 'window listener', 'readystatechange event should not bubble to window');
+}, false);
+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 000000000..23efcb468
--- /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 000000000..c4d59b197
--- /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 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=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 000000000..c40485d01
--- /dev/null
+++ b/dom/base/test/test_bug320799.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=320799
+-->
+<head>
+ <title>Test for Bug 320799</title>
+ <script type="application/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=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>
+</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");
+</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 000000000..b5581a6e7
--- /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 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=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 000000000..5e8f67ec7
--- /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 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=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_bug330925.xhtml b/dom/base/test/test_bug330925.xhtml
new file mode 100644
index 000000000..a26ab94be
--- /dev/null
+++ b/dom/base/test/test_bug330925.xhtml
@@ -0,0 +1,74 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=330925
+-->
+<head>
+ <title>Test for Bug 330925</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<style type="text/css">
+#t {
+-moz-binding: url(#randomxbl);
+}
+</style>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="randomxbl" inheritstyle="false">
+<content>
+xbl textnode1
+<div>
+xbl textnode2
+<children xmlns="http://www.mozilla.org/xbl"/>
+</div>
+</content>
+</binding>
+</bindings>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=330925">Mozilla Bug 330925</a>
+
+<p id="display">
+<div id="t" />
+</p>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 330925 **/
+
+// We have to wait until onload because XBL doesn't bind immediately.
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(init);
+
+function init()
+{
+ var t = document.getElementById("t");
+ var d = SpecialPowers.wrap(document);
+
+ is(SpecialPowers.unwrap(d.getBindingParent(d.getAnonymousNodes(t)[0])),
+ t,
+ "Wrong binding parent for anonymous node");
+
+ is(SpecialPowers.unwrap(d.getBindingParent(d.getAnonymousNodes(t)[1].childNodes[0])),
+ t,
+ "Wrong binding parent for child of anonymous node");
+
+ is(d.getBindingParent(t),
+ null,
+ "Non-anonymous node should not have a binding parent");
+
+ is(d.getBindingParent(document.documentElement),
+ null,
+ "Document element should not have a binding parent");
+
+ 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 000000000..5ef20400f
--- /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 type="application/javascript" 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=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 = "data:text/plain,FAIL";
+const PASSURL = "data:text/plain,PASS";
+
+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.textContent, "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).parentNode.removeChild($(id));
+ 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.textContent, "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).parentNode.removeChild($(id));
+ 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.textContent, "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).parentNode.removeChild($(id));
+ 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 000000000..6e99fed88
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/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=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 000000000..452284080
--- /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 type="application/javascript" 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 000000000..fbc3700d0
--- /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 type="application/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=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 000000000..4f9beeec9
--- /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 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=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 000000000..424c02aa1
--- /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 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=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 000000000..4e50e4e33
--- /dev/null
+++ b/dom/base/test/test_bug338583.html
@@ -0,0 +1,666 @@
+<!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 type="text/javascript" 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 (var 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 (var 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.indexOf("Android") != -1 || 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.dafsgbhjab.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 000000000..ac34bfc01
--- /dev/null
+++ b/dom/base/test/test_bug338679.html
@@ -0,0 +1,78 @@
+<!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 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=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();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+testDiv.addEventListener("DOMAttrModified", attr_modified, false);
+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, null).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 000000000..b44bd7562
--- /dev/null
+++ b/dom/base/test/test_bug339494.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=339494
+-->
+<head>
+ <title>Test for Bug 339494</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=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, 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>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug339494.xhtml b/dom/base/test/test_bug339494.xhtml
new file mode 100644
index 000000000..6a028f155
--- /dev/null
+++ b/dom/base/test/test_bug339494.xhtml
@@ -0,0 +1,60 @@
+<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 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=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, 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>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug340571.html b/dom/base/test/test_bug340571.html
new file mode 100644
index 000000000..c0289e9a9
--- /dev/null
+++ b/dom/base/test/test_bug340571.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=340571
+-->
+<head>
+ <title>Test for Bug 340571</title>
+ <script type="application/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=340571">Mozilla Bug 340571</a>
+<p id="display"></p>
+<div><select id="sel"></select></div>
+<script type="application/javascript">
+ok(!document.getBoxObjectFor, "Detecting getBoxObjectFor");
+try {
+ document.getBoxObjectFor(document.body);
+ ok(false, "getBoxObjectFor succeeded");
+} catch (e) {
+ ok(true, "getBoxObjectFor succeeded");
+}
+
+var sel = document.getElementById("sel");
+ok(!sel.boxObject, "Getting boxObject");
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_bug343596.html b/dom/base/test/test_bug343596.html
new file mode 100644
index 000000000..567bef491
--- /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 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=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 000000000..095696762
--- /dev/null
+++ b/dom/base/test/test_bug345339.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=345339
+-->
+<head>
+ <title>Test for Bug 345339</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=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;version=1.7">
+/** 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";
+
+ 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");
+
+ // 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 000000000..8db0fc67e
--- /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 type="application/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=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 000000000..faf2f681a
--- /dev/null
+++ b/dom/base/test/test_bug352728.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=352728
+-->
+<head>
+ <title>Test for Bug 352728</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=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 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 = [ "nsIDOMComment", "nsIDOMCharacterData", "nsIDOMNode",
+ "nsIDOMEventTarget" ];
+ checkInterfaces(comment, "comment", interfaces);
+
+ 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);
+
+ var interfaces = [ "nsIDOMProcessingInstruction", "nsIDOMNode",
+ "nsIDOMEventTarget" ];
+ 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);
+
+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 000000000..6f85810d6
--- /dev/null
+++ b/dom/base/test/test_bug352728.xhtml
@@ -0,0 +1,187 @@
+<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 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=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 = [ "nsIDOMComment", "nsIDOMCharacterData", "nsIDOMNode",
+ "nsIDOMEventTarget" ];
+ checkInterfaces(comment, "comment", interfaces);
+
+ 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);
+ var types = [ CDATASection, CharacterData, Node ];
+ checkTypes(cdataSection, "CDATA section", types);
+
+ var interfaces = [ "nsIDOMCDATASection", "nsIDOMCharacterData",
+ "nsIDOMNode", "nsIDOMEventTarget" ];
+ 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(0, "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 = [ "nsIDOMProcessingInstruction", "nsIDOMNode",
+ "nsIDOMEventTarget" ];
+ 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(0, "Invalid processing instruction creation", aReason);
+ }
+ } catch (e) {
+ if (aShouldSucceed) {
+ ok(0, "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 000000000..0f0acd423
--- /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 type="text/javascript" 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" src="data:text/html,<body>test</body>"></iframe>
+<object id="four" data="data:text/html,<body>test</body>"></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 000000000..70237dc63
--- /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 type="text/javascript" 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 000000000..9b2f56fc7
--- /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 type="text/javascript" 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 000000000..872ea4798
--- /dev/null
+++ b/dom/base/test/test_bug357450.xhtml
@@ -0,0 +1,40 @@
+<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 type="text/javascript"
+ 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 000000000..87d25bd83
--- /dev/null
+++ b/dom/base/test/test_bug357450_svg.xhtml
@@ -0,0 +1,47 @@
+<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 type="text/javascript"
+ 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 000000000..f18f5143b
--- /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 type="text/javascript" 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, "nsIDOMRange.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 000000000..bad975936
--- /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 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=358660">Mozilla Bug 358660</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="testframe"
+ src="data:text/html,<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 000000000..f40d18761
--- /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 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=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 000000000..72276443b
--- /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 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=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 000000000..14c0af0f1
--- /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 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=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 000000000..8e4acd9e3
--- /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 000000000..bd35238a4
--- /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 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=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 000000000..a499a833d
--- /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 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=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; }, false);
+span.addEventListener("DOMNodeRemoved", function() { rem1 = true; }, true);
+span.addEventListener("DOMNodeRemoved", function() { rem2 = true; }, false);
+
+$("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 000000000..74c6c055e
--- /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 type="text/javascript" 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, null);
+ 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 000000000..2fdf3fcb4
--- /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 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=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 000000000..8bf6c1261
--- /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 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=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 000000000..878676f85
--- /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 type="text/javascript" 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 000000000..1d3ca956c
--- /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 type="text/javascript" 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 000000000..75bf1a541
--- /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 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=372086">Mozilla Bug 372086</a>
+<p id="display">
+ <!-- Binding is:
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="test">
+ <content>ghi<children/>jkl</content>
+ </binding>
+ </bindings>
+ -->
+ <div id="d" style="-moz-binding: url(data:application/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22test%22%3E%3Ccontent%3Eghi%3Cchildren%2F%3Ejkl%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E)">abc</div>def
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 372086 **/
+
+// Note that we could avoid a lot of SpecialPowers.unwrap calls if the SpecialPowers
+// wrappers could be made to preserve identity. See bug 718543.
+
+function runTest() {
+ var range = SpecialPowers.wrap(document.createRange());
+
+ var c = $("d").firstChild;
+ var a = $("d").nextSibling;
+
+ range.setStart(c, 1);
+ range.setEnd(c, 3);
+ is(SpecialPowers.unwrap(range.startContainer), c, "Unexpected start container");
+ is(range.startOffset, 1, "Unexpected start offset");
+ is(SpecialPowers.unwrap(range.endContainer), c, "Unexpected end container");
+ is(range.endOffset, 3, "Unexpected end offset");
+ is(range.toString(), "bc", "Unexpected range serialization");
+
+ var anon = SpecialPowers.wrap(document).getAnonymousNodes($("d"))[0];
+ // Should collapse the range, because can't determine order
+ range.setEnd(anon, 2);
+ is(SpecialPowers.unwrap(range.startContainer), SpecialPowers.unwrap(anon),
+ "Unexpected collapsed start container");
+ is(range.startOffset, 2, "Unexpected collapsed start offset");
+ is(SpecialPowers.unwrap(range.endContainer), SpecialPowers.unwrap(anon),
+ "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(SpecialPowers.unwrap(range.startContainer), a, "Unexpected start container after");
+ is(range.startOffset, 0, "Unexpected start offset after");
+ is(SpecialPowers.unwrap(range.endContainer), a, "Unexpected end container after");
+ is(range.endOffset, 2, "Unexpected end offset after");
+ is(range.toString(), "de", "Unexpected range serialization after");
+
+ anon = SpecialPowers.wrap(document).getAnonymousNodes($("d"))[2];
+ // Collapses because one endpoint is anonymous from point of view of
+ // the other.
+ range.setStart(anon, 1);
+ is(SpecialPowers.unwrap(range.startContainer),
+ SpecialPowers.unwrap(anon), "Unexpected newly collapsed start container");
+ is(range.startOffset, 1, "Unexpected newly collapsed start offset");
+ is(SpecialPowers.unwrap(range.endContainer), SpecialPowers.unwrap(anon),
+ "Unexpected newly collapsed end container");
+ is(range.endOffset, 1, "Unexpected newly collapsed end offset");
+ is(range.toString(), "", "Unexpected collapsed range serialization");
+
+ range.setEnd(anon, 3);
+ is(SpecialPowers.unwrap(range.startContainer),
+ SpecialPowers.unwrap(anon), "Unexpected anon start container");
+ is(range.startOffset, 1, "Unexpected anon start offset");
+ is(SpecialPowers.unwrap(range.endContainer),
+ SpecialPowers.unwrap(anon), "Unexpected anon end container");
+ is(range.endOffset, 3, "Unexpected anon end offset");
+ is(range.toString(), "kl", "Unexpected anon 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 000000000..cbf941b59
--- /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 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=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, false);
+ 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 src="data:text/html,<script>function listener() { ++parent.eventCount; } </script>">
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug372964.html b/dom/base/test/test_bug372964.html
new file mode 100644
index 000000000..cc8ac7d2d
--- /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 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=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, false);
+ } 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, false);
+ 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 000000000..17af07822
--- /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 type="text/javascript" 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.html b/dom/base/test/test_bug375314.html
new file mode 100644
index 000000000..4858f8063
--- /dev/null
+++ b/dom/base/test/test_bug375314.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375314
+-->
+<head>
+ <title>Test for Bug 375314</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=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;
+
+// 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: function(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: function(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad: function(contentType, contentLocation, requestOrigin, context, mimeTypeGuess, extra) {
+
+ // 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: function(contentType, contentLocation, requestOrigin, context, mimeTypeGuess, extra) {
+
+ 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", "OBJECT", "DOCUMENT", "SUBDOCUMENT", "XBL", "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);
+
+ if (tests[curTest] == "XBL")
+ {
+ //XXX Removing binding to work-around a memory leak (bugs 478528, 499735).
+ div.style.MozBinding = "";
+ }
+ }
+
+ 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_document() {
+ top.location.href = testURL;
+}
+
+function request_subdocument() {
+ var content = $("content");
+
+ var frame = document.createElement("iframe");
+ frame.setAttribute("src", testURL);
+ content.appendChild(frame);
+}
+
+function request_xbl() {
+ var content = $("content");
+
+ div = document.createElement("div");
+ div.style.MozBinding = "url(" + testURL + ")";
+ $('test').appendChild(div);
+ div.offsetLeft; // Flush styles.
+}
+
+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 000000000..965df74ba
--- /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 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=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 000000000..f2166c2bd
--- /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 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=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 000000000..5f8d4969c
--- /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 000000000..3cf679152
--- /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 type="text/javascript" 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 000000000..c36a5f335
--- /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 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=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 000000000..d1ed91821
--- /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 type="application/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=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 000000000..9f0b1aa7f
--- /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 type="text/javascript" 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 000000000..025d780e7
--- /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 type="text/javascript" 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 000000000..4afde0075
--- /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 type="text/javascript" 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, false);
+
+ 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 000000000..13f9788f9
--- /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 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=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 000000000..08ed85292
--- /dev/null
+++ b/dom/base/test/test_bug393968.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=393968
+-->
+<head>
+ <title>Test for Bug 393968</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=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">
+
+/** 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");
+
+</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 000000000..aa1ee17d3
--- /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 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=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 000000000..654df12aa
--- /dev/null
+++ b/dom/base/test/test_bug397234.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=397234
+-->
+<head>
+ <title>Test for Bug 397234</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=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">
+
+/** 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");
+
+</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 000000000..3b5d7400c
--- /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 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=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 000000000..c390a80d0
--- /dev/null
+++ b/dom/base/test/test_bug401662.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=401662
+-->
+<head>
+ <title>Test for Bug 401662</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=401662">Mozilla Bug 401662</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="testframe"
+ src="data:text/html,<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 Cc = SpecialPowers.Cc;
+ const Ci = SpecialPowers.Ci;
+
+ var encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/html"]
+ .createInstance(Ci.nsIDocumentEncoder);
+
+ 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?");
+
+ 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 000000000..5c24dd6de
--- /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 type="text/javascript" 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 000000000..453e7e1f3
--- /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 000000000..390aac039
--- /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 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=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 000000000..30192cb1b
--- /dev/null
+++ b/dom/base/test/test_bug403852.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=403852
+-->
+ <title>Test for Bug 403852</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=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 url = SimpleTest.getTestFileURL("bug403852_fileOpener.js");
+var 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("lastModifiedDate" in domFile, "lastModifiedDate must be present");
+
+ var d = new Date(message.mtime);
+ is(d.getTime(), domFile.lastModifiedDate.getTime(), "lastModifiedDate should be the same");
+
+ var x = new Date();
+
+ // In our implementation of File object, lastModifiedDate is unknown only for new objects.
+ // Using canvas or input[type=file] elements, we 'often' have a valid lastModifiedDate values.
+ // For canvas we use memory files and the lastModifiedDate is now().
+ var f = new File([new Blob(['test'], {type: 'text/plain'})], "test-name");
+
+ var y = f.lastModifiedDate;
+ var z = new Date();
+
+ ok((x.getTime() <= y.getTime()) && (y.getTime() <= z.getTime()), "lastModifiedDate of file which does not have last modified date should be current time");
+
+
+ var d = new Date(message.fileDate);
+ is(d.getTime(), message.fileWithDate.lastModifiedDate.getTime(), "lastModifiedDate should be the same when lastModified is set: " + message.fileWithDate.lastModified);
+
+ 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 000000000..cb2ababff
--- /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 000000000..70ca5ffed
--- /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 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=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, false);
+
+ 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 000000000..695de5f91
--- /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 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=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 **/
+
+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.parentNode.removeChild(zz);
+ 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 000000000..cf4498355
--- /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 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=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 000000000..35b469d40
--- /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 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=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 000000000..09a48b9f0
--- /dev/null
+++ b/dom/base/test/test_bug414190.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=414190
+-->
+<head>
+ <title>Test for Bug 414190</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=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)");
+}
+
+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");
+
+caption.style.captionSide = "left";
+
+tableRects = table.getClientRects();
+tableBoundingRect = table.getBoundingClientRect();
+cellBoundingRect = cell.getBoundingClientRect();
+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 plus border");
+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.right + 10, cellBoundingRect.left, "Discontiguous horizontal geometry");
+is(tableBoundingRect.top, cellBoundingRect.top - 10, "Table top error");
+is(tableBoundingRect.left, captionBoundingRect.left, "Table left error");
+is(tableBoundingRect.bottom, cellBoundingRect.bottom + 10, "Table bottom error");
+is(tableBoundingRect.right, cellBoundingRect.right + 10, "Table right error");
+
+</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 000000000..336bf654f
--- /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 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=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 000000000..84bdc1786
--- /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 type="text/javascript" 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 000000000..b0ed8e337
--- /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 type="text/javascript" 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 000000000..741fbf878
--- /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 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=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.removeChild(testParent.firstChild);
+ 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 000000000..36be15b43
--- /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 type="text/javascript" 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 000000000..866bb88cc
--- /dev/null
+++ b/dom/base/test/test_bug417384.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=417384
+-->
+<head>
+ <title>Test for Bug 417384</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=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.Cc["@mozilla.org/layout/documentEncoder;1?type=text/html"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+ 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 000000000..cc0e798ff
--- /dev/null
+++ b/dom/base/test/test_bug418214.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418214
+-->
+<head>
+ <title>Test for Bug 418214</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=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";
+is (m2.style, undefined, ".style shouldn't exist on MathML");
+
+is(d2.getAttribute("style"), "color: green;",
+ "Adjusting .style should parse style on HTML");
+is(s2.getAttribute("style"), "color: green;",
+ "Getting .style should parse style on SVG");
+
+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";
+is (m.style, undefined, ".style shouldn't exist on MathML");
+
+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");
+
+</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 000000000..3ffa19fa9
--- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript;version=1.7" 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 000000000..f38c5e08f
--- /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 type="application/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=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);
+ }
+ }, false);
+ 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 000000000..c468af0ae
--- /dev/null
+++ b/dom/base/test/test_bug419527.xhtml
@@ -0,0 +1,74 @@
+<?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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <binding id="rangebinding">
+ <content><html:span>Foo</html:span>
+ </content>
+ <implementation>
+ <constructor>
+ var win = XPCNativeWrapper.unwrap(window);
+ var span = document.getAnonymousNodes(this)[0];
+ 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 = XPCNativeWrapper(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!");
+ //XXX This should just call SimpleTest.finish(), bugs 478528, 499735.
+ setTimeout(win.finish, 0);
+ </constructor>
+ </implementation>
+ </binding>
+ </bindings>
+</head>
+<body>
+<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 **/
+
+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", "div");
+ d.style.MozBinding = "url('" + window.location + "#rangebinding" + "')";
+ document.body.appendChild(d);
+}
+
+function finish() {
+ SimpleTest.finish();
+}
+
+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 000000000..aaa38e439
--- /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 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=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 000000000..c52391bb2
--- /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 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=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 000000000..eda74d02f
--- /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 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=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 000000000..fa6cbbd84
--- /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 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=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;1']
+ .createInstance(SpecialPowers.Ci.nsIURI);
+ baseUri.spec = window.location.href;
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel2(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.open2(), 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.Cc["@mozilla.org/layout/documentEncoder;1?type=application/xhtml+xml"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+
+ 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 000000000..755cd7253
--- /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 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=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;1']
+ .createInstance(SpecialPowers.Ci.nsIURI);
+ baseUri.spec = window.location.href;
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel2(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.open2(), 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.Cc["@mozilla.org/layout/documentEncoder;1?type=application/xhtml+xml"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+
+ 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 000000000..351fcfa24
--- /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 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=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 000000000..ae8c2236c
--- /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 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=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 000000000..1ee8164f6
--- /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 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=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;1']
+ .createInstance(SpecialPowers.Ci.nsIURI);
+ baseUri.spec = window.location.href;
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel2(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.open2(), 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.Cc["@mozilla.org/layout/documentEncoder;1?type=text/html"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+
+ 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 000000000..5c6232718
--- /dev/null
+++ b/dom/base/test/test_bug424359-2.html
@@ -0,0 +1,320 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test HTML serializer with entities</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=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;1']
+ .createInstance(SpecialPowers.Ci.nsIURI);
+ baseUri.spec = window.location.href;
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel2(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.open2(), 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.Cc["@mozilla.org/layout/documentEncoder;1?type=text/html"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+
+ 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, "text/html", de.OutputLFLineBreak | de.OutputEncodeW3CEntities);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_2_entw3c.html");
+ isRoughly(out, expected, "test OutputEncodeW3CEntities");
+
+ //------------ 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");
+
+ //------------ OutputEncodeLatin1Entities
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputEncodeLatin1Entities);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_2_latin1.html");
+ isRoughly(out, expected, "test OutputEncodeLatin1Entities");
+
+ //------------ OutputEncodeHTMLEntities
+ encoder.init(doc, "text/html", de.OutputLFLineBreak | de.OutputEncodeHTMLEntities);
+ out = encoder.encodeToString();
+ expected = loadFileContent("file_htmlserializer_2_enthtml.html");
+ isRoughly(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, "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 000000000..29367adc1
--- /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 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=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 000000000..63aea6cd1
--- /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 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=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 000000000..e96e69e62
--- /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 type="text/javascript" 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_bug429157.html b/dom/base/test/test_bug429157.html
new file mode 100644
index 000000000..1c01eb880
--- /dev/null
+++ b/dom/base/test/test_bug429157.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=429157
+-->
+<head>
+ <title>Test for Bug 429157</title>
+ <script type="text/javascript" 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=429157">Mozilla Bug 429157</a>
+<br>
+
+<script>
+var missingPlugins = new Array();
+const OBJLC = SpecialPowers.Ci.nsIObjectLoadingContent;
+
+function pluginBindingAttached(event)
+{
+ var plugin = event.target;
+ plugin instanceof OBJLC;
+ if (SpecialPowers.wrap(plugin).pluginFallbackType == OBJLC.PLUGIN_UNSUPPORTED)
+ missingPlugins.push(plugin);
+}
+
+document.addEventListener("PluginBindingAttached", pluginBindingAttached, true);
+</script>
+
+<object id="obj1" type="image/png" >ALT image/png</object><br>
+<object id="obj2" type="image/svg+xml">ALT image/svg+xml</object><br>
+<object id="obj3" type="text/html" >ALT text/html</object><br>
+<object id="obj4" type="text/plain" >ALT text/plain</object><br>
+
+<script class="testbody" type="text/javascript">
+function runtests()
+{
+ for (var obj of document.querySelectorAll("object")) {
+ obj.clientTop;
+ }
+
+ SimpleTest.executeSoon(function () {
+ is(missingPlugins.length, 0, "There should be no missing plugins for this page");
+
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug431082.html b/dom/base/test/test_bug431082.html
new file mode 100644
index 000000000..081226bbb
--- /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 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=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.parentNode.removeChild(window.frameElement);}, true);
+document.documentElement.appendChild(document.getElementsByTagName('box')[0]);
+}
+setTimeout(doe,0);
+</script>
+</window>
+-->
+<iframe src="data:application/vnd.mozilla.xul+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 000000000..19947c147
--- /dev/null
+++ b/dom/base/test/test_bug431701.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=431701
+-->
+<head>
+ <meta charset="windows-1252">
+ <title>Test for Bug 431701</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=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 = [
+ "data:text/html,<html></html>",
+ "data:text/html;charset=UTF-8,<html></html>",
+ "data:text/html;charset=ISO-8859-1,<html></html>",
+ "data:text/xml,<html></html>",
+ "data:text/xml,<?xml version='1.0'?><html></html>",
+ "data:text/xml,<?xml version='1.0' encoding='UTF-8'?><html></html>",
+ "data:text/xml,<?xml version='1.0' encoding='ISO-8859-1'?><html></html>",
+];
+
+for (var 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 (var 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 000000000..24c2367b6
--- /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 type="text/javascript" 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" src="data:text/html,1"></iframe>
+ <iframe id="f2" src="data:text/html,2"></iframe>
+ <iframe id="f3" src="data:text/html,<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 000000000..8eb13c73a
--- /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 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=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 000000000..ec30775a2
--- /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 000000000..828931d67
--- /dev/null
+++ b/dom/base/test/test_bug435425.html
@@ -0,0 +1,432 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435425
+-->
+<head>
+ <title>Test for Bug 435425</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=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 xhr = null;
+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 ? true : false) + ".");
+ }
+ 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) {
+ 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_bug444030.xhtml b/dom/base/test/test_bug444030.xhtml
new file mode 100644
index 000000000..6ba2c6ec1
--- /dev/null
+++ b/dom/base/test/test_bug444030.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=444030
+-->
+<head>
+ <title>Test for Bug 444030</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="e">
+ <content>e</content>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=444030">Mozilla Bug 444030</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 444030 **/
+
+function doTest() {
+ var anonTextNode = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById("boundElement")))[0];
+ var hadException = false;
+ try {
+ var wholeText = anonTextNode.wholeText;
+ } catch(e) {
+ hadException = true;
+ }
+ ok(hadException,
+ "Should have got an exception when using .wholeText with a text node child of the binding parent");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(doTest);
+
+]]>
+</script>
+</pre>
+<div style="-moz-binding: url(#e)" id="boundElement"></div>
+</body>
+</html>
+
diff --git a/dom/base/test/test_bug444322.html b/dom/base/test/test_bug444322.html
new file mode 100644
index 000000000..95866f048
--- /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 type="text/javascript" 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 000000000..3209f9054
--- /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 type="application/javascript" 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 loaded = (evt.loaded / evt.total);
+ if (loaded < 1) {
+ evt.target.log.style.width = (loaded * 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 000000000..ff601b9f7
--- /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 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=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 000000000..96364ea5b
--- /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 type="application/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=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 000000000..551da939a
--- /dev/null
+++ b/dom/base/test/test_bug450160.html
@@ -0,0 +1,142 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=450160
+-->
+<head>
+ <title>Test for Bug 450160</title>
+ <script type="application/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=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 testHTMLDocuments(ids, isXHTML) {
+ for (var i = 0; i < ids.length; ++i) {
+ var docType1 =
+ document.implementation.createDocumentType(isXHTML ? "html" : "HTML",
+ ids[i],
+ 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!");
+ is(doc1.body, null, "Shouldn't have .body!");
+ ok(doc1 instanceof HTMLDocument,
+ "Document should be an HTML document!");
+
+ var docType2 =
+ document.implementation.createDocumentType(isXHTML ? "html" : "HTML",
+ ids[i],
+ null);
+ var doc2 = document.implementation.createDocument(
+ "http://www.w3.org/1999/xhtml", "html", docType2);
+ is(docType2.ownerDocument, doc2, "docType should have ownerDocument!");
+ ok(doc2.documentElement, "Document should have document element!");
+ is(doc2.documentElement.localName, "html", "Wrong document element!");
+ is(doc2.body, null, "Shouldn't have .body!");
+ doc2.documentElement.appendChild(doc2.createElement("body"));
+ is(doc2.body, doc2.documentElement.firstChild, "Should have .body!");
+ if (isXHTML) {
+ doc2.body.appendChild(doc2.createElementNS("http://www.w3.org/1999/xhtml", "form"));
+ } else {
+ doc2.body.appendChild(doc2.createElement("form"));
+ }
+ is(doc2.forms.length, 1, "Form wasn't added .forms");
+ }
+}
+
+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!");
+}
+
+var htmlPublicIDs =
+ [ "-//W3C//DTD HTML 4.01//EN",
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
+ "-//W3C//DTD HTML 4.01 Frameset//EN",
+ "-//W3C//DTD HTML 4.0//EN",
+ "-//W3C//DTD HTML 4.0 Transitional//EN",
+ "-//W3C//DTD HTML 4.0 Frameset//EN" ];
+
+var xhtmlPublicIDs =
+ [ "-//W3C//DTD XHTML 1.0 Strict//EN",
+ "-//W3C//DTD XHTML 1.0 Transitional//EN",
+ "-//W3C//DTD XHTML 1.0 Frameset//EN" ];
+
+testHTMLDocuments(htmlPublicIDs, false);
+testHTMLDocuments(xhtmlPublicIDs, true);
+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 000000000..85dc4e5c7
--- /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 type="text/javascript" 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 000000000..dc06d9718
--- /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 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=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 000000000..804e0afdb
--- /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 type="application/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=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 000000000..724cd4b54
--- /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 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=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 000000000..5541a10d0
--- /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 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=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 000000000..33fc24d87
--- /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 type="application/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=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 src="data:text/html,<script>parent.ran[0]=true</script>"></iframe>
+ <object type="text/html" data="data:text/html,<script>parent.ran[1]=true</script>"></object>
+ <embed type="image/svg+xml" src="data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20onload='parent.ran[2]=true'/>">
+ <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 000000000..eab16d67e
--- /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 type="application/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=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 000000000..ca050a9c8
--- /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 type="application/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=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 000000000..6cab45f4b
--- /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 type="application/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=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 000000000..283d75b4b
--- /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 type="application/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=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 000000000..b91ca9832
--- /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 type="application/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=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();
+}, false);
+
+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 000000000..630c572f8
--- /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 type="application/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=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 000000000..3869653bb
--- /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 type="application/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=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 000000000..b889e899f
--- /dev/null
+++ b/dom/base/test/test_bug466080.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 466080</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="onWindowLoad()">
+<iframe id="frame1"
+ src="https://requireclientcert.example.com/tests/dom/base/test/bug466080.sjs"
+ onload="document.iframeWasLoaded = true">
+
+ 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">
+
+document.iframeWasLoaded = false;
+
+var 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"},
+
+];
+
+function onWindowLoad() {
+ // First, check that resource was loaded into the iframe
+ // This check in fact depends on bug #444165... :)
+ ok(document.iframeWasLoaded, "Loading resource via src-attribute");
+
+
+ function runTest(test) {
+
+ 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 == 0) {
+ 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 000000000..0e4439238
--- /dev/null
+++ b/dom/base/test/test_bug466409.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=466409
+-->
+<head>
+ <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=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');
+
+
+
+SpecialPowers.pushPrefEnv({"set": [['intl.charset.detector', 'universal_charset_detector']]}, function() {
+ 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 000000000..ad80ea7d5
--- /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 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=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 000000000..a20cfa63d
--- /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 type="application/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=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 000000000..3dae9287f
--- /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 type="application/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=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 000000000..336c979e9
--- /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 type="application/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=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 000000000..a62749671
--- /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 type="application/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=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 000000000..a262d47d8
--- /dev/null
+++ b/dom/base/test/test_bug475156.html
@@ -0,0 +1,299 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475156
+-->
+<head>
+ <title>Test for Bug 475156</title>
+ <script type="text/javascript" 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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs?etag=a1");
+ },
+
+ loading: function(xhr)
+ {
+ },
+
+ done: function(xhr)
+ {
+ },
+ },
+
+ // Try to load the file the first time regularly, we have to get 200 OK
+ {
+ init: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), false, "Not coming from the cache");
+ },
+
+ done: function(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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a1");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done: function(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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a1");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done: function(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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs?etag=a2");
+ },
+
+ loading: function(xhr)
+ {
+ },
+
+ done: function(xhr)
+ {
+ },
+ },
+
+ // Try to load the file, we have to get 200 OK with the new content
+ {
+ init: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a2");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), false, "Not coming from the cache");
+ },
+
+ done: function(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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a2");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done: function(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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a2");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done: function(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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs?etag=a3");
+ },
+
+ loading: function(xhr)
+ {
+ },
+
+ done: function(xhr)
+ {
+ },
+ },
+
+ // Try to load the file, we have to get 200 OK with the new content
+ {
+ init: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a3");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), false, "Not coming from the cache");
+ },
+
+ done: function(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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a3");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done: function(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: function(xhr)
+ {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a3");
+ },
+
+ loading: function(xhr)
+ {
+ is(fromCache(xhr), true, "Coming from the cache");
+ },
+
+ done: function(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: function (xhr) {
+ xhr.open("GET", path + "bug475156.sjs");
+ xhr.setRequestHeader("If-Match", "a1");
+ },
+
+ loading: function (xhr) {
+ },
+
+ done: function (xhr) {
+ },
+ },
+]
+
+
+function drive(test)
+{
+ 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 == 0)
+ 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 000000000..17f5bfdb3
--- /dev/null
+++ b/dom/base/test/test_bug482935.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 482935</title>
+ <script type="text/javascript" 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() {
+ 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(e) {
+ 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();
+ }, false);
+
+ xhr2.open("GET", url);
+ xhr2.setRequestHeader("X-Request", "1", false);
+
+ try { xhr2.send(); }
+ catch(e) {
+ is(xhr2.status, "200", "Exception!");
+ }
+ });
+ }, false);
+
+ xhr.open("GET", url, true);
+ xhr.setRequestHeader("X-Request", "0", false);
+ try { xhr.send(); }
+ catch(e) {
+ is("Nothing", "Exception", "Boom: " + e);
+ }
+}
+
+function onWindowLoad() {
+ 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 000000000..3565f442d
--- /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 type="application/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=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 000000000..ebd020baa
--- /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 type="text/javascript" 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 000000000..b982e3dc7
--- /dev/null
+++ b/dom/base/test/test_bug493881.js
@@ -0,0 +1,72 @@
+/**
+ * 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 (var 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 (var 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 (i = 0; i < idPropList.length; i++) {
+ var style = window.getComputedStyle(document.getElementById(idPropList[i].id), null);
+ 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 (var 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 (i = 0; i < idPropList.length; i++) {
+ var style = window.getComputedStyle(document.getElementById(idPropList[i].id), null);
+ 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 000000000..373c64470
--- /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 type="application/javascript" 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 000000000..5bafcd2ea
--- /dev/null
+++ b/dom/base/test/test_bug498433.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for HTML serializer</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=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;1']
+ .createInstance(SpecialPowers.Ci.nsIURI);
+ baseUri.spec = window.location.href;
+
+ var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var chann = ios.newChannel2(aFile,
+ aCharset,
+ baseUri,
+ null, // aLoadingNode
+ SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_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.open2(), 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.Cc["@mozilla.org/layout/documentEncoder;1?type=text/html"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+
+ 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 000000000..c45c0421e
--- /dev/null
+++ b/dom/base/test/test_bug498897.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=498897
+-->
+<head>
+ <title>Test for Bug 498897</title>
+ <script type="application/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=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: function(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: function(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad: function(contentType, contentLocation, requestOrigin, context,
+ mimeTypeGuess, extra) {
+ var url = window.location.href.substr(0, window.location.href.indexOf('test_bug498897'));
+ if (contentLocation.spec == url + "file_bug498897.css" &&
+ requestOrigin.spec == url + "file_bug498897.html") {
+ checkedLoad = true;
+ }
+
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+
+ shouldProcess: function(contentType, contentLocation, requestOrigin, context,
+ mimeTypeGuess, extra) {
+ 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 000000000..8fd71091a
--- /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 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=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 000000000..3da53fe5a
--- /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 type="application/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=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 000000000..c342adc86
--- /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 type="application/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=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 000000000..fe563aa57
--- /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 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=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 000000000..98db4893e
--- /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 000000000..e11fe3611
--- /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_bug505783.html b/dom/base/test/test_bug505783.html
new file mode 100644
index 000000000..a1c9789ed
--- /dev/null
+++ b/dom/base/test/test_bug505783.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=505783
+-->
+<head>
+ <title>Test for Bug 505783</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <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=505783">Mozilla Bug 505783</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 505783 **/
+
+var a = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
+a.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:base", "http://example.org/");
+a.href = "page";
+is(a.href, "http://example.org/page", "xml:base not used when not in doc");
+document.getElementById("content").appendChild(a);
+is(a.href, "http://example.org/page", "xml:base not used when in doc");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug51034.html b/dom/base/test/test_bug51034.html
new file mode 100644
index 000000000..b6eac76ba
--- /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 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=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 000000000..ac2cb74ba
--- /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.
+
+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 000000000..87acc5d0e
--- /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 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=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 000000000..7b64d8fcb
--- /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 type="application/javascript" 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 000000000..d3e1884d7
--- /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 type="text/javascript" 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 000000000..d60d2ac9f
--- /dev/null
+++ b/dom/base/test/test_bug518104.html
@@ -0,0 +1,37 @@
+<!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 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=518104">Mozilla Bug 518104</a>
+<p id="display"></p>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 518104 **/
+SimpleTest.waitForExplicitFinish();
+
+function done() {
+ var iframe = document.getElementById("iframe");
+ var divs = iframe.contentWindow.document.getElementsByTagName("p").length;
+ is(divs, 0, "<p> got written.")
+ SimpleTest.finish();
+}
+
+</script>
+<div id="content" style="display: none">
+ <iframe id='iframe' src="data:text/html,
+ <div></div><div></div>
+ <script defer src='data:application/javascript,document.write(%2522<p></p>%2522);parent.done();document.close();'></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 000000000..666338d04
--- /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 type="text/javascript" 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 000000000..f4d12621f
--- /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 type="application/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=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 000000000..c311b1615
--- /dev/null
+++ b/dom/base/test/test_bug541937.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for XHTML serializer, bug 541937</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=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.Cc["@mozilla.org/layout/documentEncoder;1?type=text/html"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+
+ 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 000000000..350360f41
--- /dev/null
+++ b/dom/base/test/test_bug544642.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 544642</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 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;version=1.8">
+SimpleTest.waitForExplicitFinish();
+var gen = runTest();
+
+addLoadEvent(function() { gen.next(); });
+
+function runTest() {
+ var iframe = $('iframe');
+ iframe.onerror = function() { gen.send("error"); };
+ iframe.onload = function() { gen.send("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();
+
+ yield undefined;
+}
+
+</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 000000000..d3e169ec4
--- /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 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=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 000000000..c45fd1621
--- /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 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=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 000000000..922c0e9d1
--- /dev/null
+++ b/dom/base/test/test_bug548463.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=548463
+-->
+<head>
+ <title>Test for Bug 548463</title>
+ <script type="application/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=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, false);
+ otherDoc.adoptNode(nodeToAdopt);
+ }
+ }
+ nodeRemoved.addEventListener("DOMNodeRemoved", reparent, false);
+
+ 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);
+
+var thrown = false;
+
+function changeOwnerDocument()
+{
+ SpecialPowers.wrap(elem1).setUserData("foo", null, null);
+ otherDoc.adoptNode(elem1);
+}
+SpecialPowers.wrap(elem1).setUserData("foo", "bar", changeOwnerDocument);
+try {
+ document.adoptNode(elem1);
+}
+catch (e) {
+ thrown = true;
+}
+
+ok(!thrown, "adoptNode while adopting should not throw");
+
+</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 000000000..ae43642cb
--- /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 type="application/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=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 000000000..4a5ec5549
--- /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 type="application/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=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 000000000..759ee2095
--- /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 type="application/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=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="-moz-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 name="test" value="test" type="button">','test_558726_1.innerHTML')
+ is(document.getElementById('test_558726_2').innerHTML, 'y<input><i style="-moz-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 000000000..dbbb49032
--- /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 type="application/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=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 000000000..d8a8fd8e7
--- /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 type="application/javascript" 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"/>
+
+<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") ? true : false;
+ 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 000000000..76bb09b62
--- /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 type="application/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=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 000000000..e2e9e9da2
--- /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 type="application/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=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 000000000..c421f3708
--- /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 type="application/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=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 000000000..5da9867fd
--- /dev/null
+++ b/dom/base/test/test_bug562652.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562652
+-->
+<head>
+ <title>Test for Bug 562652</title>
+ <script type="application/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=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 dtd = document.implementation.createDocumentType("HTML", "-//W3C//DTD HTML 4.01//EN", null);
+ var d = document.implementation.createDocument(null, null, dtd);
+ d.appendChild(d.createElement("html"));
+ d.documentElement.appendChild(d.createElement("body"));
+ d.body.setAttribute("id", "testtarget");
+ return d;
+}
+
+function test(d) {
+ var t = d.getElementById("testtarget");
+ d.addEventListener("DOMNodeInserted", function(e) { ++testCount; }, false);
+ 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 000000000..5f215232a
--- /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 type="application/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=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.xhtml b/dom/base/test/test_bug564863.xhtml
new file mode 100644
index 000000000..df6d2de3c
--- /dev/null
+++ b/dom/base/test/test_bug564863.xhtml
@@ -0,0 +1,359 @@
+<!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 type="text/javascript" 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);
+}
+#xul_id {
+ color: rgb(30, 30, 30);
+}
+#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" />
+<xul:button id="xul_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];
+xul = root.children[2];
+svg = root.children[3].firstChild;
+nsx = root.children[4];
+
+var div_cs = getComputedStyle(div, "");
+var a_cs = getComputedStyle(a, "");
+var xul_cs = getComputedStyle(xul, "");
+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($("xul_id"), xul, "xul getElementById " + test);
+ is($("svg_id"), svg, "svg getElementById " + test);
+ is($("ns_id"), nsx, "ns getElementById " + test);
+}
+
+function checkHasIdNoGEBI(test) {
+ is(div_cs.color, "rgb(10, 10, 10)", "div color " + test);
+ is(a_cs.color, "rgb(20, 20, 20)", "a color " + test);
+ is(xul_cs.color, "rgb(30, 30, 30)", "xul color " + test);
+ is(svg_cs.color, "rgb(40, 40, 40)", "svg color " + test);
+ is(nsx_cs.color, "rgb(50, 50, 50)", "nsx color " + test);
+
+ is(div.id, "div_id", "div id " + test);
+ is(a.id, "a_id", "a id " + test);
+ is(xul.id, "xul_id", "xul 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(xul_cs.color, "rgb(0, 0, 0)", "xul 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(xul.id, "", "xul 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(xul.getAttribute("id"), "", "xul 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($("xul_id"), null, "xul 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 = "";
+xul.id = "";
+svg.id = "";
+nsx.setAttribute("id", "");
+
+checkHasNoId(false, "set to empty");
+
+div.id = "div_id";
+a.id = "a_id";
+xul.id = "xul_id";
+svg.id = "svg_id";
+nsx.setAttribute("id", "ns_id");
+
+checkHasId("set using .id");
+
+div.setAttribute("id", "");
+a.setAttribute("id", "");
+xul.setAttribute("id", "");
+svg.setAttribute("id", "");
+nsx.setAttribute("id", "");
+
+checkHasNoId(false, "setAttribute to empty");
+
+div.id = "div_id";
+a.id = "a_id";
+xul.id = "xul_id";
+svg.id = "svg_id";
+nsx.setAttribute("id", "ns_id");
+
+checkHasId("set again using .id");
+
+div.removeAttribute("id");
+a.removeAttribute("id");
+xul.removeAttribute("id");
+svg.removeAttribute("id");
+nsx.removeAttribute("id");
+
+checkHasNoId(true, "removed attribute");
+
+div.setAttribute("id", "div_id");
+a.setAttribute("id", "a_id");
+xul.setAttribute("id", "xul_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";
+t3 = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "button");
+t3.id = "xul_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.parentNode.removeChild(child);
+}
+
+insertAfter(t1, div);
+insertAfter(t2, a);
+insertAfter(t3, xul);
+insertAfter(t4, svg);
+insertAfter(t5, nsx);
+
+checkHasId("inserted after");
+
+insertBefore(t1, div);
+insertBefore(t2, a);
+insertBefore(t3, xul);
+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($("xul_id"), t3, "xul 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");
+t3.removeAttribute("id");
+t4.removeAttribute("id");
+t5.removeAttribute("id");
+
+checkHasId("removed tx attribute");
+
+t1.setAttribute("id", "div_id");
+t2.setAttribute("id", "a_id");
+t3.setAttribute("id", "xul_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($("xul_id"), t3, "xul getElementById setAttribute before");
+is($("svg_id"), t4, "svg getElementById setAttribute before");
+is($("ns_id"), t5, "ns getElementById setAttribute before");
+
+removeNode(t1);
+removeNode(t2);
+removeNode(t3);
+removeNode(t4);
+removeNode(t5);
+
+checkHasId("removed temporaries");
+
+removeNode(div);
+removeNode(a);
+removeNode(xul);
+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) {
+ div.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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) {
+ nsx.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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) {
+ nsx.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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) {
+ div.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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");
+
+// 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) {
+ xul.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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_bug567350.html b/dom/base/test/test_bug567350.html
new file mode 100644
index 000000000..14a8687af
--- /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 000000000..0978f4333
--- /dev/null
+++ b/dom/base/test/test_bug574596.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=574596
+-->
+<head>
+ <title>Test for Bug 574596</title>
+ <script type="application/javascript" 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 { -moz-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) {
+ 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);
+}
+
+function runTest() {
+ var result = synthesizeDragStart($('link1'), dragLinkText, window);
+ is(result, null, "Drag -moz-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 = synthesizeDragStart($('link2'), dragLinkText, window);
+ is(result, null, "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 000000000..dd6c7fed1
--- /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 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=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 000000000..5c63c0811
--- /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 type="application/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=585978">Mozilla Bug 585978</a>
+
+<script type="application/javascript;version=1.7">
+
+/* 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 000000000..7cef6938f
--- /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 type="application/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=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, false);
+ }, false);
+}
+document.onbeforescriptexecute = function(event) {
+ lastBeforeExecute = event.target;
+};
+
+window.addEventListener("load", function() {
+ is(afterCount, 4, "correct number of afterscriptexecute");
+ SimpleTest.finish();
+}, false);
+</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 000000000..087741a4d
--- /dev/null
+++ b/dom/base/test/test_bug588990.html
@@ -0,0 +1,336 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=588990
+-->
+<head>
+ <title>Test for Bug 588990</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=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.parentNode.removeChild(child);
+}
+
+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) {
+ i1_1.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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) {
+ i2_1.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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) {
+ i2_2.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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) {
+ i3_1.removeEventListener("DOMAttrModified", arguments.callee, false);
+ 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;
+}, false);
+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 000000000..834fa38f8
--- /dev/null
+++ b/dom/base/test/test_bug590812.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for XML pretty printing, bug 590812</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.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=590812">Mozilla Bug 590812</a>
+<p id="display"></p>
+<iframe src="http://noxul.example.com/tests/dom/base/test/file_bug590812.xml"></iframe>
+<iframe src="file_bug590812.xml"></iframe>
+<iframe src="file_bug590812-ref.xhtml"></iframe>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ sNoXUL = snapshotWindow(window.frames[0], false);
+ sWithXUL = snapshotWindow(window.frames[1], false);
+ sRef = snapshotWindow(window.frames[2], 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");
+
+ SimpleTest.finish();
+}
+
+</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 000000000..0faa2f4b6
--- /dev/null
+++ b/dom/base/test/test_bug590870.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for creating XUL elements, bug 590870</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="gen.next()">
+<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;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+window.addEventListener("message", function(e) { gen.send(e.data) }, false);
+
+var gen = runTest();
+
+function runTest() {
+ var iframe = $('iframe');
+ iframe.src = "http://noxul.example.com/tests/dom/base/test/file_bug590870.html";
+ is((yield), true, "shouldn't be able to create XUL elements");
+
+ iframe.src = "file_bug590870.html";
+ is((yield), false, "should be able to create XUL elements");
+
+ SimpleTest.finish();
+ yield undefined;
+}
+
+</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 000000000..1269c4250
--- /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 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=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 000000000..816305849
--- /dev/null
+++ b/dom/base/test/test_bug592829.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=592829
+-->
+<head>
+ <title>Test for Bug 592829</title>
+ <script type="application/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=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;
+try {
+ var x =
+ SpecialPowers.Cc
+ .getService(Components.interfaces.nsIDOMParser)
+ .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 000000000..1eb0bb6a2
--- /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 type="application/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=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 000000000..4cfb0d067
--- /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 type="application/javascript" 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 000000000..a94490e7a
--- /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 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=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 000000000..cbc15a22c
--- /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 type="application/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=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 000000000..611d81136
--- /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 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=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 000000000..0441f7f14
--- /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 type="application/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=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 000000000..0008c2dd5
--- /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 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=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 000000000..ad1f95fe1
--- /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 type="application/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=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 000000000..46fa4443e
--- /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 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=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 000000000..3ae136d7f
--- /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 type="application/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=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_bug614583.html b/dom/base/test/test_bug614583.html
new file mode 100644
index 000000000..866aa1752
--- /dev/null
+++ b/dom/base/test/test_bug614583.html
@@ -0,0 +1,261 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=614583
+-->
+<head>
+ <title>Test for Bug 614583</title>
+ <script type="application/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=614583">Mozilla Bug 614583</a>
+<span id="crash1"
+attr0 attr1 attr2 attr3 attr4 attr5 attr6 attr7 attr8 attr9
+attr10 attr11 attr12 attr13 attr14 attr15 attr16 attr17 attr18 attr19
+attr20 attr21 attr22 attr23 attr24 attr25 attr26 attr27 attr28 attr29
+attr30 attr31 attr32 attr33 attr34 attr35 attr36 attr37 attr38 attr39
+attr40 attr41 attr42 attr43 attr44 attr45 attr46 attr47 attr48 attr49
+attr50 attr51 attr52 attr53 attr54 attr55 attr56 attr57 attr58 attr59
+attr60 attr61 attr62 attr63 attr64 attr65 attr66 attr67 attr68 attr69
+attr70 attr71 attr72 attr73 attr74 attr75 attr76 attr77 attr78 attr79
+attr80 attr81 attr82 attr83 attr84 attr85 attr86 attr87 attr88 attr89
+attr90 attr91 attr92 attr93 attr94 attr95 attr96 attr97 attr98 attr99
+attr100 attr101 attr102 attr103 attr104 attr105 attr106 attr107 attr108 attr109
+attr110 attr111 attr112 attr113 attr114 attr115 attr116 attr117 attr118 attr119
+attr120 attr121 attr122 attr123 attr124 attr125 attr126 attr127 attr128 attr129
+attr130 attr131 attr132 attr133 attr134 attr135 attr136 attr137 attr138 attr139
+attr140 attr141 attr142 attr143 attr144 attr145 attr146 attr147 attr148 attr149
+attr150 attr151 attr152 attr153 attr154 attr155 attr156 attr157 attr158 attr159
+attr160 attr161 attr162 attr163 attr164 attr165 attr166 attr167 attr168 attr169
+attr170 attr171 attr172 attr173 attr174 attr175 attr176 attr177 attr178 attr179
+attr180 attr181 attr182 attr183 attr184 attr185 attr186 attr187 attr188 attr189
+attr190 attr191 attr192 attr193 attr194 attr195 attr196 attr197 attr198 attr199
+attr200 attr201 attr202 attr203 attr204 attr205 attr206 attr207 attr208 attr209
+attr210 attr211 attr212 attr213 attr214 attr215 attr216 attr217 attr218 attr219
+attr220 attr221 attr222 attr223 attr224 attr225 attr226 attr227 attr228 attr229
+attr230 attr231 attr232 attr233 attr234 attr235 attr236 attr237 attr238 attr239
+attr240 attr241 attr242 attr243 attr244 attr245 attr246 attr247 attr248 attr249
+attr250 attr251 attr252 attr253 attr254 attr255 attr256 attr257 attr258 attr259
+attr260 attr261 attr262 attr263 attr264 attr265 attr266 attr267 attr268 attr269
+attr270 attr271 attr272 attr273 attr274 attr275 attr276 attr277 attr278 attr279
+attr280 attr281 attr282 attr283 attr284 attr285 attr286 attr287 attr288 attr289
+attr290 attr291 attr292 attr293 attr294 attr295 attr296 attr297 attr298 attr299
+attr300 attr301 attr302 attr303 attr304 attr305 attr306 attr307 attr308 attr309
+attr310 attr311 attr312 attr313 attr314 attr315 attr316 attr317 attr318 attr319
+attr320 attr321 attr322 attr323 attr324 attr325 attr326 attr327 attr328 attr329
+attr330 attr331 attr332 attr333 attr334 attr335 attr336 attr337 attr338 attr339
+attr340 attr341 attr342 attr343 attr344 attr345 attr346 attr347 attr348 attr349
+attr350 attr351 attr352 attr353 attr354 attr355 attr356 attr357 attr358 attr359
+attr360 attr361 attr362 attr363 attr364 attr365 attr366 attr367 attr368 attr369
+attr370 attr371 attr372 attr373 attr374 attr375 attr376 attr377 attr378 attr379
+attr380 attr381 attr382 attr383 attr384 attr385 attr386 attr387 attr388 attr389
+attr390 attr391 attr392 attr393 attr394 attr395 attr396 attr397 attr398 attr399
+attr400 attr401 attr402 attr403 attr404 attr405 attr406 attr407 attr408 attr409
+attr410 attr411 attr412 attr413 attr414 attr415 attr416 attr417 attr418 attr419
+attr420 attr421 attr422 attr423 attr424 attr425 attr426 attr427 attr428 attr429
+attr430 attr431 attr432 attr433 attr434 attr435 attr436 attr437 attr438 attr439
+attr440 attr441 attr442 attr443 attr444 attr445 attr446 attr447 attr448 attr449
+attr450 attr451 attr452 attr453 attr454 attr455 attr456 attr457 attr458 attr459
+attr460 attr461 attr462 attr463 attr464 attr465 attr466 attr467 attr468 attr469
+attr470 attr471 attr472 attr473 attr474 attr475 attr476 attr477 attr478 attr479
+attr480 attr481 attr482 attr483 attr484 attr485 attr486 attr487 attr488 attr489
+attr490 attr491 attr492 attr493 attr494 attr495 attr496 attr497 attr498 attr499
+attr500 attr501 attr502 attr503 attr504 attr505 attr506 attr507 attr508 attr509
+attr510 attr511 attr512 attr513 attr514 attr515 attr516 attr517 attr518 attr519
+attr520 attr521 attr522 attr523 attr524 attr525 attr526 attr527 attr528 attr529
+attr530 attr531 attr532 attr533 attr534 attr535 attr536 attr537 attr538 attr539
+attr540 attr541 attr542 attr543 attr544 attr545 attr546 attr547 attr548 attr549
+attr550 attr551 attr552 attr553 attr554 attr555 attr556 attr557 attr558 attr559
+attr560 attr561 attr562 attr563 attr564 attr565 attr566 attr567 attr568 attr569
+attr570 attr571 attr572 attr573 attr574 attr575 attr576 attr577 attr578 attr579
+attr580 attr581 attr582 attr583 attr584 attr585 attr586 attr587 attr588 attr589
+attr590 attr591 attr592 attr593 attr594 attr595 attr596 attr597 attr598 attr599
+attr600 attr601 attr602 attr603 attr604 attr605 attr606 attr607 attr608 attr609
+attr610 attr611 attr612 attr613 attr614 attr615 attr616 attr617 attr618 attr619
+attr620 attr621 attr622 attr623 attr624 attr625 attr626 attr627 attr628 attr629
+attr630 attr631 attr632 attr633 attr634 attr635 attr636 attr637 attr638 attr639
+attr640 attr641 attr642 attr643 attr644 attr645 attr646 attr647 attr648 attr649
+attr650 attr651 attr652 attr653 attr654 attr655 attr656 attr657 attr658 attr659
+attr660 attr661 attr662 attr663 attr664 attr665 attr666 attr667 attr668 attr669
+attr670 attr671 attr672 attr673 attr674 attr675 attr676 attr677 attr678 attr679
+attr680 attr681 attr682 attr683 attr684 attr685 attr686 attr687 attr688 attr689
+attr690 attr691 attr692 attr693 attr694 attr695 attr696 attr697 attr698 attr699
+attr700 attr701 attr702 attr703 attr704 attr705 attr706 attr707 attr708 attr709
+attr710 attr711 attr712 attr713 attr714 attr715 attr716 attr717 attr718 attr719
+attr720 attr721 attr722 attr723 attr724 attr725 attr726 attr727 attr728 attr729
+attr730 attr731 attr732 attr733 attr734 attr735 attr736 attr737 attr738 attr739
+attr740 attr741 attr742 attr743 attr744 attr745 attr746 attr747 attr748 attr749
+attr750 attr751 attr752 attr753 attr754 attr755 attr756 attr757 attr758 attr759
+attr760 attr761 attr762 attr763 attr764 attr765 attr766 attr767 attr768 attr769
+attr770 attr771 attr772 attr773 attr774 attr775 attr776 attr777 attr778 attr779
+attr780 attr781 attr782 attr783 attr784 attr785 attr786 attr787 attr788 attr789
+attr790 attr791 attr792 attr793 attr794 attr795 attr796 attr797 attr798 attr799
+attr800 attr801 attr802 attr803 attr804 attr805 attr806 attr807 attr808 attr809
+attr810 attr811 attr812 attr813 attr814 attr815 attr816 attr817 attr818 attr819
+attr820 attr821 attr822 attr823 attr824 attr825 attr826 attr827 attr828 attr829
+attr830 attr831 attr832 attr833 attr834 attr835 attr836 attr837 attr838 attr839
+attr840 attr841 attr842 attr843 attr844 attr845 attr846 attr847 attr848 attr849
+attr850 attr851 attr852 attr853 attr854 attr855 attr856 attr857 attr858 attr859
+attr860 attr861 attr862 attr863 attr864 attr865 attr866 attr867 attr868 attr869
+attr870 attr871 attr872 attr873 attr874 attr875 attr876 attr877 attr878 attr879
+attr880 attr881 attr882 attr883 attr884 attr885 attr886 attr887 attr888 attr889
+attr890 attr891 attr892 attr893 attr894 attr895 attr896 attr897 attr898 attr899
+attr900 attr901 attr902 attr903 attr904 attr905 attr906 attr907 attr908 attr909
+attr910 attr911 attr912 attr913 attr914 attr915 attr916 attr917 attr918 attr919
+attr920 attr921 attr922 attr923 attr924 attr925 attr926 attr927 attr928 attr929
+attr930 attr931 attr932 attr933 attr934 attr935 attr936 attr937 attr938 attr939
+attr940 attr941 attr942 attr943 attr944 attr945 attr946 attr947 attr948 attr949
+attr950 attr951 attr952 attr953 attr954 attr955 attr956 attr957 attr958 attr959
+attr960 attr961 attr962 attr963 attr964 attr965 attr966 attr967 attr968 attr969
+attr970 attr971 attr972 attr973 attr974 attr975 attr976 attr977 attr978 attr979
+attr980 attr981 attr982 attr983 attr984 attr985 attr986 attr987 attr988 attr989
+attr990 attr991 attr992 attr993 attr994 attr995 attr996 attr997 attr998 attr999
+attr1000 attr1001 attr1002 attr1003 attr1004 attr1005 attr1006 attr1007 attr1008 attr1009
+attr1010 attr1011 attr1012 attr1013 attr1014 attr1015 attr1016 attr1017 attr1018 attr1019
+attr1020 attr1021 attr1022 attr1023 attr1024 attr1025 attr1026 attr1027 attr1028 attr1029
+attr1030 attr1031 attr1032 attr1033 attr1034 attr1035 attr1036 attr1037 attr1038 attr1039
+attr1040 attr1041 attr1042 attr1043 attr1044 attr1045 attr1046 attr1047 attr1048 attr1049
+attr1050 attr1051 attr1052 attr1053 attr1054 attr1055 attr1056 attr1057 attr1058 attr1059
+attr1060 attr1061 attr1062 attr1063 attr1064 attr1065 attr1066 attr1067 attr1068 attr1069
+attr1070 attr1071 attr1072 attr1073 attr1074 attr1075 attr1076 attr1077 attr1078 attr1079
+attr1080 attr1081 attr1082 attr1083 attr1084 attr1085 attr1086 attr1087 attr1088 attr1089
+attr1090 attr1091 attr1092 attr1093 attr1094 attr1095 attr1096 attr1097 attr1098 attr1099
+>I'm on a boat</span>
+<span
+attr0 attr1 attr2 attr3 attr4 attr5 attr6 attr7 attr8 attr9
+attr10 attr11 attr12 attr13 attr14 attr15 attr16 attr17 attr18 attr19
+attr20 attr21 attr22 attr23 attr24 attr25 attr26 attr27 attr28 attr29
+attr30 attr31 attr32 attr33 attr34 attr35 attr36 attr37 attr38 attr39
+attr40 attr41 attr42 attr43 attr44 attr45 attr46 attr47 attr48 attr49
+attr50 attr51 attr52 attr53 attr54 attr55 attr56 attr57 attr58 attr59
+attr60 attr61 attr62 attr63 attr64 attr65 attr66 attr67 attr68 attr69
+attr70 attr71 attr72 attr73 attr74 attr75 attr76 attr77 attr78 attr79
+attr80 attr81 attr82 attr83 attr84 attr85 attr86 attr87 attr88 attr89
+attr90 attr91 attr92 attr93 attr94 attr95 attr96 attr97 attr98 attr99
+attr100 attr101 attr102 attr103 attr104 attr105 attr106 attr107 attr108 attr109
+attr110 attr111 attr112 attr113 attr114 attr115 attr116 attr117 attr118 attr119
+attr120 attr121 attr122 attr123 attr124 attr125 attr126 attr127 attr128 attr129
+attr130 attr131 attr132 attr133 attr134 attr135 attr136 attr137 attr138 attr139
+attr140 attr141 attr142 attr143 attr144 attr145 attr146 attr147 attr148 attr149
+attr150 attr151 attr152 attr153 attr154 attr155 attr156 attr157 attr158 attr159
+attr160 attr161 attr162 attr163 attr164 attr165 attr166 attr167 attr168 attr169
+attr170 attr171 attr172 attr173 attr174 attr175 attr176 attr177 attr178 attr179
+attr180 attr181 attr182 attr183 attr184 attr185 attr186 attr187 attr188 attr189
+attr190 attr191 attr192 attr193 attr194 attr195 attr196 attr197 attr198 attr199
+attr200 attr201 attr202 attr203 attr204 attr205 attr206 attr207 attr208 attr209
+attr210 attr211 attr212 attr213 attr214 attr215 attr216 attr217 attr218 attr219
+attr220 attr221 attr222 attr223 attr224 attr225 attr226 attr227 attr228 attr229
+attr230 attr231 attr232 attr233 attr234 attr235 attr236 attr237 attr238 attr239
+attr240 attr241 attr242 attr243 attr244 attr245 attr246 attr247 attr248 attr249
+attr250 attr251 attr252 attr253 attr254 attr255 attr256 attr257 attr258 attr259
+attr260 attr261 attr262 attr263 attr264 attr265 attr266 attr267 attr268 attr269
+attr270 attr271 attr272 attr273 attr274 attr275 attr276 attr277 attr278 attr279
+attr280 attr281 attr282 attr283 attr284 attr285 attr286 attr287 attr288 attr289
+attr290 attr291 attr292 attr293 attr294 attr295 attr296 attr297 attr298 attr299
+attr300 attr301 attr302 attr303 attr304 attr305 attr306 attr307 attr308 attr309
+attr310 attr311 attr312 attr313 attr314 attr315 attr316 attr317 attr318 attr319
+attr320 attr321 attr322 attr323 attr324 attr325 attr326 attr327 attr328 attr329
+attr330 attr331 attr332 attr333 attr334 attr335 attr336 attr337 attr338 attr339
+attr340 attr341 attr342 attr343 attr344 attr345 attr346 attr347 attr348 attr349
+attr350 attr351 attr352 attr353 attr354 attr355 attr356 attr357 attr358 attr359
+attr360 attr361 attr362 attr363 attr364 attr365 attr366 attr367 attr368 attr369
+attr370 attr371 attr372 attr373 attr374 attr375 attr376 attr377 attr378 attr379
+attr380 attr381 attr382 attr383 attr384 attr385 attr386 attr387 attr388 attr389
+attr390 attr391 attr392 attr393 attr394 attr395 attr396 attr397 attr398 attr399
+attr400 attr401 attr402 attr403 attr404 attr405 attr406 attr407 attr408 attr409
+attr410 attr411 attr412 attr413 attr414 attr415 attr416 attr417 attr418 attr419
+attr420 attr421 attr422 attr423 attr424 attr425 attr426 attr427 attr428 attr429
+attr430 attr431 attr432 attr433 attr434 attr435 attr436 attr437 attr438 attr439
+attr440 attr441 attr442 attr443 attr444 attr445 attr446 attr447 attr448 attr449
+attr450 attr451 attr452 attr453 attr454 attr455 attr456 attr457 attr458 attr459
+attr460 attr461 attr462 attr463 attr464 attr465 attr466 attr467 attr468 attr469
+attr470 attr471 attr472 attr473 attr474 attr475 attr476 attr477 attr478 attr479
+attr480 attr481 attr482 attr483 attr484 attr485 attr486 attr487 attr488 attr489
+attr490 attr491 attr492 attr493 attr494 attr495 attr496 attr497 attr498 attr499
+attr500 attr501 attr502 attr503 attr504 attr505 attr506 attr507 attr508 attr509
+attr510 attr511 attr512 attr513 attr514 attr515 attr516 attr517 attr518 attr519
+attr520 attr521 attr522 attr523 attr524 attr525 attr526 attr527 attr528 attr529
+attr530 attr531 attr532 attr533 attr534 attr535 attr536 attr537 attr538 attr539
+attr540 attr541 attr542 attr543 attr544 attr545 attr546 attr547 attr548 attr549
+attr550 attr551 attr552 attr553 attr554 attr555 attr556 attr557 attr558 attr559
+attr560 attr561 attr562 attr563 attr564 attr565 attr566 attr567 attr568 attr569
+attr570 attr571 attr572 attr573 attr574 attr575 attr576 attr577 attr578 attr579
+attr580 attr581 attr582 attr583 attr584 attr585 attr586 attr587 attr588 attr589
+attr590 attr591 attr592 attr593 attr594 attr595 attr596 attr597 attr598 attr599
+attr600 attr601 attr602 attr603 attr604 attr605 attr606 attr607 attr608 attr609
+attr610 attr611 attr612 attr613 attr614 attr615 attr616 attr617 attr618 attr619
+attr620 attr621 attr622 attr623 attr624 attr625 attr626 attr627 attr628 attr629
+attr630 attr631 attr632 attr633 attr634 attr635 attr636 attr637 attr638 attr639
+attr640 attr641 attr642 attr643 attr644 attr645 attr646 attr647 attr648 attr649
+attr650 attr651 attr652 attr653 attr654 attr655 attr656 attr657 attr658 attr659
+attr660 attr661 attr662 attr663 attr664 attr665 attr666 attr667 attr668 attr669
+attr670 attr671 attr672 attr673 attr674 attr675 attr676 attr677 attr678 attr679
+attr680 attr681 attr682 attr683 attr684 attr685 attr686 attr687 attr688 attr689
+attr690 attr691 attr692 attr693 attr694 attr695 attr696 attr697 attr698 attr699
+attr700 attr701 attr702 attr703 attr704 attr705 attr706 attr707 attr708 attr709
+attr710 attr711 attr712 attr713 attr714 attr715 attr716 attr717 attr718 attr719
+attr720 attr721 attr722 attr723 attr724 attr725 attr726 attr727 attr728 attr729
+attr730 attr731 attr732 attr733 attr734 attr735 attr736 attr737 attr738 attr739
+attr740 attr741 attr742 attr743 attr744 attr745 attr746 attr747 attr748 attr749
+attr750 attr751 attr752 attr753 attr754 attr755 attr756 attr757 attr758 attr759
+attr760 attr761 attr762 attr763 attr764 attr765 attr766 attr767 attr768 attr769
+attr770 attr771 attr772 attr773 attr774 attr775 attr776 attr777 attr778 attr779
+attr780 attr781 attr782 attr783 attr784 attr785 attr786 attr787 attr788 attr789
+attr790 attr791 attr792 attr793 attr794 attr795 attr796 attr797 attr798 attr799
+attr800 attr801 attr802 attr803 attr804 attr805 attr806 attr807 attr808 attr809
+attr810 attr811 attr812 attr813 attr814 attr815 attr816 attr817 attr818 attr819
+attr820 attr821 attr822 attr823 attr824 attr825 attr826 attr827 attr828 attr829
+attr830 attr831 attr832 attr833 attr834 attr835 attr836 attr837 attr838 attr839
+attr840 attr841 attr842 attr843 attr844 attr845 attr846 attr847 attr848 attr849
+attr850 attr851 attr852 attr853 attr854 attr855 attr856 attr857 attr858 attr859
+attr860 attr861 attr862 attr863 attr864 attr865 attr866 attr867 attr868 attr869
+attr870 attr871 attr872 attr873 attr874 attr875 attr876 attr877 attr878 attr879
+attr880 attr881 attr882 attr883 attr884 attr885 attr886 attr887 attr888 attr889
+attr890 attr891 attr892 attr893 attr894 attr895 attr896 attr897 attr898 attr899
+attr900 attr901 attr902 attr903 attr904 attr905 attr906 attr907 attr908 attr909
+attr910 attr911 attr912 attr913 attr914 attr915 attr916 attr917 attr918 attr919
+attr920 attr921 attr922 attr923 attr924 attr925 attr926 attr927 attr928 attr929
+attr930 attr931 attr932 attr933 attr934 attr935 attr936 attr937 attr938 attr939
+attr940 attr941 attr942 attr943 attr944 attr945 attr946 attr947 attr948 attr949
+attr950 attr951 attr952 attr953 attr954 attr955 attr956 attr957 attr958 attr959
+attr960 attr961 attr962 attr963 attr964 attr965 attr966 attr967 attr968 attr969
+attr970 attr971 attr972 attr973 attr974 attr975 attr976 attr977 attr978 attr979
+attr980 attr981 attr982 attr983 attr984 attr985 attr986 attr987 attr988 attr989
+attr990 attr991 attr992 attr993 attr994 attr995 attr996 attr997 attr998 attr999
+attr1000 attr1001 attr1002 attr1003 attr1004 attr1005 attr1006 attr1007 attr1008 attr1009
+attr1010 attr1011 attr1012 attr1013 attr1014 attr1015 attr1016 attr1017 attr1018 attr1019
+attr1020 attr1021 attr1022 attr1023 attr1024 attr1025 attr1026 attr1027 attr1028 attr1029
+attr1030 attr1031 attr1032 attr1033 attr1034 attr1035 attr1036 attr1037 attr1038 attr1039
+attr1040 attr1041 attr1042 attr1043 attr1044 attr1045 attr1046 attr1047 attr1048 attr1049
+attr1050 attr1051 attr1052 attr1053 attr1054 attr1055 attr1056 attr1057 attr1058 attr1059
+attr1060 attr1061 attr1062 attr1063 attr1064 attr1065 attr1066 attr1067 attr1068 attr1069
+attr1070 attr1071 attr1072 attr1073 attr1074 attr1075 attr1076 attr1077 attr1078 attr1079
+attr1080 attr1081 attr1082 attr1083 attr1084 attr1085 attr1086 attr1087 attr1088 attr1089
+attr1090 attr1091 attr1092 attr1093 attr1094 attr1095 attr1096 attr1097 attr1098 attr1099
+id="crash2"
+>I'm on a boat</span>
+<pre id="test">
+<script>
+span = document.createElement("span");
+try {
+ var i = 0;
+ while(1) {
+ span.setAttribute("attr" + (++i), "foo");
+ }
+}
+catch (ex) {}
+span.id = "crash3";
+document.body.appendChild(span);
+var attrCount = span.attributes.length;
+var spans = document.getElementsByTagName("span");
+for (i = 0; i < spans.length; ++i) {
+ is(spans[i].attributes.length, attrCount, "didn't have maxed out attrs");
+}
+while (spans.length) {
+ spans[0].parentNode.removeChild(spans[0]);
+}
+</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 000000000..134bb898d
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/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=622088">Mozilla Bug 622088</a>
+<pre id="test">
+
+<iframe id='iframe' src='file_bug622088_inner.html'></iframe>
+
+<iframe id='dataWindow' src="data:text/html,<html><head>
+<script>function getXHRObject() { return new XMLHttpRequest(); }</script>
+</head><body onload='parent.dataWindowLoaded()'>Hello!!</body></html>"></iframe>
+
+<script class="testbody" type="application/javascript;version=1.8">
+
+// 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 000000000..e712fd4e7
--- /dev/null
+++ b/dom/base/test/test_bug622117.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622117
+-->
+<head>
+ <title>Test for Bug 622117</title>
+ <script type="application/javascript" 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=622117">Mozilla Bug 622117</a>
+<p id="display">
+ <iframe id="testframe"
+ src="data:text/html,<a href='data:text/plain,PASS' 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 PASSURL = "data:text/plain,PASS";
+
+SimpleTest.waitForFocus(function() {
+ $("testframe").onload = function() {
+ is(this.contentDocument.documentElement.textContent, "PASS", "Should have loaded link");
+ SimpleTest.finish();
+ };
+
+ var win = $("testframe").contentWindow;
+ 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 000000000..684a8f2b3
--- /dev/null
+++ b/dom/base/test/test_bug622246.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=622246
+-->
+<head>
+ <title>Test for Bug 622246</title>
+ <script type="application/javascript" 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=622246">Mozilla Bug 622246</a>
+<p id="display">
+ <iframe id="testframe"
+ src="data:text/html,<span onclick='this.parentNode.removeChild(this)'><a href='data:text/plain,PASS'>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 PASSURL = "data:text/plain,PASS";
+
+SimpleTest.waitForFocus(function() {
+ $("testframe").onload = function() {
+ is(this.contentDocument.documentElement.textContent, "PASS", "Should have loaded link");
+ SimpleTest.finish();
+ };
+
+ var win = $("testframe").contentWindow;
+ 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 000000000..92d144323
--- /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 type="application/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=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 000000000..70ee12feb
--- /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 type="application/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=626262">Mozilla Bug 626262</a>
+<p id="display"><iframe id="f" src="data:text/html,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, false);
+ document.adoptNode(parent);
+ }
+
+ var text = document.createTextNode(" ");
+ document.documentElement.appendChild(text);
+
+ var thrown = false;
+ try {
+ window.addEventListener("DOMNodeRemoved", a, false);
+ 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 000000000..d29b86a99
--- /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 type="application/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=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 : function(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 : function(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 : function(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 000000000..b78295eb3
--- /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 000000000..4f6741ed0
--- /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 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=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 000000000..bb5a6a02a
--- /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 type="application/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=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 000000000..8c2bb7195
--- /dev/null
+++ b/dom/base/test/test_bug650001.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650001
+-->
+<head>
+ <title>Test for Bug 650001</title>
+ <script type="application/javascript" src="/MochiKit/packed.js"></script>
+ <script type="application/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=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 000000000..fbc6f8c03
--- /dev/null
+++ b/dom/base/test/test_bug650776.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650776
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 650776</title>
+ <script type="application/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=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 gets dropped when styles allowed; however, reconstructing the p { ... } part seems broken!
+todo_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; }</style></head><body><p style="background-color: blue;"></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");
+
+
+</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 000000000..c907c265a
--- /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 type="application/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=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 000000000..47c05845d
--- /dev/null
+++ b/dom/base/test/test_bug656283.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=656283
+-->
+<head>
+ <title>Test for Bug 656283</title>
+ <script type="application/javascript" 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() {
+ // First a test with data documents.
+ 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.src = "data:text/html,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.src = "data:text/html,<input>";
+}
+
+function test3() {
+ ifr.contentDocument.getElementsByTagName("input")[0].focus();
+ is(firstDoc.activeElement, firstDoc.body,
+ "Active element should be body by default! (3)");
+ ifr.parentNode.removeChild(ifr);
+ 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 000000000..fbfdde3ee
--- /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 type="application/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=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 000000000..4f73c4075
--- /dev/null
+++ b/dom/base/test/test_bug666604.html
@@ -0,0 +1,142 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=666604
+-->
+<head>
+ <title>Test for Bug 666604</title>
+ <script type="application/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=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 = document.createEvent("KeyboardEvent");
+ e.initKeyEvent("keypress", true, true, window, ctrl, false,
+ false, false, 13, 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";
+ 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";
+ 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 000000000..a48fea935
--- /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 type="application/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=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 000000000..cef5ff560
--- /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 type="application/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=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 000000000..7fc5097d0
--- /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 type="application/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=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 div = document.createElement("DIV");
+ if (l) div.appendChild(l);
+ if (r) div.appendChild(r);
+ return div;
+ }
+ 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 000000000..1f0ca4b63
--- /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 type="application/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=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 000000000..c2dc8ed3a
--- /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 type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" 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;version=1.7">
+/** 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 = `data:text/html;charset=UTF-8,<!DOCTYPE html>
+<html><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", function() {
+ try {
+ tests.next();
+ ok(compareSnapshots(snapshotWindow(testFrame.contentWindow),
+ snapshotWindow(refFrame.contentWindow), true)[0],
+ "bidi is not detected correctly");
+
+ testFrame.contentWindow.location.reload();
+ } catch (err if err instanceof StopIteration) {
+ SimpleTest.finish();
+ }
+ }, false);
+ testFrame.src = page;
+}, false);
+refFrame.src = 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 000000000..1b67ca6ca
--- /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 type="application/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=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 000000000..2dcff2112
--- /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 type="application/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=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 000000000..fe59afb62
--- /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 type="application/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=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 000000000..d05cdc049
--- /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 type="application/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=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 000000000..7562259a3
--- /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 type="application/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=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 000000000..c38c6349c
--- /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 type="text/javascript" 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 000000000..7a9b39c06
--- /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 type="application/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=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 000000000..71741e236
--- /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 type="application/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=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 000000000..f688fb9c3
--- /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 type="application/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=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 000000000..2fb4c3c97
--- /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 type="application/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=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 000000000..c27b8a555
--- /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 type="application/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=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 000000000..8396ba262
--- /dev/null
+++ b/dom/base/test/test_bug698381.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=698381
+ -->
+ <head>
+ <title>Test for Bug 698381</title>
+ <script type="text/javascript"
+ 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 000000000..f55a5f7f9
--- /dev/null
+++ b/dom/base/test/test_bug698384.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=698384
+ -->
+ <head>
+ <title>Test for Bug 698384</title>
+ <script type="text/javascript"
+ 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 000000000..39759f154
--- /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 type="application/javascript" 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.html b/dom/base/test/test_bug704320.html
new file mode 100644
index 000000000..5c8e46c03
--- /dev/null
+++ b/dom/base/test/test_bug704320.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=704320
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 704320</title>
+ <script type="application/javascript" 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;version=1.7">
+
+//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',
+ ];
+});
+
+var testIframeUrls = generateURLArray('http', 'http');
+testIframeUrls = testIframeUrls.concat(generateURLArray('https', 'https'));
+testIframeUrls = testIframeUrls.concat(generateURLArray('http', 'https'));
+testIframeUrls = testIframeUrls.concat(generateURLArray('https', 'http'));
+
+SimpleTest.waitForExplicitFinish();
+var advance = function() { tests.next(); };
+
+/**
+ * This is the main test routine -- serialized by use of a generator.
+ * It performs all tests in sequence using in the same iframe.
+ */
+var tests = (function() {
+ SimpleTest.requestLongerTimeout(4);
+ var iframe = document.getElementById("testframe");
+ iframe.onload = function() {
+ advance();
+ }
+
+ // load the test frame from testIframeUrls[url]
+ // it will call back into this function via postMessage when it finishes loading.
+ // and continue beyond the yield.
+ for(url in testIframeUrls) {
+ yield iframe.src = testIframeUrls[url];
+ // run test and check result for loaded test URL
+ yield checkExpectedGlobalResults();
+ }
+
+ // complete. Be sure to yield so we don't call this twice.
+ yield SimpleTest.finish();
+})();
+
+</script>
+</head>
+
+<body onload="tests.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="testframe"></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 000000000..8188a9480
--- /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 type="application/javascript" 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;version=1.7">
+
+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. Be sure to yield so we don't call this twice.
+ yield 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 000000000..b9cc74e3d
--- /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 type="application/javascript" 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;version=1.7">
+
+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. Be sure to yield so we don't call this twice.
+ yield 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 000000000..d7f2fc72d
--- /dev/null
+++ b/dom/base/test/test_bug704320_preload.html
@@ -0,0 +1,145 @@
+<!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 type="application/javascript" 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;version=1.7">
+
+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 first 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_noreuse.html';
+
+ // check the first test
+ yield checkResults(finalizePreloadNoreuse);
+
+ // 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);
+
+ // complete. Be sure to yield so we don't call this twice.
+ yield SimpleTest.finish();
+})();
+
+// Helper functions below.
+
+/**
+ * This checks the first test: a test where the preloads should not
+ * be reused. * we expect two requests for each image, script, js request
+ * since the referrer policy changed after speculative loads were started.
+ * Problem is that the "origin"/revised loads won't necessarily happen,
+ * so we test for one or two loads (both are OK) and make the 'origin'
+ * referrer optional.
+ */
+function finalizePreloadNoreuse(results) {
+ console.log("<br/><pre>" + JSON.stringify(results) + "</pre>");
+ var expected = {'css': {'count': 2, 'referrers': ['full', 'origin']},
+ 'img': {'count': 2, 'referrers': ['full', 'origin']},
+ 'js': {'count': 2, 'referrers': ['full', 'origin']}};
+
+ for (var x in expected) {
+ ok(x in results, "some " + x + " loads required in results object.");
+
+ ok(results[x].count == 1 || results[x].count == 2,
+ "Expected 1-2 loads for " + x + " requests.");
+
+ // the 'full' preload is optional, but required if count > 1
+ if (results[x].count > 1) {
+ ok(results[x].referrers.indexOf('full') >= 0,
+ "More than one load for " + x + ", so expected an 'full' referrer preload.")
+ }
+
+ // 'origin' (final load) is required
+ ok(results[x].referrers.indexOf('origin') >= 0,
+ "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.");
+ }
+
+ advance();
+}
+
+/**
+ * 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.indexOf('origin') >= 0,
+ "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 000000000..2cde32fd5
--- /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 type="application/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=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 000000000..d6b5b0c6d
--- /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 000000000..65bc20c35
--- /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 type="application/javascript" 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 000000000..c92be3dd1
--- /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 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=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_bug715041.xul b/dom/base/test/test_bug715041.xul
new file mode 100644
index 000000000..c269b461b
--- /dev/null
+++ b/dom/base/test/test_bug715041.xul
@@ -0,0 +1,815 @@
+<?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=715041
+-->
+ <window title="Mozilla Bug 715041"
+xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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=715041"
+target="_blank">Mozilla Bug 715041</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Mock Idle Service Test for Bug 715041 **/
+ //class mock javascript idle service
+ var idleServiceObj = {
+ observers: [],
+ windowObservers: [],
+ idleTimeInMS: 5000, //in milli seconds
+
+ // takes note of the idle observers added as the minimum idle observer
+ // with the idle service
+ timesAdded: [],
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIFactory) ||
+ iid.equals(Components.interfaces.nsIIdleService)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ createInstance: function(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ get idleTime() {
+ return this.idleTimeInMS; //in milli seconds
+ },
+
+ set idleTime(timeInMS) {
+ this.idleTimeInMS = timeInMS;
+ },
+
+ getWindowFromObserver: function(observer) {
+ try {
+ var interfaceRequestor = observer.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ var window = interfaceRequestor.getInterface(Components.interfaces.nsIDOMWindow);
+ return window;
+ }
+ catch (e) {}
+
+ return null;
+ },
+
+ testIdleBackService: function(observer, topic) {
+ dump("\nJS FAKE IDLE SERVICE\n");
+ dump("JS NUM OBSERVERS: " + this.observers.length + "\n");
+
+ if (this.observers.length > 1) {
+ this.observers[1].observer.observe(observer, topic, '\0');
+ dump("JS CALLED OBSERVE FUNCTION!!!\n\n");
+ }
+ },
+
+ addIdleObserver: function(observer, time) {
+ dump("\nJS FAKE IDLE SERVICE add idle observer before\n");
+ dump("JS NUM OBSERVERS: " + this.observers.length + "\n");
+
+ var window = this.getWindowFromObserver(observer);
+ dump("window is: " + window + "\n");
+
+ if (window) {
+ this.observers.push({ observer: observer, time: time, });
+ addedIdleObserver = true;
+ numIdleObserversAdded++;
+ this.timesAdded.push(time);
+
+ dump ("\nMOCK IDLE SERVICE ADDING idle observer with time: " + time + "\n");
+ dump("MOCK IDLE SERVICE: num idle observers added: " + numIdleObserversAdded + "\n\n");
+ }
+ else {
+ dump("SHOULD NEVER GET HERE!");
+ oldIdleService.addIdleObserver(observer, time);
+ addedIdleObserver = false;
+ }
+
+ dump("\nJS FAKE IDLE SERVICE end of add idle observer\n");
+ dump("JS NUM OBSERVERS: " + this.observers.length + "\n");
+ },
+
+ removeIdleObserver: function(observer, time) {
+ dump("\nJS REMOVE IDLE OBSERVER () time to be removed: " + time + "\n");
+ var window = this.getWindowFromObserver(observer);
+ if (!window) {
+ oldIdleService.removeIdleObserver(observer, time);
+ }
+ else {
+ var observerIndex = -1;
+ for (var i=0; i<this.observers.length; i++) {
+ dump("JS removeIdleObserver() observer time: " + this.observers[i].time + "\n");
+ if (this.observers[i].time === time) {
+ observerIndex = i;
+ break;
+ }
+ }
+
+ if (observerIndex != -1 && this.observers.length > 0) {
+ numIdleObserversRemoved++;
+ this.observers.splice(observerIndex, 1);
+ removedIdleObserver = true;
+ dump("MOCK IDLE SERVICE REMOVING idle observer with time " + time + "\n");
+ dump("MOCK IDLE SERVICE numIdleObserversRemoved: " + numIdleObserversRemoved + " numIdleObserversAdded: " + numIdleObserversAdded + "\n\n");
+ }
+ else {
+ removedIdleObserver = false;
+ }
+ }
+ dump("\nJS FAKE IDLE SERVICE end of remove idle observer\n");
+ dump("JS NUM OBSERVERS: " + this.observers.length + "\n");
+ },
+ };
+
+ /** Test for Bug 715041 **/
+ dump("\n\n\nJS STARTING TESTING FOR BUG 715041\n");
+
+ //bool variables
+ var addedIdleObserver = removedIdleObserver = passed = cleanUp = false;
+
+ //test case enabled
+ var AddOutOfOrderActiveEnabled = AddOutOfOrderIdleEnabled =
+ AddShiftLocalEnabled = AddNewLocalWhileAllIdleEnabled =
+ TestActiveToActiveNotification = ShiftLocalTimerBackEnabled =
+ AddRemoveIdleObserverWithInvalidTimeEnabled = true;
+
+ //msgXCount
+ var msg0Count = msg1Count = msg2Count = msg3Count = msg4Count = msg5Count =
+ msg6Count = tcZero = currTestCaseNum = prevMsgNum =
+ numIdleObserversRemoved = numIdleObserversAdded = 0;
+
+ //test case number
+ var tcZero = 0;
+ var tcAddOutOfOrderActive = 1;
+ var tcAddOutOfOrderIdle = 2;
+ var tcAddShiftLocal = 3;
+ var tcAddNewLocalWhileAllIdle = 4;
+ var tcShiftLocalTimerBack = 5;
+ var tcAddRemoveIdleObserverWithInvalidTime = 6;
+ var tcTestActiveToActiveNotification = 7;
+
+ function ResetMsgCounts() {
+ msg0Count = msg1Count = msg2Count = msg3Count = msg4Count = msg5Count =
+ msg6Count = prevMsgNum = 0;
+ }
+
+ function ResetVars() {
+ msg0Count = msg1Count = msg2Count = msg3Count = msg4Count = msg5Count =
+ msg6Count = prevMsgNum = 0;
+
+ numIdleObserversAdded = numIdleObserversRemoved = 0;
+ currTestCaseNum = -1;
+ addedIdleObserver = removedIdleObserver = passed = cleanUp = false;
+ }
+
+ /*
+ * - function printMsgCounts()
+ */
+ function printMsgCounts() {
+ dump("\nmsg0Count: " + msg0Count +
+ "\nmsg1Count: " + msg1Count +
+ "\nmsg2Count: " + msg2Count +
+ "\nmsg3Count: " + msg3Count +
+ "\nmsg5Count: " + msg5Count +
+ "\nmsg6Count: " + msg6Count +
+ "\n"
+ );
+ }
+
+ function performNextTest() {
+ dump("\nfunction performNextTest()\ncurrTestCaseNum: " + currTestCaseNum +
+ "\ncleanUp: " + cleanUp +
+ "\npassed: " + passed +
+ "\nnumIdleObserversRemoved: " + numIdleObserversRemoved +
+ "\nnumIdleObservesAdded: " + numIdleObserversAdded + "\n");
+
+ switch (currTestCaseNum) {
+ case tcZero:
+ ok(passed, "Test case 0 failed clean up!");
+ caseZeroCleanUp();
+ break;
+ case tcAddShiftLocal:
+ if (cleanUp && numIdleObserversRemoved === 1) {
+ passed = true;
+ ok(passed, "Failed test case AddShiftLocalCleanUp()");
+ if (AddNewLocalWhileAllIdleEnabled) {
+ AddNewLocalWhileAllIdle();
+ }
+ else {
+ SimpleTest.finish();
+ }
+ }
+ break;
+ case tcAddNewLocalWhileAllIdle:
+ ok(passed, "Failed test case: AddNewLocalWhileAllIdle()");
+ AddNewLocalWhileAllIdleCleanUp();
+ break;
+ default:
+ //do nothing.
+ break;
+ }
+ }
+
+ //Place Holder.
+ var idleHandler0 = function() { dump("rmsg 0, should never be used!\n"); };
+
+ //idleHandler1
+ function idleHandler1() {
+ msg1Count++;
+ dump("msg 1 Count: " + msg1Count + "\n");
+
+ switch (currTestCaseNum) {
+ case tcAddOutOfOrderIdle:
+ if (msg1Count === 1 && msg2Count === 1 && msg3Count === 1) {
+ idleServiceObj.idleTime = 0;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "active");
+ }
+ else if (msg1Count === 4 && msg2Count === 4 && msg3Count === 4) {
+ passed = true;
+ AddOutOfOrderIdleCleanUp();
+ }
+ break;
+ case tcTestActiveToActiveNotification:
+ if (msg1Count === 1 && !msg2Count && !msg3Count && !msg4Count && !msg5Count) {
+ idleServiceObj.idleTime = 500;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "active");
+ return;
+ }
+
+ if (msg1Count === 2 && !msg2Count && !msg3Count && !msg4Count && !msg5Count) {
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ return;
+ }
+
+ if (msg1Count === 3 && !msg2Count && !msg3Count && !msg4Count && !msg5Count) {
+ return;
+ }
+ ok(false, "Failed test active to active notification.");
+ SimpleTest.finish();
+ break;
+ default:
+ break;
+ }
+ }
+
+ //idleHandler2
+ function idleHandler2() {
+ msg2Count++;
+ dump("msg 2 Count: " + msg2Count + "\n");
+
+ switch (currTestCaseNum) {
+ case tcZero:
+ switch (msg2Count) {
+ case 2:
+ passed = true;
+ performNextTest();
+ break;
+ default:
+ break;
+ }
+ break;
+ case tcAddOutOfOrderIdle:
+ if (msg3Count === 1 && msg2Count === 1 && !msg1Count) {
+ idleServiceObj.idleTime = 4000;
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ }
+ break;
+ case tcAddShiftLocal:
+ if (!msg1Count && msg2Count === 1 && !msg3Count && !msg4Count) {
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ }
+ AddShiftLocalCleanUp();
+ break;
+ case tcAddNewLocalWhileAllIdle:
+ if (msg1Count === 1 && msg2Count === 2) {
+ idleServiceObj.idleTime = 3500;
+ window.navigator.addIdleObserver(idleObserversArray[5]);
+ }
+ break;
+ case (tcShiftLocalTimerBack):
+ if (!msg1Count && msg2Count === 1 && !msg3Count && !msg4Count && !msg5Count) {
+ window.navigator.addIdleObserver(idleObserversArray[5]);
+ window.navigator.addIdleObserver(idleObserversArray[4]);
+ }
+ break;
+ default:
+ //do nothing.
+ break;
+ }
+ }
+
+ //idleHandler3
+ function idleHandler3() {
+ msg3Count++;
+ dump("msg 3 Count: " + msg3Count + "\n");
+
+ switch (currTestCaseNum) {
+ case (tcAddOutOfOrderIdle):
+ if (msg3Count === 1) {
+ idleServiceObj.idleTime = 3500;
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ }
+ if (msg1Count === 2 && msg2Count === 2 && msg3Count === 2) {
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+ else if (msg1Count === 3 && msg2Count === 3 && msg3Count === 3) {
+ AddOutOfOrderIdle();
+ }
+ else if (msg1Count === 3 && msg2Count === 3 && msg3Count === 4) {
+ passed = true;
+ AddOutOfOrderIdleCleanUp();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ //idleHandler4
+ function idleHandler4() {
+ msg4Count++;
+ dump("msg 4 Count: " + msg4Count + "\n");
+
+ switch (currTestCaseNum) {
+ case tcAddOutOfOrderActive:
+ if (msg1Count && msg2Count && msg3Count && msg4Count) {
+ passed = true;
+ ok(passed, "idleHandler4: failed AddOutOfOrderActive()");
+ AddOutOfOrderActiveCleanUp();
+ cleanUp = true;
+ }
+ break;
+ case tcAddShiftLocal:
+ if (msg1Count === 1 && msg3Count === 1 && msg4Count === 1) {
+ idleServiceObj.idleTime = 3200;
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ }
+ break;
+ default:
+ //do nothing.
+ break;
+ }
+ }
+
+ //idleHandler5
+ function idleHandler5() {
+ msg5Count++;
+ dump("msg 5 Count: " + msg5Count + "\n");
+
+ switch (currTestCaseNum) {
+ case tcAddNewLocalWhileAllIdle:
+ if (msg1Count === 1 && msg2Count === 2 && msg5Count === 1) {
+ passed = true;
+ performNextTest();
+ }
+ break;
+ case tcShiftLocalTimerBack:
+ if (!msg1Count && msg2Count === 1 && !msg3Count && msg4Count === 1 && msg5Count === 1) {
+ passed = true;
+ ShiftLocalTimerBackCleanUp();
+ }
+ break;
+ case tcTestActiveToActiveNotification:
+ passed = false;
+ if (msg1Count === 3 && !msg2Count && !msg3Count && !msg4Count && msg5Count === 1) {
+ passed = true;
+ }
+ ok(passed, "Failed TestActiveToActiveNotification.");
+ TestActiveNotificationCleanUp();
+ break;
+ default:
+ //do nothing.
+ break;
+ }
+ }
+
+ //idleHandler6
+ function idleHandler6() {
+ dump("msg 6 Count: " + msg6Count + "\n");
+ }
+
+ var idleObserversArray = [];
+ idleObserversArray[0] = {time: 0, onidle: idleHandler0, onactive: idleHandler0};
+ idleObserversArray[1] = {time: 1, onidle: idleHandler1, onactive: idleHandler1};
+ idleObserversArray[2] = {time: 2, onidle: idleHandler2, onactive: idleHandler2};
+ idleObserversArray[3] = {time: 3, onidle: idleHandler3, onactive: idleHandler3};
+ idleObserversArray[4] = {time: 4, onidle: idleHandler4, onactive: idleHandler4};
+ idleObserversArray[5] = {time: 5, onidle: idleHandler5, onactive: idleHandler5};
+ idleObserversArray[6] = {time: 0, onidle: idleHandler6, onactive: idleHandler6};
+ idleObserversArray[7] = {time: 2, onidle: null, onactive: null};
+
+ idleServiceObj.observers.push( {observer: idleObserversArray[0], time: 0, } );
+
+ /*
+ * - case 0
+ * - AddSingleIdleObserver
+ * - takes care of adding duplicate local too
+ * - user is currently idle since the
+ * requested idle time of 2s < current idle time of 5000ms set below.
+ */
+ function caseZero() {
+ dump("\n\nTESTING CASE 0\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcZero;
+ idleServiceObj.idleTime = 5000;
+
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ idleServiceObj.testIdleBackService(idleObserversArray[2], "idle");
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ }
+
+ function caseZeroCleanUp() {
+ dump("\ncaseZeroCleanUp()\n");
+ dump("==============\n");
+ ResetVars();
+ currTestCaseNum = tcZero;
+ cleanUp = false;
+
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+
+ if (AddOutOfOrderActiveEnabled) {
+ AddOutOfOrderActive();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ AddOutOfOrderActive()
+ - Tests if the idle observer with the min time is always registered correctly
+ with the idle service.
+ */
+ function AddOutOfOrderActive() {
+ dump("\n\nTESTING CASE AddOutOfOrderActive\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcAddOutOfOrderActive;
+ idleServiceObj.idleTime = 500;
+
+ window.navigator.addIdleObserver(idleObserversArray[3]); //msg3
+ window.navigator.addIdleObserver(idleObserversArray[4]); //msg4
+ window.navigator.addIdleObserver(idleObserversArray[1]); //msg1
+ window.navigator.addIdleObserver(idleObserversArray[2]); //msg2
+ passed = false;
+
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ /*
+ - AddOutOfOrderActiveCleanUp()
+ */
+ function AddOutOfOrderActiveCleanUp() {
+ dump("\nAddOutOfOrderActiveCleanUp()\n");
+ dump("==============================\n");
+ ResetVars();
+ currTestCaseNum = tcAddOutOfOrderActive;
+ cleanUp = false;
+ idleServiceObj.idleTime = 4500;
+
+ for (var i=1; i<5; i++) {
+ window.navigator.removeIdleObserver(idleObserversArray[i]);
+ }
+ dump("JS AddOutOfOrderActiveCleanUp() DONE\n");
+ if (AddOutOfOrderIdleEnabled) {
+ AddOutOfOrderIdle();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ - AddOutOfOrderIdle()
+ */
+ function AddOutOfOrderIdle() {
+ dump("\nAddOutOfOrderIdle()\n");
+ dump("======================================================================\n");
+
+ dump("\nJS AddOutOfOrderIdle\n");
+ dump("JS NUM OBSERVERS: " + idleServiceObj.observers.length + "\n");
+
+ if (!msg1Count && !msg2Count && !msg3Count) {
+ ResetVars();
+ }
+ currTestCaseNum = tcAddOutOfOrderIdle;
+ cleanUp = false;
+
+ if (!msg1Count && !msg2Count && !msg3Count) {
+ idleServiceObj.idleTime = 3100;
+ }
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ if (!msg1Count && !msg2Count && !msg3Count) {
+ idleServiceObj.testIdleBackService(idleObserversArray[3], "idle");
+ }
+ }
+
+ /*
+ - AddOutOfOrderIdleCleanUp()
+ */
+ function AddOutOfOrderIdleCleanUp() {
+ ok(passed, "Failed test case: AddOutOfOrderIdle()");
+ dump("\nAddOutOfOrderIdleCleanUp()\n");
+ dump("==========================\n");
+ ResetVars();
+ currTestCaseNum = tcAddOutOfOrderIdle;
+ cleanUp = true;
+ idleServiceObj.idleTime = 4100;
+
+ for (var j=1; j<4; j++) {
+ window.navigator.removeIdleObserver(idleObserversArray[j]);
+ }
+ window.navigator.removeIdleObserver(idleObserversArray[3]);
+
+ if (idleServiceObj.observers.length === 1) {
+ passed = true;
+ }
+ else {
+ passed = false;
+ }
+ ok(passed, "Failed test case: AddOutOfOrderIdleCleanUp()");
+ if (AddShiftLocalEnabled) {
+ AddShiftLocal();
+ }
+ else {
+ dump("Finished AddOutOfOrderIdleCleanUp() test.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ * function AddShiftLocal()
+ * - user is idle
+ * - check that local idle timer is shifted correctly
+ * - msg 1 fired when user is idle
+ * - msg 3 fired when 3000
+ * - msg 2 fired immediately when added at 3200 ms
+ * - msg 4 fired by local timer.
+ */
+ function AddShiftLocal()
+ {
+ dump("\n\nTESTING CASE AddShiftLocal\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcAddShiftLocal;
+ idleServiceObj.idleTime = 500;
+
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ window.navigator.addIdleObserver(idleObserversArray[4]);
+
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ /*
+ * function AddShiftLocalCleanUp()
+ */
+ function AddShiftLocalCleanUp()
+ {
+ dump("\n\nTESTING CASE AddShiftLocalCleanUp\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcAddShiftLocal;
+
+ for (var i=1; i<5; i++) {
+ window.navigator.removeIdleObserver(idleObserversArray[i]);
+ }
+ dump("AddShiftLocalCleanUp() done clean up\n");
+ if (AddNewLocalWhileAllIdleEnabled) {
+ AddNewLocalWhileAllIdle();
+ }
+ else {
+ dump("Finished testing AddShiftLocal()\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ * AddNewLocalWhileAllIdle()
+ * - no local idle timer exists because all of the idle observers that were added had a requested
+ * idle time of < curr user idle time and so were fired immediately. No local timer was required.
+ * - now add an idle observer whose requested idle time is > current use idle time and > min idle
+ * requested time in the list of idle observers.
+ */
+ function AddNewLocalWhileAllIdle()
+ {
+ dump("\n\nTESTING CASE AddNewLocalWhileAllIdle\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcAddNewLocalWhileAllIdle;
+ idleServiceObj.idleTime = 500;
+
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function AddNewLocalWhileAllIdleCleanUp()
+ {
+ dump("\n\nTESTING CASE AddNewLocalWhileAllIdleCleanUp\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcAddNewLocalWhileAllIdle;
+
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ window.navigator.removeIdleObserver(idleObserversArray[5]);
+
+ if (ShiftLocalTimerBackEnabled) {
+ ShiftLocalTimerBack();
+ }
+ else {
+ dump("Finished testing TestActiveToActiveNotificationCleanUp()\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ * ShiftLocalTimerBack()
+ * - add a new idle observer whose requested time is > current user idle time
+ * but < the current local timer that has been set.
+ * - the local timer will need to be reset to fire the new msg added.
+ * RESULT
+ * - should print all of them in order
+ */
+ function ShiftLocalTimerBack()
+ {
+ dump("\n\nTESTING CASE ShiftLocalTimerBack()\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcShiftLocalTimerBack;
+ idleServiceObj.idleTime = 2100;
+
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ idleServiceObj.testIdleBackService(idleObserversArray[2], "idle");
+ }
+
+ function ShiftLocalTimerBackCleanUp()
+ {
+ dump("\n\nTESTING CASE ShiftLocalTimerBackCleanUp\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcShiftLocalTimerBack;
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ window.navigator.removeIdleObserver(idleObserversArray[4]);
+ window.navigator.removeIdleObserver(idleObserversArray[5]);
+ dump("ShiftLocalTimerBackCleanUp() done clean up\n");
+
+ if (TestActiveToActiveNotificationEnabled) {
+ TestActiveNotification();
+ }
+ else {
+ dump("Finished testing AddNewLocalWhileAllIdle()\n");
+ SimpleTest.finish();
+ }
+ }
+
+ function TestActiveNotification()
+ {
+ dump("\n\nTESTING CASE TestActiveNotification\n");
+ dump("===============================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcTestActiveToActiveNotification;
+
+ idleServiceObj.idleTime = 500;
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ window.navigator.addIdleObserver(idleObserversArray[5]);
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function TestActiveNotificationCleanUp()
+ {
+ dump("\n\nTESTING CASE TestActiveNotificationCleanUp\n");
+ dump("===============================================\n");
+
+ try {
+ componentMgr.unregisterFactory(idleServiceCID, idleServiceObj);
+ }
+ catch(err) {
+ dump("test_bug715041.xul: ShiftLocalTimerBackCleanUp() Failed to unregister factory, mock idle service!\n");
+ }
+
+ try {
+ componentMgr.registerFactory(oldIdleServiceCID, "Re registering old idle service", idleServiceContractID, oldIdleServiceFactoryObj);
+ }
+ catch(err) {
+ dump("test_bug715041.xul: ShiftLocalTimerBackCleanUp() Failed to register factory, original idle service!\n");
+ }
+
+ SimpleTest.finish();
+ }
+
+ /*
+ * function AddRemoveIdleObserverWithInvalidTime()
+ */
+ function AddRemoveIdleObserverWithInvalidTime()
+ {
+ dump("\n\nTESTING CASE AddRemoveIdleObserverWithInvalidTime()\n");
+ dump("==============\n");
+
+ ResetVars();
+ currTestCaseNum = tcAddRemoveIdleObserverWithInvalidTime;
+
+ //while idle
+ idleServiceObj.idleTime = 2100;
+ var rv = window.navigator.addIdleObserver(idleObserversArray[6]);
+ dump("rv: " + rv + "\n");
+ rv = window.navigator.removeIdleObserver(idleObserversArray[6]);
+
+ idleServiceObj.idleTime = 0;
+ window.navigator.addIdleObserver(idleObserversArray[6]);
+ window.navigator.removeIdleObserver(idleObserversArray[6]);
+
+ SimpleTest.finish();
+ }
+
+ try {
+ var idleServiceCID = Components.ID("287075a6-f968-4516-8043-406c46f503b4");
+ var idleServiceContractID = "@mozilla.org/widget/idleservice;1";
+ var oldIdleService = Components.classes[idleServiceContractID].getService(Components.interfaces.nsIIdleService);
+ }
+ catch(ex) {
+ dump("test_bug715041.xul: 1) Failed to get old idle service.\n");
+ }
+
+ try {
+ // Registering new moch JS idle service
+ var componentMgr = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ }
+ catch(err) {
+ dump("test_bug715041.xul: Failed to query component registrar interface.\n");
+ }
+
+ try {
+ var oldIdleServiceFactoryObj = componentMgr.getClassObjectByContractID(idleServiceContractID, Components.interfaces.nsIFactory);
+ }
+ catch(err) {
+ dump("test_bug715041.xul: Failed to get old idle service.\n");
+ }
+
+ try {
+ var oldIdleServiceCID = componentMgr.contractIDToCID(idleServiceContractID);
+ }
+ catch(err) {
+ dump("test_bug715041.xul: Failed to convert ID to CID for old idle service.\n");
+ }
+
+ try {
+ componentMgr.unregisterFactory(oldIdleServiceCID, oldIdleServiceFactoryObj);
+ }
+ catch(err) {
+ dump("test_bug715041.xul: Failed to unregister old idle service factory object!\n");
+ }
+
+ try {
+ componentMgr.registerFactory(idleServiceCID, "Test Simple Idle/Back Notifications", idleServiceContractID, idleServiceObj);
+ }
+ catch(err) {
+ dump("test_bug715041.xul: Failed to register mock idle service.\n");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(10);
+
+ AddOutOfOrderActiveEnabled = true;
+ AddOutOfOrderIdleEnabled = true;
+ AddNewLocalWhileAllIdleEnabled = true;
+ TestActiveToActiveNotificationEnabled = true;
+ AddShiftLocalEnabled = true;
+ AddIdleObserverWithInvalidTimeEnabled = false;
+
+ SpecialPowers.pushPrefEnv({"set":[['dom.idle-observers-api.fuzz_time.disabled', true]]}, caseZero);
+ ]]>
+ </script>
+ </window>
+
diff --git a/dom/base/test/test_bug715041_removal.xul b/dom/base/test/test_bug715041_removal.xul
new file mode 100644
index 000000000..1a61ed83d
--- /dev/null
+++ b/dom/base/test/test_bug715041_removal.xul
@@ -0,0 +1,841 @@
+<?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=715041
+-->
+ <window title="Mozilla Bug 715041"
+xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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=715041"
+target="_blank">Mozilla Bug 715041</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Mock Idle Service Test for Bug 715041 **/
+ try {
+ var idleServiceCID = Components.ID("6f95d965-4322-4829-8a3c-5dc8a4587f4d");
+ var idleServiceContractID = "@mozilla.org/widget/idleservice;1";
+ var oldIdleService = Components.classes[idleServiceContractID].getService(Components.interfaces.nsIIdleService);
+ }
+ catch (ex) {
+ dump("test_bug715041_removal.xul: failed to get old idle service 1.");
+ }
+
+ //class mock javascript idle service
+ var idleServiceObj = {
+ observers: [],
+ windowObservers: [],
+ idleTimeInMS: 5000, //in milli seconds
+
+ // takes note of the idle observers added as the minimum idle observer
+ //with the idle service
+ timesAdded: [],
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIFactory) ||
+ iid.equals(Components.interfaces.nsIIdleService)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ createInstance: function(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ get idleTime() {
+ return this.idleTimeInMS; //in milli seconds
+ },
+
+ set idleTime(timeInMS) {
+ this.idleTimeInMS = timeInMS;
+ },
+
+ getWindowFromObserver: function(observer) {
+ try {
+ var interfaceRequestor = observer.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ var window = interfaceRequestor.getInterface(Components.interfaces.nsIDOMWindow);
+ return window;
+ }
+ catch (e) {}
+
+ return null;
+ },
+
+ testIdleBackService: function(observer, topic) {
+ dump("\nJS FAKE IDLE SERVICE\n");
+ dump("JS NUM OBSERVERS: " + this.observers.length + "\n");
+
+ if (this.observers.length > 1) {
+ this.observers[1].observer.observe(observer, topic, '\0');
+ dump("JS CALLED OBSERVE FUNCTION!!!\n\n");
+ }
+ },
+
+ addIdleObserver: function(observer, time) {
+ dump("\nJS FAKE IDLE SERVICE add idle observer before\n");
+ dump("JS NUM OBSERVERS: " + this.observers.length + "\n");
+ var window = this.getWindowFromObserver(observer);
+
+ if (window) {
+ this.observers.push({ observer: observer, time: time, });
+ addedIdleObserver = true;
+ numIdleObserversAdded++;
+ this.timesAdded.push(time);
+ dump ("\nMOCK IDLE SERVICE ADDING idle observer with time: " + time + "\n");
+ dump("MOCK IDLE SERVICE: num idle observers added: " + numIdleObserversAdded + "\n\n");
+ }
+ else {
+ dump("SHOULD NEVER GET HERE!");
+ oldIdleService.addIdleObserver(observer, time);
+ addedIdleObserver = false;
+ }
+
+ dump("\nJS FAKE IDLE SERVICE end of add idle observer\n");
+ dump("JS NUM OBSERVERS: " + this.observers.length + "\n");
+ },
+
+ removeIdleObserver: function(observer, time) {
+ dump("\nJS REMOVE IDLE OBSERVER () time to be removed: " + time + "\n");
+ var window = this.getWindowFromObserver(observer);
+ if (!window) {
+ oldIdleService.removeIdleObserver(observer, time);
+ }
+ else {
+ var observerIndex = -1;
+ for (var i=0; i<this.observers.length; i++) {
+ dump("JS removeIdleObserver() observer time: " + this.observers[i].time + "\n");
+ if (this.observers[i].time === time) {
+ observerIndex = i;
+ break;
+ }
+ }
+
+ if (observerIndex != -1 && this.observers.length > 0) {
+ numIdleObserversRemoved++;
+ this.observers.splice(observerIndex, 1);
+ removedIdleObserver = true;
+ dump("MOCK IDLE SERVICE REMOVING idle observer with time " + time + "\n");
+ dump("MOCK IDLE SERVICE numIdleObserversRemoved: " + numIdleObserversRemoved + " numIdleObserversAdded: " + numIdleObserversAdded + "\n\n");
+ }
+ else {
+ removedIdleObserver = false;
+ }
+ }
+ dump("\nJS FAKE IDLE SERVICE end of remove idle observer\n");
+ dump("JS NUM OBSERVERS: " + this.observers.length + "\n");
+ },
+ };
+
+ /** Test for Bug 715041 **/
+ dump("\n\n\nJS STARTING TESTING FOR BUG 715041 REMOVAL\n");
+
+ //bool variables
+ var addedIdleObserver = removedIdleObserver = passed = cleanUp = false;
+
+ //msgXCount
+ var msg0Count = msg1Count = msg2Count = msg3Count = msg4Count = msg5Count =
+ msg6Count = tcZero = currTestCaseNum = prevMsgNum = 0;
+
+ //test case number
+ var tcRemoveHeadIdleObserverWhileActive = 0;
+ var tcRemoveLocalIdleObserverWhileIdle = 1;
+ var tcRemoveHeadIdleObserver = 2;
+ var tcRemoveLocalIdleTimerWhileIdle = 3;
+ var tcRemoveLocalIdleTimerLastElement = 4;
+ var tcRemoveHeadAfterLastLocalFired = 5;
+ var tcRemoveHeadIdleObserverWhileIdleCase1 = 6;
+ var tcRemoveLastAddLast = 7;
+
+ function ResetMsgCounts() {
+ msg0Count = msg1Count = msg2Count = msg3Count = msg4Count = msg5Count =
+ msg6Count = prevMsgNum = 0;
+ }
+
+ function ResetVars() {
+ msg0Count = msg1Count = msg2Count = msg3Count = msg4Count = msg5Count =
+ msg6Count = prevMsgNum = 0;
+
+ numIdleObserversAdded = numIdleObserversRemoved = 0;
+ currTestCaseNum = -1;
+ addedIdleObserver = removedIdleObserver = passed = cleanUp = false;
+ }
+
+ function printVariableValues()
+ {
+ dump("\nfunction printVariableValues()\ncurrTestCaseNum: " + currTestCaseNum +
+ "\ncleanUp: " + cleanUp +
+ "\npassed: " + passed +
+ "\nnumIdleObserversRemoved: " + numIdleObserversRemoved +
+ "\nnumIdleObservesAdded: " + numIdleObserversAdded +
+ "\nmsg1Count " + msg1Count +
+ "\nmsg2Count " + msg2Count +
+ "\nmsg3Count " + msg3Count +
+ "\nmsg4Count " + msg4Count +
+ "\nmsg5Count " + msg5Count +
+ "\n");
+ }
+
+ //Place Holder.
+ var idleHandler0 = function() { dump("msg 0, should never be used!\n"); };
+
+ //idleHandler1
+ function idleHandler1() {
+ msg1Count++;
+ dump("msg 1 Count: " + msg1Count + "\n");
+
+ switch (currTestCaseNum) {
+ case tcRemoveHeadIdleObserver:
+ if (msg1Count === 1 && !msg2Count && !msg3Count && !msg4Count && !msg5Count) {
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+ }
+ break;
+ case tcRemoveLocalIdleObserverWhileIdle:
+ if (msg1Count === 1 && !msg2Count && !msg3Count && !msg4Count) {
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ }
+ break;
+ case tcRemoveLocalIdleTimerWhileIdle:
+ if (msg1Count === 1 && !msg2Count && !msg3Count && !msg4Count) {
+ idleServiceObj.idleTime = 2000;
+ window.navigator.removeIdleObserver(idleObserversArray[3]);
+ }
+ break;
+ case tcRemoveHeadIdleObserverWhileIdleCase1:
+ if (msg1Count === 1 && !msg2Count && !msg3Count && !msg4Count) {
+ for (var i=1; i<4; i++) {
+ window.navigator.addIdleObserver(idleObserversArray[i]);
+ }
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+
+ idleServiceObj.idleTime = 1200;
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+ }
+ break;
+ case tcRemoveLocalIdleTimerLastElement:
+ if (msg1Count === 1 && !msg2Count && !msg3Count && !msg4Count && !msg5Count) {
+ idleServiceObj.idleTime = 1500;
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+ idleServiceObj.idleTime = 1700;
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ idleServiceObj.idleTime = 2000;
+ idleServiceObj.testIdleBackService(idleObserversArray[2], "idle");
+ }
+ break;
+ case tcRemoveHeadAfterLastLocalFired:
+ if (msg1Count === 1 && !msg2Count && !msg3Count && !msg4Count && !msg5Count) {
+ dump("idle handler 1: case tcRemoveHeadAfterLastLocalFired:\n");
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ window.navigator.addIdleObserver(idleObserversArray[4]);
+ }
+ break;
+ case tcRemoveLastAddLast:
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ //idleHandler2
+ function idleHandler2() {
+ msg2Count++;
+ dump("msg 2 Count: " + msg2Count + "\n");
+
+ switch (currTestCaseNum) {
+ case tcRemoveLocalIdleTimerLastElement:
+ if (msg1Count === 1 && msg2Count === 1 && !msg3Count && !msg4Count) {
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ window.navigator.addIdleObserver(idleObserversArray[4]);
+ window.navigator.removeIdleObserver(idleObserversArray[3]);
+ }
+ break;
+ default:
+ //do nothing.
+ break;
+ }
+ }
+
+ //idleHandler3
+ function idleHandler3() {
+ msg3Count++;
+ dump("msg 3 Count: " + msg3Count + "\n");
+ passed = false;
+ switch (currTestCaseNum) {
+ case tcRemoveHeadIdleObserverWhileActive:
+ if (!msg1Count && msg2Count === 1 && msg3Count === 1) {
+ passed = true;
+ }
+ dump("idleHandler3: passed: " + passed + "\n");
+ RemoveHeadIdleObserverWhileActiveCleanUp();
+ break;
+ case tcRemoveHeadIdleObserverWhileIdleCase1:
+ if (msg3Count != 2 && msg3Count != 4) {
+ return;
+ }
+
+ if (msg1Count === 2 && msg2Count === 2 && msg3Count === 2 && !msg4Count) {
+ passed = true;
+ ok(passed, "Failed test case remove head idle observer while idle case 1, part 1.\n");
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "active");
+ return;
+ }
+
+ if (msg1Count === 3 && msg2Count === 4 && msg3Count === 4 &&
+ !msg4Count && !msg5Count) {
+ passed = true;
+ }
+ RemoveHeadIdleObserverWhileIdleCase1CleanUp();
+ break;
+ case tcRemoveLastAddLast:
+ if (msg1Count === 1 && msg2Count === 1 && msg3Count === 1
+ && !msg4Count && !msg5Count) {
+ idleServiceObj.idleTime = 3200;
+ window.navigator.removeIdleObserver(idleObserversArray[3]);
+ idleServiceObj.idleTime = 3500;
+ window.navigator.addIdleObserver(idleObserversArray[4]);
+ return;
+ }
+ break;
+ case tcRemoveHeadAfterLastLocalFired:
+ if (msg3Count === 1) {
+ return;
+ }
+
+ if (msg1Count === 2 && msg2Count === 2 && msg3Count === 2 && msg4Count === 1) {
+ passed = true;
+ }
+ RemoveHeadAfterLastLocalFiredCleanUp();
+ break;
+ default:
+ break;
+ }
+ }
+
+ //idleHandler4
+ function idleHandler4() {
+ msg4Count++;
+ dump("msg 4 Count: " + msg4Count + "\n");
+
+ switch (currTestCaseNum) {
+ case tcRemoveLocalIdleObserverWhileIdle:
+ if (msg1Count === 1 && !msg2Count && msg3Count === 1 && msg4Count === 1) {
+ passed = true;
+ RemoveLocalIdleObserverWhileIdleCleanUp();
+ }
+ break;
+ case tcRemoveHeadIdleObserver:
+ printVariableValues();
+ if (msg1Count === 1 && msg2Count === 1 && msg3Count === 1 && msg4Count === 1) {
+ passed = true;
+ RemoveHeadIdleObserverCleanUp();
+ }
+ break;
+ case tcRemoveLocalIdleTimerWhileIdle:
+ if (msg1Count === 1 && !msg2Count && !msg3Count && msg4Count === 1) {
+ passed = true;
+ RemoveLocalIdleTimerWhileIdleCleanUp();
+ }
+ break
+ case tcRemoveLocalIdleTimerLastElement:
+ if (msg1Count === 1 && msg2Count === 1 && !msg3Count && msg4Count === 1) {
+ passed = true;
+ ok(passed, "Failed test case remove local idle timer last element.\n");
+ RemoveLocalIdleTimerLastElementCleanUp();
+ }
+ break;
+ case tcRemoveHeadAfterLastLocalFired:
+ if (msg1Count === 1 && msg2Count === 1 && msg3Count === 1 && msg4Count === 1) {
+ window.navigator.removeIdleObserver(idleObserversArray[4]);
+ passed = true;
+ ok(passed, "Failed remove head after last local fired.\n");
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "active");
+ }
+ break;
+ case tcRemoveLastAddLast:
+ if (msg1Count === 1 && msg2Count === 1 && msg3Count === 1 && msg4Count == 1) {
+ idleServiceObj.idleTime = 4100;
+ passed = true;
+ RemoveLastAddLastCleanUp();
+ }
+ break;
+ default:
+ //do nothing.
+ break;
+ }
+ }
+
+ //idleHandler5
+ function idleHandler5() {
+ msg5Count++;
+ dump("msg 5 Count: " + msg5Count + "\n");
+
+ switch (currTestCaseNum) {
+
+ default:
+ //do nothing.
+ break;
+ }
+ }
+
+ //idleHandler6
+ function idleHandler6() {
+ dump("msg 6 Count: " + msg6Count + "\n");
+ }
+
+ var idleObserversArray = [];
+ idleObserversArray[0] = {time: 0, onidle: idleHandler0, onactive: idleHandler0};
+ idleObserversArray[1] = {time: 1, onidle: idleHandler1, onactive: idleHandler1};
+ idleObserversArray[2] = {time: 2, onidle: idleHandler2, onactive: idleHandler2};
+ idleObserversArray[3] = {time: 3, onidle: idleHandler3, onactive: idleHandler3};
+ idleObserversArray[4] = {time: 4, onidle: idleHandler4, onactive: idleHandler4};
+ idleObserversArray[5] = {time: 5, onidle: idleHandler5, onactive: idleHandler5};
+ idleObserversArray[6] = {time: 0, onidle: idleHandler6, onactive: idleHandler6};
+
+ //observers array space holder at index zero
+ idleServiceObj.observers.push( {observer: idleObserversArray[0], time: 0, } );
+
+ /*
+ * - function RemoveHeadIdleObserverWhileActive1()
+ * - Remove head idle observer before the head idle notification is fired by the
+ * idle service. I.e. remove the head idle observer while the user is active.
+ * - RESULT: prints 2 in 2ms, 3
+ */
+ function RemoveHeadIdleObserverWhileActive() {
+ dump("\n\nTESTING CASE RemoveHeadIdleObserverWhileActive\n");
+ dump("=================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadIdleObserverWhileActive;
+ idleServiceObj.idleTime = 500;
+
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ dump("test_bug715041_removal.xul: RemoveHeadIdleObserverWhileActive() idle time " + idleServiceObj.idleTime + "\n");
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+
+ idleServiceObj.idleTime = 800;
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[2], "idle");
+ }
+
+ function RemoveHeadIdleObserverWhileActiveCleanUp() {
+ dump("\nRemoveHeadIdleObserverWhileActiveCleanUp()\n");
+ dump("=====================================\n");
+
+ dump("Passed: " + passed + "\n");
+ ok(passed, "Failed test case: RemoveHeadIdleObserverWhileActive");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadIdleObserverWhileActive;
+ idleServiceObj.idleTime = 3500;
+
+ for (var i=2; i<4; i++) {
+ window.navigator.removeIdleObserver(idleObserversArray[i]);
+ }
+
+ dump("JS RemoveHeadIdleObserverWhileActiveCleanUp() DONE\n");
+ if (RemoveLocalIdleObserverWhileIdleEnabled) {
+ RemoveLocalIdleObserverWhileIdle();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ * function RemoveLocalIdleObserverWhileIdle()
+ * Remove local observer before the local oberver at index 1 is triggered while
+ * the user is idle.
+ * RESULT: should print 1, 3, 4
+ */
+ function RemoveLocalIdleObserverWhileIdle() {
+ dump("\n\nTESTING CASE RemoveLocalIdleObserverWhileIdle\n");
+ dump("=================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveLocalIdleObserverWhileIdle;
+ idleServiceObj.idleTime = 500;
+
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ window.navigator.addIdleObserver(idleObserversArray[4]);
+
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function RemoveLocalIdleObserverWhileIdleCleanUp() {
+ dump("\nRemoveLocalIdleObserverWhileIdleCleanUp()\n");
+ dump("=====================================\n");
+
+ ok(passed, "Failed test case: RemoveLocalIdleObserverWhileIdleCleanUp()");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadIdleObserverWhileActive;
+ idleServiceObj.idleTime = 3500;
+
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+ window.navigator.removeIdleObserver(idleObserversArray[3]);
+ window.navigator.removeIdleObserver(idleObserversArray[4]);
+
+ dump("JS RemoveLocalIdleObserverWhileIdleCleanUp() DONE\n");
+ if (RemoveHeadIdleObserverEnabled) {
+ RemoveHeadIdleObserver();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+
+ /*
+ * function RemoveHeadIdleObserver()
+ * Remove head idle observer while the user has been idle for 2400 ms.
+ * - RESULT: prints 1, 2, remove 2, 3, 4
+ */
+ function RemoveHeadIdleObserver() {
+ dump("\n\nTESTING CASE RemoveHeadIdleObserver\n");
+ dump("=================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadIdleObserver;
+ idleServiceObj.idleTime = 500;
+
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ window.navigator.addIdleObserver(idleObserversArray[2]);
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ window.navigator.addIdleObserver(idleObserversArray[4]);
+
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function RemoveHeadIdleObserverCleanUp() {
+ dump("\nRemoveHeadIdleObserverCleanUp()\n");
+ dump("=====================================\n");
+
+ ok(passed, "Failed test case: RemoveHeadIdleObserverCleanUp()");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadIdleObserver;
+ idleServiceObj.idleTime = 3500;
+
+ for (var i=2; i<5; i++) {
+ window.navigator.removeIdleObserver(idleObserversArray[i]);
+ }
+
+ dump("JS RemoveHeadIdleObserverCleanUp() DONE\n");
+ if (RemoveLocalIdleTimerWhileIdleEnabled) {
+ RemoveLocalIdleTimerWhileIdle();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ * RemoveLocalIdleTimerWhileIdle()
+ * - Removes the idle observer that is also set as the current local idle timer callback
+ * local idle observer being removed is NOT at index 1!
+ * - RESULT: should trigger 1 in 1ms and 4 in 4ms
+ */
+ function RemoveLocalIdleTimerWhileIdle()
+ {
+ dump("\n\nTESTING CASE RemoveLocalIdleTimerWhileIdle\n");
+ dump("=================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveLocalIdleTimerWhileIdle;
+ idleServiceObj.idleTime = 500;
+
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ window.navigator.addIdleObserver(idleObserversArray[3]);
+ window.navigator.addIdleObserver(idleObserversArray[4]);
+
+ idleServiceObj.idleTime = 1000;
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function RemoveLocalIdleTimerWhileIdleCleanUp()
+ {
+ dump("\nRemoveLocalIdleTimerWhileIdleCleanUp()\n");
+ dump("=====================================\n");
+
+ ok(passed, "Failed test case: RemoveLocalIdleTimerWhileIdleCleanUp()");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveLocalIdleTimerWhileIdle;
+
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+ window.navigator.removeIdleObserver(idleObserversArray[4]);
+
+ dump("JS RemoveLocalIdleTimerWhileIdleCleanUp() DONE\n");
+ if (RemoveLocalIdleTimerLastElementEnabled) {
+ RemoveLocalIdleTimerLastElement();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ * function RemoveLocalIdleTimerLastElement()
+ */
+ function RemoveLocalIdleTimerLastElement()
+ {
+ dump("\n\nTESTING CASE RemoveLocalIdleTimerLastElement\n");
+ dump("=================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveLocalIdleTimerLastElement;
+ idleServiceObj.idleTime = 1200;
+
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function RemoveLocalIdleTimerLastElementCleanUp() {
+ dump("\nRemoveLocalIdleTimerLastElementCleanUp()\n");
+ dump("=====================================\n");
+
+ ok(passed, "Failed test case: RemoveLocalIdleTimerLastElementCleanUp()");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveLocalIdleTimerLastElement;
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ window.navigator.removeIdleObserver(idleObserversArray[4]);
+
+ dump("JS RemoveLocalIdleTimerLastElementCleanUp() DONE\n");
+ if (RemoveHeadAfterLastLocalFiredEnabled) {
+ RemoveHeadAfterLastLocalFired();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ - Remove the head after the last local idle timer has been fired
+ - add 1 2 3 4
+ - after 4 has been notified, removed idle observer with time 4
+ - send a back topic
+ - message notification should be 1, 2, 3.
+ */
+ function RemoveHeadAfterLastLocalFired()
+ {
+ dump("\n\nTESTING CASE RemoveHeadAfterLastLocalFired\n");
+ dump("=================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadAfterLastLocalFired;
+ idleServiceObj.idleTime = 1200;
+
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function RemoveHeadAfterLastLocalFiredCleanUp() {
+ dump("\RemoveHeadAfterLastLocalFiredCleanUp()\n");
+ dump("=====================================\n");
+
+ ok(passed, "Failed test case: RemoveHeadAfterLastLocalFiredCleanUp()");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadAfterLastLocalFired;
+
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ window.navigator.removeIdleObserver(idleObserversArray[3]);
+
+ dump("JS RemoveHeadAfterLastLocalFiredCleanUp() DONE\n");
+ if (RemoveHeadIdleObserverWhileIdleCase1Enabled) {
+ RemoveHeadIdleObserverWhileIdleCase1();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ function RemoveHeadIdleObserverWhileIdleCase1() {
+ dump("\n\nTESTING CASE RemoveHeadIdleObserverWhileIdleCase1\n");
+ dump("=================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadIdleObserverWhileIdleCase1;
+ idleServiceObj.idleTime = 1000;
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function RemoveHeadIdleObserverWhileIdleCase1CleanUp() {
+ dump("\nRemoveHeadIdleObserverWhileIdleCase1CleanUp()\n");
+ dump("=====================================\n");
+
+ ok(passed, "Failed test case: RemoveHeadIdleObserverWhileIdleCase1CleanUp()");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveHeadIdleObserverWhileIdleCase1;
+
+ for (var i=1; i<4; i++) {
+ window.navigator.removeIdleObserver(idleObserversArray[i]);
+ }
+
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ window.navigator.removeIdleObserver(idleObserversArray[3]);
+
+ dump("JS RemoveHeadIdleObserverWhileIdleCase1CleanUp() DONE\n");
+ if (RemoveLastAddLastEnabled) {
+ RemoveLastAddLast();
+ }
+ else {
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+ }
+
+ /*
+ * - RemoveLastAddLast()
+ *
+ * - User is currently idle.
+ * - Add callback 1, 2, 3,
+ * - Remove callback 3 after 3200 MS. I.e. after callback 3 has been notified.
+ * - Add callback 4 after 3500 MS
+ * - Output: 1, 2, 3, 4
+ */
+ function RemoveLastAddLast()
+ {
+ dump("\n\nTESTING CASE RemoveLastAddLast()\n");
+ dump("=================================\n");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveLastAddLast;
+ idleServiceObj.idleTime = 1000;
+ window.navigator.addIdleObserver(idleObserversArray[1]);
+ idleServiceObj.testIdleBackService(idleObserversArray[1], "idle");
+ }
+
+ function RemoveLastAddLastCleanUp()
+ {
+ dump("\RemoveLastAddLastCleanUp()\n");
+ dump("=====================================\n");
+
+ ok(passed, "Failed test case: RemoveLastAddLastCleanUp()");
+
+ ResetVars();
+ currTestCaseNum = tcRemoveLastAddLast;
+
+ window.navigator.removeIdleObserver(idleObserversArray[1]);
+ window.navigator.removeIdleObserver(idleObserversArray[2]);
+ window.navigator.removeIdleObserver(idleObserversArray[4]);
+
+ if (numIdleObserversRemoved === 1 && !numIdleObserversAdded) {
+ ok(true, "Failed test case: RemoveLastAddLastCleanUp()");
+ }
+ else {
+ ok(false, "Failed test case: RemoveLastAddLastCleanUp()");
+ }
+
+
+ try {
+ componentMgr.unregisterFactory(idleServiceCID, idleServiceObj);
+ }
+ catch(err) {
+ dump("test_bug715041_removal.xul: RemoveLastAddLastCleanUp() Failed to unregister factory, mock idle service!\n");
+ }
+
+ try {
+ componentMgr.registerFactory(oldIdleServiceCID, "Re registering old idle service", idleServiceContractID, oldIdleServiceFactoryObj);
+ }
+ catch(err) {
+ dump("test_bug715041_removal.xul: RemoveLastAddLastCleanUp() Failed to register factory, original idle service!\n");
+ }
+
+ dump("JS RemoveLastAddLastCleanUp() DONE\n");
+ dump("Finishing testing idle API.\n");
+ SimpleTest.finish();
+ }
+
+
+ // Registering new moch JS idle service
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestLongerTimeout(10);
+
+ try {
+ var idleServiceCID = Components.ID("0fdc1bbf-3868-4660-9855-0c2e376922bc");
+ var idleServiceContractID = "@mozilla.org/widget/idleservice;1";
+ var oldIdleService = Components.classes[idleServiceContractID].getService(Components.interfaces.nsIIdleService);
+ }
+ catch(ex) {
+ dump("test_bug715041_removal.xul: 1) Failed to get old idle service.\n");
+ }
+
+ try {
+ // Registering new moch JS idle service
+ var componentMgr = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ }
+ catch(err) {
+ dump("test_bug715041_removal.xul: Failed to query component registrar interface.\n");
+ }
+
+ try {
+ var oldIdleServiceFactoryObj = componentMgr.getClassObjectByContractID(idleServiceContractID, Components.interfaces.nsIFactory);
+ }
+ catch(err) {
+ dump("test_bug715041_removal.xul: Failed to get old idle service.\n");
+ }
+
+ try {
+ var oldIdleServiceCID = componentMgr.contractIDToCID(idleServiceContractID);
+ }
+ catch(err) {
+ dump("test_bug715041._removalxul: Failed to convert ID to CID for old idle service.\n");
+ }
+
+ try {
+ componentMgr.unregisterFactory(oldIdleServiceCID, oldIdleServiceFactoryObj);
+ }
+ catch(err) {
+ dump("test_bug715041_removal.xul: Failed to unregister old idle service factory object!\n");
+ }
+
+ try {
+ componentMgr.registerFactory(idleServiceCID, "Test Simple Idle/Back Notifications", idleServiceContractID, idleServiceObj);
+ }
+ catch(err) {
+ dump("test_bug715041_removal.xul: Failed to register mock idle service.\n");
+ }
+
+ //test case enabled
+ var RemoveLocalIdleObserverWhileIdleEnabled = true;
+ var RemoveHeadIdleObserverEnabled = true;
+ var RemoveLocalIdleTimerWhileIdleEnabled = true;
+ var RemoveLocalIdleTimerLastElementEnabled = true;
+ var RemoveHeadAfterLastLocalFiredEnabled = true;
+ var RemoveHeadIdleObserverWhileIdleCase1Enabled = true;
+ var RemoveLastAddLastEnabled = true;
+ SpecialPowers.pushPrefEnv({"set":[['dom.idle-observers-api.fuzz_time.disabled', true]]}, RemoveHeadIdleObserverWhileActive);
+]]>
+ </script>
+ </window>
+
diff --git a/dom/base/test/test_bug719533.html b/dom/base/test/test_bug719533.html
new file mode 100644
index 000000000..4154b3bdb
--- /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 000000000..97db67755
--- /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 type="application/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=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 000000000..008ad3bc0
--- /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 000000000..9a24a70b9
--- /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 000000000..9a17ab06e
--- /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 000000000..4fa00fba9
--- /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 type="application/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=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 000000000..4376b7447
--- /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 type="application/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=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 000000000..565588f41
--- /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 type="application/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=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 000000000..29044a013
--- /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 type="application/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=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 000000000..91da66877
--- /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 type="application/javascript" 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 = "data:text/html;charset=utf-8,\\u003Cscript>parent.pass();\\u003C/script>"; 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 000000000..28991e52f
--- /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 type="application/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=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 000000000..6a5cc723e
--- /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 type="application/javascript" 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 000000000..9bc3d0e84
--- /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 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=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;version=1.7">
+SimpleTest.waitForExplicitFinish();
+
+var url = "file_bug782342.txt";
+var xhr = new XMLHttpRequest();
+xhr.open("GET", url, true);
+xhr.responseType = "blob";
+
+function checkHeaders(xhr) {
+ var headers = xhr.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 = xhr.getResponseHeader('content-type');
+ ok(ct.length > 0, 'Get Response Header - content type: ' + ct);
+ var cl = xhr.getResponseHeader('content-length');
+ ok(cl.length > 0, '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();
+ }, false);
+ xhrBlob.send();
+ }
+}, false);
+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 000000000..998701d55
--- /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 type="application/javascript" 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 000000000..e3081f36e
--- /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 type="application/javascript" 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 000000000..6cf30a7a4
--- /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 type="application/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=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_bug793311.html b/dom/base/test/test_bug793311.html
new file mode 100644
index 000000000..5c1e8e7d3
--- /dev/null
+++ b/dom/base/test/test_bug793311.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=793311
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 793311</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug {793311} **/
+ SimpleTest.waitForExplicitFinish();
+
+ try {
+ SpecialPowers.DOMWindowUtils.wrapDOMFile(null);
+ ok(false, "wrapDOMFile(null) throws an exception");
+ } catch(e) {
+ ok(true, "wrapDOMFile(null) throws an exception");
+ }
+ SimpleTest.finish();
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=793311">Mozilla Bug 793311</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_bug804395.html b/dom/base/test/test_bug804395.html
new file mode 100644
index 000000000..58e5fcf38
--- /dev/null
+++ b/dom/base/test/test_bug804395.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=804395
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 804395</title>
+ <script type="application/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=804395">Mozilla Bug 804395</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+function test200() {
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ xhr.open('GET', 'jar:http://example.org/tests/dom/base/test/file_bug804395.jar!/foo.bar', true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ ok(xhr.status == 200, "Existing file must have Status 200!");
+ runTests();
+ }
+ }
+ xhr.send(null);
+}
+
+function test404() {
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ xhr.open('GET', 'jar:http://example.org/tests/dom/base/test/file_bug804395.jar!/foo.do_not_exist', true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ ok(xhr.status == 404, "Non existing file must have Status 404!");
+ runTests();
+ }
+ }
+ xhr.send(null);
+}
+
+function test0() {
+ var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+ xhr.open('GET', 'jar:http://example.org/tests/dom/base/test/file_bug804395.jar!/foo.bar', true);
+ ok(xhr.status == 0, "Not Sent request must have status 0");
+ runTests();
+}
+
+var tests = [ test200, test404, test0 ];
+function runTests() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+/** Test for Bug 804395 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, function() {
+ SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTests);
+ });
+});
+</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 000000000..06515b178
--- /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 type="application/javascript" 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 000000000..2aa659b80
--- /dev/null
+++ b/dom/base/test/test_bug810494.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=810494
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 810494</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" 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;version=1.8">
+
+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");
+ ok(obj.pluginFallbackType == OBJLC.PLUGIN_ALTERNATE,
+ "expected alternate fallback mode");
+}
+
+// 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 000000000..70c5abe0b
--- /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 type="application/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=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 000000000..10e11013d
--- /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 type="application/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=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 000000000..5afa50444
--- /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 type="application/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=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 000000000..8ed47a5b8
--- /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 type="application/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=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 000000000..bb4f15204
--- /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 type="text/javascript" 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 000000000..21ecb2f04
--- /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 type="application/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=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_bug840098.html b/dom/base/test/test_bug840098.html
new file mode 100644
index 000000000..8eaceb589
--- /dev/null
+++ b/dom/base/test/test_bug840098.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=840098
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 840098</title>
+ <script type="application/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=840098">Mozilla Bug 840098</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div id="foo"></div>
+</div>
+<marquee id="m">Hello</marquee>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 840098 **/
+
+var mar = document.getElementById("m");
+var anonymousNode = SpecialPowers.wrap(document).getAnonymousNodes(mar)[0];
+try {
+ SpecialPowers.wrap(document).implementation.createDocument("", "", null).adoptNode(anonymousNode);
+ ok(false, "shouldn't be able to adopt the root of an anonymous subtree");
+} catch (e) {
+ is(e.name, "NotSupportedError", "threw the correct type of error");
+}
+
+</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 000000000..37504353f
--- /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 type="application/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=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 000000000..0d189004b
--- /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 type="application/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=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 000000000..71ecea55b
--- /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 type="application/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=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 000000000..a93200a1b
--- /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 type="application/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=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 000000000..947166030
--- /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 type="application/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=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 000000000..bb5a6a02a
--- /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 type="application/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=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_bug890580.html b/dom/base/test/test_bug890580.html
new file mode 100644
index 000000000..808a4a868
--- /dev/null
+++ b/dom/base/test/test_bug890580.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=890580
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 883129</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function testPaintextSerializerWithPlaceHolder() {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ const Cc = SpecialPowers.Cc;
+
+ // Create a plaintext encoder with the flag OutputNonTextContentAsPlaceholder.
+ var encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
+ .createInstance(de);
+ var flags = de.OutputRaw |
+ de.OutputNonTextContentAsPlaceholder;
+ encoder.init(document, "text/plain", flags);
+
+ function toPlaintext(id) {
+ var element = document.getElementById(id);
+ var range = document.createRange();
+ range.selectNodeContents(element);
+ encoder.setRange(range);
+ return encoder.encodeToString();
+ }
+
+ // The follows are test cases.
+ is(toPlaintext("case1"), "This is a button. Hello!", "test with <button>");
+ is(toPlaintext("case2"), "There is an\uFFFCimage.", "test with <img>");
+ is(toPlaintext("case3"), "\uFFFC with text.", "test with <input>");
+ is(toPlaintext("case4"), "There is an\uFFFCimage and a \uFFFC.",
+ "test with <img> and <input>");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testPaintextSerializerWithPlaceHolder);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=890580">Mozilla Bug 890580</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <span id="case1"><button id="b">This is a button.</button> Hello!</span>
+ <span id="case2">There is an<img>image.</span>
+ <span id="case3"><input type="button" value="Input button"> with text.</span>
+ <span id="case4" contenteditable="true">There is an<img>image and
+ a <input type="button" value="button">.</span>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_bug891952.html b/dom/base/test/test_bug891952.html
new file mode 100644
index 000000000..471e72712
--- /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 type="application/javascript" 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 000000000..f9a0c4fce
--- /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 type="application/javascript" 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_bug895239.html b/dom/base/test/test_bug895239.html
new file mode 100644
index 000000000..32c0717e5
--- /dev/null
+++ b/dom/base/test/test_bug895239.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=895239
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 895239</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function testPaintextSerializerWithPlaceHolder() {
+
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ const Cc = SpecialPowers.Cc;
+
+ // Create a plaintext encoder with the flag OutputNonTextContentAsPlaceholder.
+ var encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
+ .createInstance(de);
+ var flags = de.OutputRaw |
+ de.OutputNonTextContentAsPlaceholder;
+ encoder.init(document, "text/plain", flags);
+
+ function toPlaintext(id) {
+ var element = document.getElementById(id);
+ var range = document.createRange();
+ range.selectNodeContents(element);
+ encoder.setRange(range);
+ return encoder.encodeToString();
+ }
+
+ // Test cases to serialize all nodes including invisible nodes.
+ is(toPlaintext("case1"), "This is an audio \uFFFC! ", "test with <audio>");
+ is(toPlaintext("case2"), "This is a canvas \uFFFC! ", "test with <canvas>");
+ is(toPlaintext("case3"), "This is an iframe \uFFFC! ", "test with one <iframe>");
+ is(toPlaintext("case4"), "One iframe \uFFFC with another iframe \uFFFC. ", "test with two <iframes>");
+ is(toPlaintext("case5"), "This is a meter \uFFFC! ", "test with <meter>");
+ is(toPlaintext("case6"), "This is a progress \uFFFC! ", "test with <progress>");
+ is(toPlaintext("case7"), "This is an object \uFFFC! ", "test with <object>");
+ is(toPlaintext("case8"), "This is a svg \uFFFC! ", "test with <svg>");
+ is(toPlaintext("case9"), "This is a video \uFFFC! ", "test with <video>");
+ is(toPlaintext("case10"), "This is a video \uFFFC! ", "test with nested tags");
+
+ // Test cases to serialize visible nodes only.
+ encoder.init(document, "text/plain", flags | de.SkipInvisibleContent);
+ is(toPlaintext("case1"), "This is an audio \uFFFC! ", "test with <audio> for visible nodes");
+ is(toPlaintext("case2"), "This is a canvas \uFFFC! ", "test with <canvas> for visible nodes");
+ is(toPlaintext("case3"), "This is an iframe \uFFFC! ", "test with one <iframe> for visible nodes");
+ is(toPlaintext("case4"), "One iframe \uFFFC with another iframe . ", "test with two <iframes> for visible nodes");
+ is(toPlaintext("case5"), "This is a meter \uFFFC! ", "test with <meter> for visible nodes");
+ is(toPlaintext("case6"), "This is a progress \uFFFC! ", "test with <progress> for visible nodes");
+ is(toPlaintext("case7"), "This is an object \uFFFC! ", "test with <object> for visible nodes");
+ is(toPlaintext("case8"), "This is a svg \uFFFC! ", "test with <svg> for visible nodes");
+ is(toPlaintext("case9"), "This is a video \uFFFC! ", "test with <video> for visible nodes");
+ is(toPlaintext("case10"), "This is a video \uFFFC! ", "test with nested tags for visible nodes");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testPaintextSerializerWithPlaceHolder);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=895239">Mozilla Bug 895239</a>
+<p id="display"></p>
+<div id="content">
+ <span id="case1">This is an audio
+ <audio controls="controls">
+ Your browser does not support <code>audio</code> element.
+ </audio>!
+ </span>
+ <span id="case2">This is a canvas
+ <canvas height="100" width="100">
+ Your browser does not support canvas element.
+ </canvas>!
+ </span>
+ <span id="case3">This is an iframe
+ <iframe src="about:blank">
+ Your browser does not support iframes.
+ </iframe>!
+ </span>
+ <span id="case4">One iframe
+ <iframe src="about:blank">
+ Your browser does not support iframes.
+ </iframe> with another iframe
+ <iframe src="about:blank" style="display: none"></iframe>.
+ </span>
+ <span id="case5">This is a meter
+ <meter min="0" max="100" value="50">
+ 50%
+ </meter>!
+ </span>
+ <span id="case6">This is a progress
+ <progress max="100" value="70">
+ 70%
+ </progress>!
+ </span>
+ <span id="case7">This is an object
+ <object type="application/x-shockware-flash">
+ <a href="#">Download the plugin.</a>
+ </object>!
+ </span>
+ <span id="case8">This is a svg
+ <svg height="100" width="100">
+ Your browser does not support svg.
+ <circle cx="100" cy="100" r="80" fill="green"></circle>
+ </svg>!
+ </span>
+ <span id="case9">This is a video
+ <video>
+ Your browser does not support videos.
+ </video>!
+ </span>
+ <span id="case10">This is a video
+ <video>
+ Your browser does not support videos.<iframe src="about:blank"></iframe>
+ </video>!
+ </span>
+</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 000000000..25499d5a2
--- /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 type="application/javascript" 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_bug902847.html b/dom/base/test/test_bug902847.html
new file mode 100644
index 000000000..b8ab1bfc4
--- /dev/null
+++ b/dom/base/test/test_bug902847.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=902847
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 902847</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function testPaintextSerializerWithPlaceHolder() {
+
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ const Cc = SpecialPowers.Cc;
+
+ // Create a plaintext encoder.
+ var encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
+ .createInstance(de);
+ var flags = de.OutputRaw |
+ de.OutputLFLineBreak |
+ de.OutputDontRemoveLineEndingSpaces;
+ encoder.init(document, "text/plain", flags);
+
+ function toPlaintext(id) {
+ var element = document.getElementById(id);
+ var range = document.createRange();
+ range.selectNodeContents(element);
+ encoder.setRange(range);
+ return encoder.encodeToString().replace(/\n/g, '\\n')
+ .replace(/\r/g, '\\r');
+ }
+
+ // Test cases.
+ is(toPlaintext("case1"), "Hello \\nboy!", "Case 1 failed.");
+ is(toPlaintext("case2"), "Hello \\nboy!", "Case 2 failed.");
+ is(toPlaintext("case3"), "Hello \\nboy!", "Case 3 failed.");
+ is(toPlaintext("case4"), "Hello \\nboy!", "Case 4 failed.");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(testPaintextSerializerWithPlaceHolder);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=902847">Mozilla Bug 902847</a>
+<p id="display"></p>
+<div id="content">
+ <p id="case1">Hello <br>boy!</p>
+ <p id="case2">Hello <br>boy!</p>
+ <p id="case3">Hello&nbsp;<br>boy!</p>
+ <p id="case4">Hello&nbsp;&nbsp;<br>boy!</p>
+</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 000000000..a62cf90a1
--- /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 type="application/javascript" 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 000000000..bfa05b150
--- /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 type="application/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=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 (event) {
+ 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 000000000..b04c0c675
--- /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 type="application/javascript" 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 c = document.createElement(tag);
+ testInnerHTML(c, "abc", "abc");
+ testInnerHTML(c, "<td>", "<tr><td></td></tr>");
+ testInnerHTML(c, "</body>", "");
+ testInnerHTML(c, "<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 000000000..0a2868171
--- /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 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=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 000000000..aa4bf7c3e
--- /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 type="application/javascript" 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;version=1.7">
+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, null, null));
+ 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 000000000..8b870d899
--- /dev/null
+++ b/dom/base/test/test_bug962251.html
@@ -0,0 +1,258 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=962251
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 962251</title>
+ <script type="application/javascript" 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 onBlur(aEvent) {
+ button1.removeEventListener("blur", onBlur);
+ 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;
+ });
+
+ button2.addEventListener("focus", function onFocus(aEvent) {
+ button2.removeEventListener("focus", onFocus);
+ 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();
+ });
+
+ button2.addEventListener("blur", function onBlur(aEvent) {
+ button2.removeEventListener("blur", onBlur);
+ is(aEvent.target.id, "button2", "Button2 should lose focus.");
+ ok(aEvent.relatedTarget === null, "The relatedTarget should be null.");
+ runTests();
+ });
+
+ button2.focus();
+ }
+
+ function Test_ElementsInDifferentDocument() {
+ var button2 = document.getElementById("button2");
+ button2.focus();
+ button2.addEventListener("blur", function onBlur(aEvent) {
+ button2.removeEventListener("blur", onBlur);
+ is(aEvent.target.id, "button2", "Button2 should lose focus.");
+ ok(aEvent.relatedTarget === null, "The relatedTarget should be null, since it's in another document.");
+ });
+
+ var iframe = document.createElement("iframe");
+ iframe.id = "iframe";
+ iframe.src = "iframe_bug962251.html";
+ window.addEventListener("message", function onMessage(aEvent) {
+ window.removeEventListener("message", onMessage);
+ if (aEvent.data == "runNextTest") {
+ runTests();
+ }
+ });
+ 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 onBlur(aEvent) {
+ button1.removeEventListener("blur", onBlur);
+ info("button1 blur");
+ is(aEvent.relatedTarget.id, button2.id, "relatedTarget.id should be button2.");
+ button3.focus();
+ });
+ button1.addEventListener("focus", function onFocus(aEvent) {
+ button1.removeEventListener("focus", onFocus);
+ info("button1 focus");
+ });
+
+ button2.addEventListener("blur", function onBlur(aEvent) {
+ button2.removeEventListener("blur", onBlur);
+ info("button2 blur");
+ });
+ button2.addEventListener("focus", function onFocus(aEvent) {
+ button2.removeEventListener("focus", onFocus);
+ info("button2 focus");
+ });
+
+ button3.addEventListener("blur", function onBlur(aEvent) {
+ button3.removeEventListener("blur", onBlur);
+ info("button3 blur");
+ });
+ button3.addEventListener("focus", function onFocus(aEvent) {
+ button3.removeEventListener("focus", onFocus);
+ info("button3 focus");
+ ok(aEvent.relatedTarget === null, "aEvent.relatedTarget is null.");
+ runTests();
+ });
+
+ 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 onFocus(aEvent) {
+ iframe.contentWindow.removeEventListener("focus", onFocus, true);
+ ok(aEvent.target === input1, "Input1 is focused.");
+ ok(aEvent.relatedTarget === input, "relatedTarget should be input.");
+ runTests();
+ }, true);
+ iframe.contentWindow.addEventListener("blur", function onBlur(aEvent) {
+ iframe.contentWindow.removeEventListener("blur", onBlur, true);
+ ok(aEvent.target === input, "Input is not focused.");
+ ok(aEvent.relatedTarget === input1, "relatedTarget should be input1.");
+ }, 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 onBlur(aEvent) {
+ iframe.contentWindow.removeEventListener("blur", onBlur, true);
+ ok(aEvent.target === input1, "Input1 is not focused.");
+ ok(aEvent.relatedTarget === null, "relatedTarget should be null.");
+ runTests();
+ }, 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 000000000..d028f70d8
--- /dev/null
+++ b/dom/base/test/test_bug976673.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=976673
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 976673</title>
+ <script type="application/javascript" 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=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 nsContentIterator.
+SimpleTest.expectAssertions(0, 6);
+
+window.addEventListener("mousedown", function (aEvent) { aEvent.preventDefault(); }, false);
+
+function testSetFocus(aEventType, aCallback)
+{
+ var description = "Setting focus from " + aEventType + " handler: ";
+
+ var iframe = document.getElementById("iframe");
+ iframe.contentWindow.focus();
+
+ window.addEventListener("message", function (aEvent) {
+ window.removeEventListener("message", arguments.callee, false);
+ 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 (aEvent) {
+ movingFocus = true;
+ input.focus();
+ aEvent.preventDefault();
+ button.removeEventListener(aEventType, arguments.callee, true);
+ }, true);
+
+ synthesizeMouseAtCenter(button, {});
+
+ window.addEventListener("message", function (aEvent) {
+ window.removeEventListener("message", arguments.callee, false);
+ if (movingFocus) {
+ is(aEvent.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(aEvent.data, "input-value: focus",
+ description + "<input> in the iframe should keep having focus");
+ }
+
+ setTimeout(aCallback, 0);
+ }, false);
+
+ iframe.contentWindow.postMessage("check", "*");
+ }, false);
+
+ 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 000000000..17121d4e2
--- /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 type="application/javascript" 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 000000000..6f5cd7a00
--- /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 type="application/javascript" 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 000000000..50f12b1ef
--- /dev/null
+++ b/dom/base/test/test_caretPositionFromPoint.html
@@ -0,0 +1,123 @@
+<!doctype html>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=654352
+-->
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script type="text/javascript" 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 + 5),
+ Math.round(test6Rect.top + (test6Rect.height / 2)),
+ 0, "test6");
+ checkOffsetsFromPoint(Math.round(test6Rect.left + test6Rect.width - 30),
+ Math.round(test6Rect.top + (test6Rect.height / 2)),
+ 5, "test6");
+
+ 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>
+</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 000000000..76816f01f
--- /dev/null
+++ b/dom/base/test/test_change_policy.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test policies for Bug 1101288</title>
+ <script type="application/javascript" 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;version=1.7">
+
+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 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. Be sure to yield so we don't call this twice.
+ yield SimpleTest.finish();
+})();
+
+</script>
+</head>
+
+<body onload="tests.next();">
+ <iframe id="testframe"></iframe>
+
+</body>
+</html>
+
diff --git a/dom/base/test/test_classList.html b/dom/base/test/test_classList.html
new file mode 100644
index 000000000..2a108d7f5
--- /dev/null
+++ b/dom/base/test/test_classList.html
@@ -0,0 +1,426 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=501257
+-->
+<head>
+ <title>Test for the classList element attribute</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="http://www.whatwg.org/specs/web-apps/current-work/#dom-classlist">classList DOM attribute</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 501257 **/
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const SVG_NS = "http://www.w3.org/2000/svg";
+const XHTML_NS = "http://www.w3.org/1999/xhtml"
+const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+
+var gMutationEvents = [];
+
+function onAttrModified(event) {
+ is(event.attrName, "class", "mutation on unexpected attribute");
+
+ gMutationEvents.push({
+ attrChange: event.attrChange,
+ prevValue: event.prevValue,
+ newValue: event.newValue,
+ });
+}
+
+function checkModification(e, funcName, args, expectedRes, before, after, expectedException) {
+ if (!Array.isArray(args)) {
+ args = [args];
+ }
+
+ var shouldThrow = typeof(expectedException) === "string";
+ if (shouldThrow) {
+ // If an exception is thrown, the class attribute shouldn't change.
+ after = before;
+ }
+ if (before === null)
+ e.removeAttribute("class");
+ else
+ e.setAttribute("class", before);
+
+ var contextMsg = "(checkModification: funcName=" + funcName + ",args=" +
+ JSON.stringify(args) + ",expectedRes=" + expectedRes +
+ ",before=" + before + ",after=" + after + ")";
+
+ gMutationEvents = [];
+ e.addEventListener("DOMAttrModified", onAttrModified, false);
+ try {
+ var list = e.classList;
+ var res = list[funcName].apply(list, args);
+ if (shouldThrow)
+ ok(false, "classList modification didn't throw " + contextMsg);
+ } catch (e) {
+ if (!shouldThrow)
+ ok(false, "classList modification threw an exception " + contextMsg);
+ is(e.name, expectedException, "wrong exception thrown " + contextMsg);
+ }
+ e.removeEventListener("DOMAttrModified", onAttrModified, false);
+ if (expectedRes !== null)
+ is(res, expectedRes, "wrong return value from " + funcName +
+ " " + contextMsg);
+
+ var expectedAfter = after;
+ // XUL returns an empty string when getting a nonexistent class attribute.
+ if (e.namespaceURI == XUL_NS && expectedAfter === null)
+ expectedAfter = "";
+
+ is(e.getAttribute("class"), expectedAfter, "wrong class after modification " +
+ contextMsg);
+ var expectedMutation = before != after;
+ is(gMutationEvents.length, expectedMutation ? 1 : 0,
+ "unexpected mutation event count " + contextMsg);
+ if (expectedMutation && gMutationEvents.length) {
+ is(gMutationEvents[0].attrChange,
+ before == null ? MutationEvent.ADDITION : MutationEvent.MODIFICATION,
+ "wrong type of attribute change " + contextMsg);
+ // If there wasn't any previous attribute, prevValue will return an empty
+ // string.
+ var expectedPrevValue = before === null ? "" : before;
+ is(gMutationEvents[0].prevValue, expectedPrevValue,
+ "wrong previous value " + contextMsg);
+ is(gMutationEvents[0].newValue, after, "wrong new value " + contextMsg);
+ }
+}
+
+function assignToClassListStrict(e) {
+ "use strict";
+ try {
+ e.classList = "foo";
+ ok(true, "assigning to classList didn't throw");
+ e.removeAttribute("class");
+ } catch (e) {
+ ok(false, "assigning to classList threw");
+ }
+}
+
+function assignToClassList(e) {
+ try {
+ var expect = e.classList;
+ e.classList = "foo";
+ ok(true, "assigning to classList didn't throw");
+ is(e.classList, expect, "classList should be unchanged after assignment");
+ e.removeAttribute("class");
+ } catch (e) {
+ ok(false, "assigning to classList threw");
+ }
+}
+
+function testClassList(e) {
+
+ // basic tests
+
+ isnot(e.classList, undefined, "no classList attribute");
+ is(typeof(e.classList.contains), "function",
+ "no classList.contains function");
+ is(typeof(e.classList.add), "function", "no classList.add function");
+ is(typeof(e.classList.remove), "function", "no classList.remove function");
+ is(typeof(e.classList.toggle), "function", "no classList.toggle function");
+
+ assignToClassListStrict(e);
+ assignToClassList(e);
+
+ // length attribute
+
+ is(e.classList.length, 0, "wrong classList.length value");
+ e.setAttribute("class", "");
+ is(e.classList.length, 0, "wrong classList.length value");
+ e.setAttribute("class", " \t \f");
+ is(e.classList.length, 0, "wrong classList.length value");
+
+ e.setAttribute("class", "a");
+ is(e.classList.length, 1, "wrong classList.length value");
+ e.setAttribute("class", "a A");
+ is(e.classList.length, 2, "wrong classList.length value");
+ e.setAttribute("class", "\r\na\t\f");
+ is(e.classList.length, 1, "wrong classList.length value");
+
+ e.setAttribute("class", "a a");
+ is(e.classList.length, 2, "wrong classList.length value");
+
+ e.setAttribute("class", "a a a a a a");
+ is(e.classList.length, 6, "wrong classList.length value");
+
+ e.setAttribute("class", "a a b b");
+ is(e.classList.length, 4, "wrong classList.length value");
+
+ e.setAttribute("class", "a A B b");
+ is(e.classList.length, 4, "wrong classList.length value");
+
+ e.setAttribute("class", "a b c c b a a b c c");
+ is(e.classList.length, 10, "wrong classList.length value");
+
+ // [Stringifies]
+
+ ok(DOMTokenList.prototype.hasOwnProperty("toString"),
+ "Should have own toString on DOMTokenList")
+
+ e.removeAttribute("class");
+ is(e.classList.toString(), "", "wrong classList.toString() value");
+ is(e.classList + "", "", "wrong classList string conversion value");
+
+ e.setAttribute("class", "foo");
+ is(e.classList.toString(), "foo", "wrong classList.toString() value");
+ is(e.classList + "", "foo", "wrong classList string conversion value");
+
+ // item() method
+
+ e.setAttribute("class", "a");
+ is(e.classList.item(-1), null, "wrong classList.item() result");
+ is(e.classList[-1], undefined, "wrong classList[] result");
+ is(e.classList.item(0), "a", "wrong classList.item() result");
+ is(e.classList[0], "a", "wrong classList[] result");
+ is(e.classList.item(1), null, "wrong classList.item() result");
+ is(e.classList[1], undefined, "wrong classList[] result");
+
+ e.setAttribute("class", "aa AA aa");
+ is(e.classList.item(-1), null, "wrong classList.item() result");
+ is(e.classList[-1], undefined, "wrong classList[] result");
+ is(e.classList.item(0), "aa", "wrong classList.item() result");
+ is(e.classList[0], "aa", "wrong classList[] result");
+ is(e.classList.item(1), "AA", "wrong classList.item() result");
+ is(e.classList[1], "AA", "wrong classList[] result");
+ is(e.classList.item(2), "aa", "wrong classList.item() result");
+ is(e.classList[2], "aa", "wrong classList[] result");
+ is(e.classList.item(3), null, "wrong classList.item() result");
+ is(e.classList[3], undefined, "wrong classList[] result");
+ is(e.classList.item(0xffffffff), null, "wrong classList.item() result");
+ is(e.classList[0xffffffff], undefined, "wrong classList[] result");
+ is(e.classList.item(0xfffffffe), null, "wrong classList.item() result");
+ is(e.classList[0xffffffe], undefined, "wrong classList[] result");
+
+ e.setAttribute("class", "a b");
+ is(e.classList.item(-1), null, "wrong classList.item() result");
+ is(e.classList[-1], undefined, "wrong classList[] result");
+ is(e.classList.item(0), "a", "wrong classList.item() result");
+ is(e.classList[0], "a", "wrong classList[] result");
+ is(e.classList.item(1), "b", "wrong classList.item() result");
+ is(e.classList[1], "b", "wrong classList[] result");
+ is(e.classList.item(2), null, "wrong classList.item() result");
+ is(e.classList[2], undefined, "wrong classList[] result");
+
+ // contains() method
+
+ e.removeAttribute("class");
+ is(e.classList.contains("a"), false, "wrong classList.contains() result");
+ try {
+ e.classList.contains("");
+ ok(true, "classList.contains(empty_string) didn't throw");
+ } catch (e) {
+ ok(false, "classList.contains(empty_string) threw");
+ }
+ try {
+ e.classList.contains(" ");
+ ok(true, "classList.contains(string_with_spaces) didn't throw");
+ } catch (e) {
+ ok(false, "classList.contains(string_with_spaces) threw");
+ }
+ try {
+ e.classList.contains("aa ");
+ ok(true, "classList.contains(string_with_spaces) didn't throw");
+ } catch (e) {
+ ok(false, "classList.contains(string_with_spaces) threw");
+ }
+
+ e.setAttribute("class", "");
+ is(e.classList.contains("a"), false, "wrong classList.contains() result");
+
+ e.setAttribute("class", "a");
+ is(e.classList.contains("a"), true, "wrong classList.contains() result");
+ is(e.classList.contains("aa"), false, "wrong classList.contains() result");
+ is(e.classList.contains("b"), false, "wrong classList.contains() result");
+
+ e.setAttribute("class", "aa AA");
+ is(e.classList.contains("aa"), true, "wrong classList.contains() result");
+ is(e.classList.contains("AA"), true, "wrong classList.contains() result");
+ is(e.classList.contains("aA"), false, "wrong classList.contains() result");
+
+ e.setAttribute("class", "a a a");
+ is(e.classList.contains("a"), true, "wrong classList.contains() result");
+ is(e.classList.contains("aa"), false, "wrong classList.contains() result");
+ is(e.classList.contains("b"), false, "wrong classList.contains() result");
+
+ e.setAttribute("class", "a b c");
+ is(e.classList.contains("a"), true, "wrong classList.contains() result");
+ is(e.classList.contains("b"), true, "wrong classList.contains() result");
+
+ // Test for bug 530171
+ e.setAttribute("class", "null undefined");
+ is(e.classList.contains(null), true, "wrong classList.contains() result");
+ is(e.classList.contains(undefined), true, "wrong classList.contains() result");
+
+ // add() method
+
+ function checkAdd(before, argument, after, expectedException) {
+ checkModification(e, "add", argument, null, before, after, expectedException);
+ }
+
+ checkAdd(null, "", null, "SyntaxError");
+ checkAdd(null, ["a", ""], null, "SyntaxError");
+ checkAdd(null, " ", null, "InvalidCharacterError");
+ checkAdd(null, ["a", " "], null, "InvalidCharacterError");
+ checkAdd(null, ["a", "aa "], null, "InvalidCharacterError");
+
+ checkAdd("a", "a", "a");
+ checkAdd("aa", "AA", "aa AA");
+ checkAdd("a b c", "a", "a b c");
+ checkAdd("a a a b", "a", "a a a b");
+ checkAdd(null, "a", "a");
+ checkAdd("", "a", "a");
+ checkAdd(" ", "a", " a");
+ checkAdd(" \f", "a", " \fa");
+ checkAdd("a", "b", "a b");
+ checkAdd("a b c", "d", "a b c d");
+ checkAdd("a b c ", "d", "a b c d");
+
+ // multiple add
+ checkAdd("a b c ", ["d", "e"], "a b c d e");
+ checkAdd("a b c ", ["a", "a"], "a b c ");
+ checkAdd("a b c ", ["d", "d"], "a b c d");
+ checkAdd("a b c ", [], "a b c ");
+ checkAdd(null, ["a", "b"], "a b");
+ checkAdd("", ["a", "b"], "a b");
+
+ // Test for bug 530171
+ checkAdd(null, null, "null");
+ checkAdd(null, undefined, "undefined");
+
+ // remove() method
+
+ function checkRemove(before, argument, after, expectedException) {
+ checkModification(e, "remove", argument, null, before, after, expectedException);
+ }
+
+ checkRemove(null, "", null, "SyntaxError");
+ checkRemove(null, " ", null, "InvalidCharacterError");
+ checkRemove(null, "aa ", null, "InvalidCharacterError");
+
+ checkRemove(null, "a", null);
+ checkRemove("", "a", "");
+ checkRemove("a b c", "d", "a b c");
+ checkRemove("a b c", "A", "a b c");
+ checkRemove(" a a a ", "a", "");
+ checkRemove("a b", "a", "b");
+ checkRemove("a b ", "a", "b");
+ checkRemove("a a b", "a", "b");
+ checkRemove("aa aa bb", "aa", "bb");
+ checkRemove("a a b a a c a a", "a", "b c");
+
+ checkRemove("a b c", "b", "a c");
+ checkRemove("aaa bbb ccc", "bbb", "aaa ccc");
+ checkRemove(" a b c ", "b", "a c");
+ checkRemove("a b b b c", "b", "a c");
+
+ checkRemove("a b c", "c", "a b");
+ checkRemove(" a b c ", "c", "a b");
+ checkRemove("a b c c c", "c", "a b");
+
+ checkRemove("a b a c a d a", "a", "b c d");
+ checkRemove("AA BB aa CC AA dd aa", "AA", "BB aa CC dd aa");
+
+ checkRemove("\ra\na\ta\f", "a", "");
+
+ // multiple remove
+ checkRemove("a b c ", ["d", "e"], "a b c");
+ checkRemove("a b c ", ["a", "b"], "c");
+ checkRemove("a b c ", ["a", "c"], "b");
+ checkRemove("a b c ", ["a", "a"], "b c");
+ checkRemove("a b c ", ["d", "d"], "a b c");
+ checkRemove("a b c ", [], "a b c");
+ checkRemove(null, ["a", "b"], null);
+ checkRemove("", ["a", "b"], "");
+
+ // Test for bug 530171
+ checkRemove("null", null, "");
+ checkRemove("undefined", undefined, "");
+
+ // toggle() method
+
+ function checkToggle(before, argument, expectedRes, after, expectedException) {
+ checkModification(e, "toggle", argument, expectedRes, before, after, expectedException);
+ }
+
+ checkToggle(null, "", null, null, "SyntaxError");
+ checkToggle(null, "aa ", null, null, "InvalidCharacterError");
+
+ checkToggle(null, "a", true, "a");
+ checkToggle("", "a", true, "a");
+ checkToggle(" ", "a", true, " a");
+ checkToggle(" \f", "a", true, " \fa");
+ checkToggle("a", "b", true, "a b");
+ checkToggle("a", "A", true, "a A");
+ checkToggle("a b c", "d", true, "a b c d");
+ checkToggle("a b c", "d", true, "a b c d");
+
+ checkToggle("a", "a", false, "");
+ checkToggle(" a a a ", "a", false, "");
+ checkToggle(" A A A ", "a", true, " A A A a");
+ checkToggle(" a b c ", "b", false, "a c");
+ checkToggle(" a b c b b", "b", false, "a c");
+ checkToggle(" a b c ", "c", false, "a b");
+ checkToggle(" a b c ", "a", false, "b c");
+
+ // Test for bug 530171
+ checkToggle("null", null, false, "");
+ checkToggle("", null, true, "null");
+ checkToggle("undefined", undefined, false, "");
+ checkToggle("", undefined, true, "undefined");
+
+
+ // tests for the force argument handling
+
+ function checkForceToggle(before, argument, force, expectedRes, after, expectedException) {
+ checkModification(e, "toggle", [argument, force], expectedRes, before, after, expectedException);
+ }
+
+ checkForceToggle("", "a", true, true, "a");
+ checkForceToggle("a", "a", true, true, "a");
+ checkForceToggle("a", "b", true, true, "a b");
+ checkForceToggle("a b", "b", true, true, "a b");
+ checkForceToggle("", "a", false, false, "");
+ checkForceToggle("a", "a", false, false, "");
+ checkForceToggle("a", "b", false, false, "a");
+ checkForceToggle("a b", "b", false, false, "a");
+}
+
+var content = document.getElementById("content");
+
+var htmlNode = document.createElement("div");
+content.appendChild(htmlNode);
+testClassList(htmlNode);
+
+var xhtmlNode = document.createElementNS(XHTML_NS, "div");
+content.appendChild(xhtmlNode);
+testClassList(xhtmlNode);
+
+var xulNode = document.createElementNS(XUL_NS, "box");
+content.appendChild(xulNode);
+testClassList(xulNode);
+
+var mathMLNode = document.createElementNS(MATHML_NS, "math");
+content.appendChild(mathMLNode);
+testClassList(mathMLNode);
+
+var xmlNode = document.createElementNS(null, "foo");
+content.appendChild(xmlNode);
+testClassList(xmlNode);
+
+var fooNode = document.createElementNS("http://example.org/foo", "foo");
+content.appendChild(fooNode);
+testClassList(fooNode);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_clearTimeoutIntervalNoArg.html b/dom/base/test/test_clearTimeoutIntervalNoArg.html
new file mode 100644
index 000000000..e1d60022f
--- /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_constructor-assignment.html b/dom/base/test/test_constructor-assignment.html
new file mode 100644
index 000000000..f04a991da
--- /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 type="application/javascript" 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 000000000..b987057e2
--- /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 type="application/javascript" 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_copyimage.html b/dom/base/test/test_copyimage.html
new file mode 100644
index 000000000..a2610e360
--- /dev/null
+++ b/dom/base/test/test_copyimage.html
@@ -0,0 +1,94 @@
+<!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 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=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).QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .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 webnav = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ var docShell = webnav.QueryInterface(Ci.nsIDocShell);
+ 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/unicode data ?
+ ok(clipboard.hasDataMatchingFlavors(["text/unicode"], 1, clipboard.kGlobalClipboard), "clipboard contains unicode text");
+ // Does the clipboard contain text/html data ?
+ ok(clipboard.hasDataMatchingFlavors(["text/html"], 1, clipboard.kGlobalClipboard), "clipboard contains html text");
+ // Does the clipboard contain image data ?
+ ok(clipboard.hasDataMatchingFlavors(["image/png"], 1, clipboard.kGlobalClipboard), "clipboard contains image");
+
+ // Is the text/uncodie data correct ?
+ testClipboardValue('text/unicode', 'about:logo');
+ // Is the text/html data correct ?
+ var expected = '<img id="logo" src="about:logo">';
+ if (navigator.platform.indexOf("Win") >= 0) {
+ expected = "<html><body>\n<!--StartFragment-->" + expected + "<!--EndFragment-->\n</body>\n</html>";
+ }
+ testClipboardValue('text/html', expected);
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["clipboard.plainTextOnly", false]]}, 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 000000000..db929270d
--- /dev/null
+++ b/dom/base/test/test_copypaste.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test for copy/paste</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" 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(() => 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>
+</body>
+</html>
diff --git a/dom/base/test/test_copypaste.xhtml b/dom/base/test/test_copypaste.xhtml
new file mode 100644
index 000000000..9b06cb8b8
--- /dev/null
+++ b/dom/base/test/test_copypaste.xhtml
@@ -0,0 +1,108 @@
+<?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/unicode 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/unicode.
+-->
+<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for copy/paste with XHTML</title>
+ <script type="text/javascript" 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(() => 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.xul b/dom/base/test/test_copypaste.xul
new file mode 100644
index 000000000..6c14c7357
--- /dev/null
+++ b/dom/base/test/test_copypaste.xul
@@ -0,0 +1,64 @@
+<?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=888839
+-->
+<window title="Mozilla Bug 888839"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript"><![CDATA[
+
+var { classes: Cc, interfaces: Ci } = Components;
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+function runTest() {
+ let desc = document.querySelector("description");
+ window.getSelection().selectAllChildren(desc);
+
+ let webnav = window.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation);
+
+ webnav.
+ QueryInterface(Ci.nsIDocShell).
+ contentViewer.
+ QueryInterface(Ci.nsIContentViewerEdit).
+ copySelection();
+
+ let mime = "text/unicode";
+ let whichClipboard = Ci.nsIClipboard.kGlobalClipboard;
+ let clipboard = Cc["@mozilla.org/widget/clipboard;1"].
+ getService(Ci.nsIClipboard);
+ ok(clipboard.hasDataMatchingFlavors([mime], 1, whichClipboard),
+ "Clipboard should have text/unicode");
+
+ let transferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ transferable.init(webnav.QueryInterface(Ci.nsILoadContext));
+ transferable.addDataFlavor(mime);
+ clipboard.getData(transferable, whichClipboard);
+ var data = {};
+ transferable.getTransferData(mime, data, {});
+ is(data.value.QueryInterface(Ci.nsISupportsString).data,
+ "\n hello\n world\n ",
+ "Paste is not HTML, so it should not be pretty printed");
+
+ SimpleTest.finish();
+}
+
+ ]]></script>
+
+ <description style="-moz-user-focus: normal; -moz-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=888839"
+ target="_blank">Mozilla Bug 888839</a>
+ </body>
+</window>
diff --git a/dom/base/test/test_createHTMLDocument.html b/dom/base/test/test_createHTMLDocument.html
new file mode 100644
index 000000000..66b090d18
--- /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_declare_stylesheet_obsolete.html b/dom/base/test/test_declare_stylesheet_obsolete.html
new file mode 100644
index 000000000..baceea2e9
--- /dev/null
+++ b/dom/base/test/test_declare_stylesheet_obsolete.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=713564
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 713564</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!-- Load the variable stylesheet, which changes the color of #content. -->
+ <link rel="stylesheet" type="text/css" href="variable_style_sheet.sjs"/>
+
+ <script type="application/javascript">
+
+ function insertLinkToVarSSAndRun(callback) {
+ var ss = document.createElement("link");
+ ss.rel = "stylesheet";
+ ss.type = "text/css";
+ ss.href = "variable_style_sheet.sjs";
+ document.getElementsByTagName("head")[0].appendChild(ss);
+ ss.addEventListener("load", callback);
+ }
+
+ /** Test for Bug 713564 **/
+
+ // Then you link to that sheet, remove the link from the DOM, insert a new link to
+ // the same url and check that there was no new access, then call our new method,
+ // insert _another_ <link> to the same url, and check that this time we hit the
+ // server.
+ SimpleTest.waitForExplicitFinish();
+
+ function do_test() {
+ var var_sheet = document.getElementsByTagName("link")[1];
+ var head = document.getElementsByTagName("head")[0];
+ var content = document.getElementById("content");
+ var var_sheet_url = var_sheet.href;
+
+ var previousBgColor = window.getComputedStyle(content).
+ getPropertyValue("background-color");
+ var_sheet.parentNode.removeChild(var_sheet);
+ insertLinkToVarSSAndRun(function() {
+ is(window.getComputedStyle(content).getPropertyValue("background-color"),
+ previousBgColor,
+ "Sheet should still be the same.");
+
+ // Obsolete sheet
+ try {
+ SpecialPowers.wrap(document).obsoleteSheet(var_sheet_url);
+ } catch (e) {
+ ok(false, "obsoleteSheet should not raise an error on valid URL.");
+ }
+ insertLinkToVarSSAndRun(function() {
+ isnot(window.getComputedStyle(content).getPropertyValue("background-color"),
+ previousBgColor,
+ "Sheet should change after obsoleted and reinserted.");
+ SimpleTest.finish();
+ });
+ });
+ // obsoleteSheet should throw with invalid input:
+ try {
+ SpecialPowers.wrap(document).obsoleteSheet("");
+ ok(false, "obsoleteSheet should throw with empty string.");
+ } catch (e) {
+ ok(true, "obsoleteSheet throws with empty string.");
+ }
+ try {
+ SpecialPowers.wrap(document).obsoleteSheet("foo");
+ ok(false, "obsoleteSheet should throw with invalid URL.");
+ } catch (e) {
+ ok(true, "obsoleteSheet throws with invalid URL.");
+ }
+ try {
+ SpecialPowers.wrap(document).obsoleteSheet("http://www.mozilla.org");
+ ok(true, "obsoleteSheet should not throw with valid URL.");
+ } catch (e) {
+ ok(false, "obsoleteSheet throws with valid URL.");
+ }
+ }
+
+ </script>
+</head>
+<body onload="do_test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=713564">Mozilla Bug 713564</a>
+<p id="display"></p>
+<div id="content">
+ <br>
+ <br>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_dialogArguments.html b/dom/base/test/test_dialogArguments.html
new file mode 100644
index 000000000..70a091d00
--- /dev/null
+++ b/dom/base/test/test_dialogArguments.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for Bug 1019761</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+<body>
+<script type="application/javascript">
+
+/*
+ Tests whether Firefox crashes when accessing the dialogArguments property
+ of a modal window that has been closed.
+*/
+SimpleTest.waitForExplicitFinish();
+
+function openModal() {
+ showModalDialog("javascript:opener.winRef = window; \
+ window.opener.setTimeout(\'winRef.dialogArguments;\', 0);\
+ window.close();");
+
+ ok(true, "dialogArguments did not cause a crash.");
+ SimpleTest.finish();
+}
+
+window.onload = openModal;
+</script>
+</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 000000000..a5140d9df
--- /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 000000000..763ba9898
--- /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 type="application/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=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(); }, false);
+</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 000000000..831120050
--- /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 type="application/javascript" 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 000000000..e33575e84
--- /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 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=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_register.html b/dom/base/test/test_document_register.html
new file mode 100644
index 000000000..6cf15a52f
--- /dev/null
+++ b/dom/base/test/test_document_register.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <script type="text/javascript" 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 c = document.getElementById("fooframe").contentDocument.registerElement("x-foo");
+ var elem = new c();
+ 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_domcursor.html b/dom/base/test/test_domcursor.html
new file mode 100644
index 000000000..d33581f12
--- /dev/null
+++ b/dom/base/test/test_domcursor.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOMCursor</title>
+ <script type="text/javascript" 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;version=1.7">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+var reqserv = SpecialPowers.getDOMRequestService();
+ok("createRequest" in reqserv, "appears to be a service");
+
+var req;
+var lastContinue = false;
+
+var index = 0;
+
+function next() {
+ if (index < tests.length) {
+ ok(true, "Begin test");
+ tests[index++]();
+ } else {
+ ok(true, "All done");
+ SimpleTest.finish();
+ }
+}
+
+var tests = [
+ function() {
+ // create a cursor, test its interface and its initial state
+ req = reqserv.createCursor(window, function() {
+ if (lastContinue) {
+ reqserv.fireDone(req);
+ } else {
+ reqserv.fireSuccess(req, "next result")
+ }
+ });
+ ok("result" in req, "cursor has result");
+ ok("error" in req, "cursor has error");
+ ok("onsuccess" in req, "cursor has onsuccess");
+ ok("onerror" in req, "cursor has onerror");
+ ok("readyState" in req, "cursor has readyState");
+ ok("done" in req, "cursor has finished");
+ ok("continue" in req, "cursor has continue");
+ ok(!("then" in req), "cursor should not have a then method");
+
+ 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");
+ next();
+ },
+ function() {
+ // fire success
+ req.onsuccess = function(e) {
+ ok(e, "got success event");
+ is(e.type, "success", "correct type during success");
+ is(e.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(req.done, false, "cursor is not done after continue")
+ next();
+ }
+ reqserv.fireSuccess(req, "my result");
+ },
+ function() {
+ // continue
+ req.onsuccess = function(e) {
+ ok(e, "got success event after continue");
+ is(e.type, "success", "correct type during continue");
+ is(e.target, req, "correct target during continue");
+ is(req.readyState, "done", "correct readyState after continue");
+ is(req.error, null, "correct error after continue");
+ is(req.result, "next result", "correct result after continue");
+ is(req.done, false, "cursor is not done after continue")
+ next();
+ }
+ req.continue();
+ },
+ function() {
+ // FireDone
+ req.onsuccess = function(e) {
+ ok(e, "got success event after continue");
+ is(e.type, "success", "correct type during continue");
+ is(e.target, req, "correct target during continue");
+ is(req.readyState, "done", "correct readyState after continue");
+ is(req.error, null, "correct error after continue");
+ is(req.result, undefined, "no result after last continue");
+ is(req.done, true, "cursor is done after last continue")
+ try {
+ req.continue();
+ ok(false, "continue when cursor is done should fail");
+ } catch (e) {
+ ok(true, "continue when cursor is done should fail");
+ }
+
+ next();
+ }
+ lastContinue = true;
+ req.continue();
+ },
+ function() {
+ // fire error
+ req = reqserv.createCursor(window, function(){});
+ req.onerror = function(e) {
+ ok(e, "got success event");
+ is(e.type, "error", "correct type during error");
+ is(e.target, req, "correct target during error");
+ is(req.readyState, "done", "correct readyState after error");
+ is(req.error.name, "error msg", "correct error after error");
+ is(req.result, undefined, "correct result after error");
+ try {
+ req.continue();
+ ok(false, "continue while in an error state should fail");
+ } catch (e) {
+ ok(true, "continue while in an error state should fail");
+ }
+
+ next();
+ }
+ reqserv.fireError(req, "error msg");
+ }
+];
+
+next();
+
+</script>
+</pre>
+</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 000000000..2bc19c717
--- /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 type="application/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=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 000000000..b45187d66
--- /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 type="application/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=816410">Mozilla Bug 816410</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+"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 000000000..85636ba52
--- /dev/null
+++ b/dom/base/test/test_domrequest.html
@@ -0,0 +1,229 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for DOMRequest</title>
+ <script type="text/javascript" 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;version=1.7">
+"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 DOMError, "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, "OhMyError", "correct error 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 DOMError, "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.fireDetailedError(req, new DOMError("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, "detailedError", "correct error 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, "OhMyError", "correct error 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 DOMError, "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 DOMError, "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.fireDetailedError(req, new DOMError("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, "detailedError", "correct error 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.xul b/dom/base/test/test_domrequesthelper.xul
new file mode 100644
index 000000000..4365e4f0e
--- /dev/null
+++ b/dom/base/test/test_domrequesthelper.xul
@@ -0,0 +1,552 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+ const Ci = Components.interfaces;
+ Cu.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(Ci.nsIMessageBroadcaster);
+
+ 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.createElement("iframe");
+ frame.onload = function() {
+ obs.addObserver(observer, TOPIC, false);
+ // 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(req instanceof DOMRequest, "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 000000000..218f510ff
--- /dev/null
+++ b/dom/base/test/test_domwindowutils.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for DOMWindowUtils</title>
+ <script type="application/javascript" 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.nsIDOMNSEvent.SHIFT_MASK;
+
+ window.addEventListener("mousedown", function listener(evt) {
+ window.removeEventListener("mousedown", listener);
+ // 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, SpecialPowers.Ci.nsIDOMMouseEvent.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);
+ });
+
+ // 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.nsIDOMNSEvent.SHIFT_MASK,
+ pressure = 0.5,
+ source = SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_KEYBOARD;
+
+ window.addEventListener("mouseup", function listener(evt) {
+ window.removeEventListener("mouseup", listener);
+ 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);
+ });
+
+ // Check explicit value for optional args
+ utils.sendMouseEvent("mouseup", x, y, button, clickCount, modifiers,
+ false, pressure, source, false);
+}
+
+var tests = [
+ test_sendMouseEventDefaults,
+ test_sendMouseEventOptionals
+];
+
+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_e4x_for_each.html b/dom/base/test/test_e4x_for_each.html
new file mode 100644
index 000000000..3d1ffa57a
--- /dev/null
+++ b/dom/base/test/test_e4x_for_each.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset=utf-8>
+ <title>Test for E4X "for each" syntax</title>
+ <script type="text/javascript" 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>
+<div id="template" style="display: none">
+function runTest(i) {
+ var t = tests[i];
+ count++;
+ try {
+ Function("for each (var a in []) {}");
+ ok(t.enabled, "JavaScript" + ("version" in t ? " " + t.version : "") + " supports for-each-in");
+ } catch (e) {
+ ok(!t.enabled, "JavaScript" + ("version" in t ? " " + t.version : "") + " does NOT support for-each-in");
+ }
+}
+</div>
+<pre id="test">
+<script class="testbody">
+
+var tests = [
+ {enabled: false},
+ {version: "1.0", enabled: false},
+ {version: "1.1", enabled: false},
+ {version: "1.2", enabled: false},
+ {version: "1.3", enabled: false},
+ {version: "1.4", enabled: false},
+ {version: "1.5", enabled: false},
+ {version: "1.6", enabled: true},
+ {version: "1.7", enabled: true},
+ {version: "1.8", enabled: true},
+];
+
+var count = 0;
+for (var i = 0; i < tests.length; i++) {
+ var t = tests[i];
+ var script = document.createElement("script");
+ script.type = "application/javascript" + ("version" in t ? ";version=" + t.version : "");
+ script.textContent = document.getElementById("template").textContent + "\n" + "runTest(" + i + ");";
+ document.body.appendChild(script);
+}
+script = document.createElement("script");
+script.textContent = 'is(count, tests.length, "runTest() call count");';
+document.body.appendChild(script);
+</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 000000000..9b360b16f
--- /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 type="application/javascript" 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 000000000..3ff4db0e7
--- /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 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="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 000000000..adede6b8b
--- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" 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_encodeToStringWithMaxLength.html b/dom/base/test/test_encodeToStringWithMaxLength.html
new file mode 100644
index 000000000..587aff77b
--- /dev/null
+++ b/dom/base/test/test_encodeToStringWithMaxLength.html
@@ -0,0 +1,64 @@
+<!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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function getEncoder() {
+ const de = SpecialPowers.Ci.nsIDocumentEncoder;
+ const Cc = SpecialPowers.Cc;
+
+ // Create a plaintext encoder without flags.
+ var encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
+ .createInstance(de);
+ 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_error.html b/dom/base/test/test_error.html
new file mode 100644
index 000000000..c06d82526
--- /dev/null
+++ b/dom/base/test/test_error.html
@@ -0,0 +1,44 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=869013
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 869013</title>
+ <script type="application/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=869013">Mozilla Bug 869013</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 869013 **/
+ var a = new DOMError('name');
+ ok(a, "DOMError created with name: " + a.name + " and message: " + a.message);
+ is(a.name, "name", "Name is correct");
+ is(a.message, "", "Message is correct");
+
+ a = new DOMError('name1', 'message1');
+ ok(a, "DOMError created with name: " + a.name + " and message: " + a.message);
+ is(a.name, "name1", "Name is correct");
+ is(a.message, "message1", "Message is correct");
+
+ try {
+ a = new DOMError();
+ ok(false, "DOMError should throw if there are not params");
+ } catch(e) {
+ ok(true, "DOMError should throw if there are not params");
+ }
+
+ </script>
+</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 000000000..9cac4c933
--- /dev/null
+++ b/dom/base/test/test_explicit_user_agent.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for XMLHttpRequest.GetResponseHeader(foo) byte-inflates the output</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.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;version=1.7">
+ "use strict";
+
+ add_task(function*() {
+ yield 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);
+ });
+
+ yield 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 = yield 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 = yield fetch('file_explicit_user_agent.sjs', {
+ method: 'GET',
+ headers: 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_file_from_blob.html b/dom/base/test/test_file_from_blob.html
new file mode 100644
index 000000000..d596d1dca
--- /dev/null
+++ b/dom/base/test/test_file_from_blob.html
@@ -0,0 +1,111 @@
+<!doctype html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=819900
+-->
+ <head>
+<title>Test for crash caused by unloading and reloading srcdoc iframes</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=819900">Mozilla Bug 819900</a>
+
+<pre id="test">
+<script>
+
+ var b = new Blob(['1234567890']);
+ ok(b, 'Blob created');
+ is(b.size, 10, 'Blob has the right size');
+
+ var status = false;
+ try {
+ f = new File(b);
+ } catch(e) {
+ status = true;
+ }
+ ok(status, "File throws if the second argument is missing");
+
+ status = false;
+ try {
+ f = new File(42, 'foobar.txt');
+ } catch(e) {
+ status = true;
+ }
+ ok(status, "File throws if the argument is not an array");
+
+ status = false;
+ try {
+ f = new File({}, 'foobar.txt');
+ } catch(e) {
+ status = true;
+ }
+ ok(status, "File throws if the argument is not an array");
+
+ status = false;
+ try {
+ f = new File("hello world", 'foobar.txt');
+ } catch(e) {
+ status = true;
+ }
+ ok(status, "File throws if the argument is not an array");
+
+ f = new File(['1234567890'], '');
+ ok(f, 'File created');
+ is(f.size, 10, 'File has the right size');
+ is(f.name, '');
+ is(f.type, '');
+
+ f = new File(['1234567890'], 42);
+ ok(f, 'File created');
+ is(f.size, 10, 'File has the right size');
+ is(f.name, '42');
+ is(f.type, '');
+
+ f = new File(['1234567890'], 'text.txt');
+ ok(f, 'File created');
+ is(f.size, 10, 'File has the right size');
+ is(f.name, 'text.txt');
+ is(f.type, '');
+
+ f = new File(['1234567890'], 'text.txt', { type: 'plain/text' });
+ ok(f, 'File created');
+ is(f.size, 10, 'File has the right size');
+ is(f.name, 'text.txt');
+ is(f.type, 'plain/text');
+
+ f = new File([b], 'text.txt');
+ ok(f, 'File created');
+ is(f.name, 'text.txt');
+ is(f.type, '');
+ is(f.size, b.size);
+
+ f = new File([b], 'test.txt', { type: 'plain/text' });
+ ok(f, 'File created');
+ is(f.name, 'test.txt');
+ is(f.type, 'plain/text');
+ is(f.size, b.size);
+
+ f = new File([b, b], 'test.txt', { type: 'plain/text' });
+ ok(f, 'File created');
+ is(f.name, 'test.txt');
+ is(f.type, 'plain/text');
+ is(f.size, b.size * 2);
+
+ var f2 = new File([f, f], 'test.txt', { type: 'plain/text' });
+ ok(f2, 'File created');
+ is(f2.name, 'test.txt');
+ is(f2.type, 'plain/text');
+ is(f2.size, f.size * 2);
+
+ var f2 = new File([f, f], 'test.txt', b);
+ ok(f2, 'File created');
+ is(f2.name, 'test.txt');
+ is(f2.type, b.type);
+ is(f2.size, f.size * 2);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_file_negative_date.html b/dom/base/test/test_file_negative_date.html
new file mode 100644
index 000000000..ebfa9bd0d
--- /dev/null
+++ b/dom/base/test/test_file_negative_date.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1158437
+-->
+<head>
+ <title>Test for negative date in File (Bug 1158437)</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="fileutils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1158437">Mozilla Bug 1158437</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+"use strict";
+
+var blob = new Blob(['hello world']);
+var f1 = new File([blob], 'f1.txt', { lastModified: 0 });
+var f2 = new File([blob], 'f2.txt', { lastModified: -1 });
+var f3 = new File([blob], 'f3.txt', { lastModified: -1000 });
+
+is(f1.lastModified, 0, "lastModified == 0 is supported");
+ok(f1.lastModifiedDate.toString(), (new Date(0)).toString(), "Correct f1.lastModifiedDate value");
+is(f2.lastModified, -1, "lastModified == -1 is supported");
+ok(f2.lastModifiedDate.toString(), (new Date(-1)).toString(), "Correct f2.lastModifiedDate value");
+is(f3.lastModified, -1000, "lastModified == -1000 is supported");
+ok(f3.lastModifiedDate.toString(), (new Date(-1000)).toString(), "Correct f3.lastModifiedDate value");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/base/test/test_fileapi.html b/dom/base/test/test_fileapi.html
new file mode 100644
index 000000000..fc33c7ae6
--- /dev/null
+++ b/dom/base/test/test_fileapi.html
@@ -0,0 +1,479 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=414796
+-->
+ <title>Test for Bug 414796</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=414796">Mozilla Bug 414796</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+
+// File constructors should not work from non-chrome code
+try {
+ var file = File("/etc/passwd");
+ ok(false, "Did not throw on unprivileged attempt to construct a File");
+} catch (e) {
+ ok(true, "Threw on an unprivileged attempt to construct a File");
+}
+
+const minFileSize = 20000;
+var testRanCounter = 0;
+var expectedTestCount = 0;
+var testSetupFinished = false;
+SimpleTest.waitForExplicitFinish();
+
+is(FileReader.EMPTY, 0, "correct EMPTY value");
+is(FileReader.LOADING, 1, "correct LOADING value");
+is(FileReader.DONE, 2, "correct DONE value");
+
+// Create strings containing data we'll test with. We'll want long
+// strings to ensure they span multiple buffers while loading
+var testTextData = "asd b\tlah\u1234w\u00a0r";
+while (testTextData.length < minFileSize) {
+ testTextData = testTextData + testTextData;
+}
+
+var testASCIIData = "abcdef 123456\n";
+while (testASCIIData.length < minFileSize) {
+ testASCIIData = testASCIIData + testASCIIData;
+}
+
+var testBinaryData = "";
+for (var i = 0; i < 256; i++) {
+ testBinaryData += String.fromCharCode(i);
+}
+while (testBinaryData.length < minFileSize) {
+ testBinaryData = testBinaryData + testBinaryData;
+}
+
+var dataurldata0 = testBinaryData.substr(0, testBinaryData.length -
+ testBinaryData.length % 3);
+var dataurldata1 = testBinaryData.substr(0, testBinaryData.length - 2 -
+ testBinaryData.length % 3);
+var dataurldata2 = testBinaryData.substr(0, testBinaryData.length - 1 -
+ testBinaryData.length % 3);
+
+
+//Set up files for testing
+var openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
+var opener = SpecialPowers.loadChromeScript(openerURL);
+opener.addMessageListener("files.opened", onFilesOpened);
+opener.sendAsyncMessage("files.open", [
+ testASCIIData,
+ testBinaryData,
+ null,
+ convertToUTF8(testTextData),
+ convertToUTF16(testTextData),
+ "",
+ dataurldata0,
+ dataurldata1,
+ dataurldata2,
+]);
+
+function onFilesOpened(message) {
+ let [
+ asciiFile,
+ binaryFile,
+ nonExistingFile,
+ utf8TextFile,
+ utf16TextFile,
+ emptyFile,
+ dataUrlFile0,
+ dataUrlFile1,
+ dataUrlFile2,
+ ] = message;
+
+ // Test that plain reading works and fires events as expected, both
+ // for text and binary reading
+
+ var onloadHasRunText = false;
+ var onloadStartHasRunText = false;
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial text readyState");
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "plain reading");
+ r.addEventListener("load", function() { onloadHasRunText = true }, false);
+ r.addEventListener("loadstart", function() { onloadStartHasRunText = true }, false);
+ r.readAsText(asciiFile);
+ is(r.readyState, FileReader.LOADING, "correct loading text readyState");
+ is(onloadHasRunText, false, "text loading must be async");
+ is(onloadStartHasRunText, true, "text loadstart should fire sync");
+ expectedTestCount++;
+
+ var onloadHasRunBinary = false;
+ var onloadStartHasRunBinary = false;
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial binary readyState");
+ r.addEventListener("load", function() { onloadHasRunBinary = true }, false);
+ r.addEventListener("loadstart", function() { onloadStartHasRunBinary = true }, false);
+ r.readAsBinaryString(binaryFile);
+ r.onload = getLoadHandler(testBinaryData, testBinaryData.length, "binary reading");
+ is(r.readyState, FileReader.LOADING, "correct loading binary readyState");
+ is(onloadHasRunBinary, false, "binary loading must be async");
+ is(onloadStartHasRunBinary, true, "binary loadstart should fire sync");
+ expectedTestCount++;
+
+ var onloadHasRunArrayBuffer = false;
+ var onloadStartHasRunArrayBuffer = false;
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial arrayBuffer readyState");
+ r.addEventListener("load", function() { onloadHasRunArrayBuffer = true }, false);
+ r.addEventListener("loadstart", function() { onloadStartHasRunArrayBuffer = true }, false);
+ r.readAsArrayBuffer(binaryFile);
+ r.onload = getLoadHandlerForArrayBuffer(testBinaryData, testBinaryData.length, "array buffer reading");
+ is(r.readyState, FileReader.LOADING, "correct loading arrayBuffer readyState");
+ is(onloadHasRunArrayBuffer, false, "arrayBuffer loading must be async");
+ is(onloadStartHasRunArrayBuffer, true, "arrayBuffer loadstart should fire sync");
+ expectedTestCount++;
+
+ // Test a variety of encodings, and make sure they work properly
+ r = new FileReader();
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "no encoding reading");
+ r.readAsText(asciiFile, "");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "iso8859 reading");
+ r.readAsText(asciiFile, "iso-8859-1");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testTextData,
+ convertToUTF8(testTextData).length,
+ "utf8 reading");
+ r.readAsText(utf8TextFile, "utf8");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.readAsText(utf16TextFile, "utf-16");
+ r.onload = getLoadHandler(testTextData,
+ convertToUTF16(testTextData).length,
+ "utf16 reading");
+ expectedTestCount++;
+
+ // Test get result without reading
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY,
+ "readyState in test reader get result without reading");
+ is(r.error, null,
+ "no error in test reader get result without reading");
+ is(r.result, null,
+ "result in test reader get result without reading");
+
+ // Test loading an empty file works (and doesn't crash!)
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty no encoding reading");
+ r.readAsText(emptyFile, "");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty utf8 reading");
+ r.readAsText(emptyFile, "utf8");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty utf16 reading");
+ r.readAsText(emptyFile, "utf-16");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty binary string reading");
+ r.readAsBinaryString(emptyFile);
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandlerForArrayBuffer("", 0, "empty array buffer reading");
+ r.readAsArrayBuffer(emptyFile);
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(""), 0, "empt binary string reading");
+ r.readAsDataURL(emptyFile);
+ expectedTestCount++;
+
+ // Test reusing a FileReader to read multiple times
+ r = new FileReader();
+ r.onload = getLoadHandler(testASCIIData,
+ testASCIIData.length,
+ "to-be-reused reading text")
+ var makeAnotherReadListener = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener, false);
+ r.onload = getLoadHandler(testASCIIData,
+ testASCIIData.length,
+ "reused reading text");
+ r.readAsText(asciiFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener, false);
+ r.readAsText(asciiFile);
+ expectedTestCount += 2;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testBinaryData,
+ testBinaryData.length,
+ "to-be-reused reading binary")
+ var makeAnotherReadListener2 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener2, false);
+ r.onload = getLoadHandler(testBinaryData,
+ testBinaryData.length,
+ "reused reading binary");
+ r.readAsBinaryString(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener2, false);
+ r.readAsBinaryString(binaryFile);
+ expectedTestCount += 2;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(testBinaryData),
+ testBinaryData.length,
+ "to-be-reused reading data url")
+ var makeAnotherReadListener3 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener3, false);
+ r.onload = getLoadHandler(convertToDataURL(testBinaryData),
+ testBinaryData.length,
+ "reused reading data url");
+ r.readAsDataURL(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener3, false);
+ r.readAsDataURL(binaryFile);
+ expectedTestCount += 2;
+
+ r = new FileReader();
+ r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+ testBinaryData.length,
+ "to-be-reused reading arrayBuffer")
+ var makeAnotherReadListener4 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener4, false);
+ r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+ testBinaryData.length,
+ "reused reading arrayBuffer");
+ r.readAsArrayBuffer(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener4, false);
+ r.readAsArrayBuffer(binaryFile);
+ expectedTestCount += 2;
+
+ // Test first reading as ArrayBuffer then read as something else
+ // (BinaryString) and doesn't crash
+ r = new FileReader();
+ r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
+ testBinaryData.length,
+ "to-be-reused reading arrayBuffer")
+ var makeAnotherReadListener5 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener5, false);
+ r.onload = getLoadHandler(testBinaryData,
+ testBinaryData.length,
+ "reused reading binary string");
+ r.readAsBinaryString(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener5, false);
+ r.readAsArrayBuffer(binaryFile);
+ expectedTestCount += 2;
+
+ //Test data-URI encoding on differing file sizes
+ is(dataurldata0.length % 3, 0, "Want to test data with length % 3 == 0");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata0),
+ dataurldata0.length,
+ "dataurl reading, %3 = 0");
+ r.readAsDataURL(dataUrlFile0);
+ expectedTestCount++;
+
+ is(dataurldata1.length % 3, 1, "Want to test data with length % 3 == 1");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata1),
+ dataurldata1.length,
+ "dataurl reading, %3 = 1");
+ r.readAsDataURL(dataUrlFile1);
+ expectedTestCount++;
+
+ is(dataurldata2.length % 3, 2, "Want to test data with length % 3 == 2");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata2),
+ dataurldata2.length,
+ "dataurl reading, %3 = 2");
+ r.readAsDataURL(dataUrlFile2),
+ expectedTestCount++;
+
+
+ // Test abort()
+ var abortHasRun = false;
+ var loadEndHasRun = false;
+ r = new FileReader();
+ r.onabort = function (event) {
+ is(abortHasRun, false, "abort should only fire once");
+ is(loadEndHasRun, false, "loadend shouldn't have fired yet");
+ abortHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onloadend = function (event) {
+ is(abortHasRun, true, "abort should fire before loadend");
+ is(loadEndHasRun, false, "loadend should only fire once");
+ loadEndHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onload = function() { ok(false, "load should not fire for aborted reads") };
+ r.onerror = function() { ok(false, "error should not fire for aborted reads") };
+ r.onprogress = function() { ok(false, "progress should not fire for aborted reads") };
+ var abortThrew = false;
+ try {
+ r.abort();
+ } catch(e) {
+ abortThrew = true;
+ }
+ is(abortThrew, true, "abort() must throw if not loading");
+ is(abortHasRun, false, "abort() is a no-op unless loading");
+ r.readAsText(asciiFile);
+ r.abort();
+ is(abortHasRun, true, "abort should fire sync");
+ is(loadEndHasRun, true, "loadend should fire sync");
+
+ // Test calling readAsX to cause abort()
+ var reuseAbortHasRun = false;
+ r = new FileReader();
+ r.onabort = function (event) {
+ is(reuseAbortHasRun, false, "abort should only fire once");
+ reuseAbortHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onload = function() { ok(false, "load should not fire for aborted reads") };
+ var abortThrew = false;
+ try {
+ r.abort();
+ } catch(e) {
+ abortThrew = true;
+ }
+ is(abortThrew, true, "abort() must throw if not loading");
+ is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
+ r.readAsText(asciiFile);
+ r.readAsText(asciiFile);
+ is(reuseAbortHasRun, true, "abort should fire sync");
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "reuse-as-abort reading");
+ expectedTestCount++;
+
+
+ // Test reading from nonexistent files
+ r = new FileReader();
+ var didThrow = false;
+ r.onerror = function (event) {
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onerror");
+ is(event.target.error.name, "NotFoundError", "error set to NotFoundError for nonexistent files");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ testHasRun();
+ };
+ r.onload = function (event) {
+ is(false, "nonexistent file shouldn't load! (FIXME: bug 1122788)");
+ testHasRun();
+ };
+ try {
+ r.readAsDataURL(nonExistingFile);
+ expectedTestCount++;
+ } catch(ex) {
+ didThrow = true;
+ }
+ // Once this test passes, we should test that onerror gets called and
+ // that the FileReader object is in the right state during that call.
+ is(didThrow, false, "shouldn't throw when opening nonexistent file, should fire error instead");
+
+
+ function getLoadHandler(expectedResult, expectedLength, testName) {
+ return function (event) {
+ is(event.target.readyState, FileReader.DONE,
+ "readyState in test " + testName);
+ is(event.target.error, null,
+ "no error in test " + testName);
+ is(event.target.result, expectedResult,
+ "result in test " + testName);
+ is(event.lengthComputable, true,
+ "lengthComputable in test " + testName);
+ is(event.loaded, expectedLength,
+ "loaded in test " + testName);
+ is(event.total, expectedLength,
+ "total in test " + testName);
+ testHasRun();
+ }
+ }
+
+ function getLoadHandlerForArrayBuffer(expectedResult, expectedLength, testName) {
+ return function (event) {
+ is(event.target.readyState, FileReader.DONE,
+ "readyState in test " + testName);
+ is(event.target.error, null,
+ "no error in test " + testName);
+ is(event.lengthComputable, true,
+ "lengthComputable in test " + testName);
+ is(event.loaded, expectedLength,
+ "loaded in test " + testName);
+ is(event.total, expectedLength,
+ "total in test " + testName);
+ is(event.target.result.byteLength, expectedLength,
+ "array buffer size in test " + testName);
+ var u8v = new Uint8Array(event.target.result);
+ is(String.fromCharCode.apply(String, u8v), expectedResult,
+ "array buffer contents in test " + testName);
+ u8v = null;
+ SpecialPowers.gc();
+ is(event.target.result.byteLength, expectedLength,
+ "array buffer size after gc in test " + testName);
+ u8v = new Uint8Array(event.target.result);
+ is(String.fromCharCode.apply(String, u8v), expectedResult,
+ "array buffer contents after gc in test " + testName);
+ testHasRun();
+ }
+ }
+
+ function testHasRun() {
+ //alert(testRanCounter);
+ ++testRanCounter;
+ if (testRanCounter == expectedTestCount) {
+ is(testSetupFinished, true, "test setup should have finished; check for exceptions");
+ is(onloadHasRunText, true, "onload text should have fired by now");
+ is(onloadHasRunBinary, true, "onload binary should have fired by now");
+ opener.destroy();
+ SimpleTest.finish();
+ }
+ }
+
+ testSetupFinished = true;
+}
+
+function convertToUTF16(s) {
+ res = "";
+ for (var i = 0; i < s.length; ++i) {
+ c = s.charCodeAt(i);
+ res += String.fromCharCode(c & 255, c >>> 8);
+ }
+ return res;
+}
+
+function convertToUTF8(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+function convertToDataURL(s) {
+ return "data:application/octet-stream;base64," + btoa(s);
+}
+
+</script>
+</pre>
+</body> </html>
diff --git a/dom/base/test/test_fileapi_slice.html b/dom/base/test/test_fileapi_slice.html
new file mode 100644
index 000000000..efe4aa259
--- /dev/null
+++ b/dom/base/test/test_fileapi_slice.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575946
+-->
+ <title>Test for Bug 575946</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="fileutils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=575946">Mozilla Bug 575946</a>
+<p id="display">
+ <canvas id=canvas width=1100 height=1100 hidden moz-opaque></canvas>
+ <canvas id=testcanvas hidden moz-opaque></canvas>
+ <input id="fileList" type="file"></input>
+</p>
+<div id="content" style="display: none">
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var fileNum = 1;
+SimpleTest.waitForExplicitFinish();
+
+// Create files containing data we'll test with. We'll want long
+// strings to ensure they span multiple buffers while loading
+
+// Create a decent-sized image
+cx = $("canvas").getContext('2d');
+var s = cx.canvas.width;
+var grad = cx.createLinearGradient(0, 0, s-1, s-1);
+for (i = 0; i < 0.95; i += .1) {
+ grad.addColorStop(i, "white");
+ grad.addColorStop(i + .05, "black");
+}
+grad.addColorStop(1, "white");
+cx.fillStyle = grad;
+cx.fillRect(0, 0, s-1, s-1);
+cx.fillStyle = "rgba(200, 0, 0, 0.9)";
+cx.fillRect(.1 * s, .1 * s, .7 * s, .7 * s);
+cx.strokeStyle = "rgba(0, 0, 130, 0.5)";
+cx.lineWidth = .14 * s;
+cx.beginPath();
+cx.arc(.6 * s, .6 * s, .3 * s, 0, Math.PI*2, true);
+cx.stroke();
+cx.closePath();
+cx.fillStyle = "rgb(0, 255, 0)";
+cx.beginPath();
+cx.arc(.1 * s, .8 * s, .1 * s, 0, Math.PI*2, true);
+cx.fill();
+cx.closePath();
+
+
+function imageLoadHandler(event) {
+ var origcanvas = $("canvas");
+ var testcanvas = $("testcanvas");
+ var image = event.target;
+ is(image.naturalWidth, origcanvas.width, "width correct");
+ is(image.naturalHeight, origcanvas.height, "height correct");
+
+ testcanvas.width = origcanvas.width;
+ testcanvas.height = origcanvas.height;
+ testcanvas.getContext("2d").drawImage(image, 0, 0);
+ // Do not use |is(testcanvas.toDataURL("image/png"), origcanvas.toDataURL("image/png"), "...");| that results in a _very_ long line.
+ var origDataURL = origcanvas.toDataURL("image/png");
+ var testDataURL = testcanvas.toDataURL("image/png");
+ is(testDataURL.length, origDataURL.length,
+ "Length of correct image data");
+ ok(testDataURL == origDataURL,
+ "Content of correct image data");
+
+ testHasRun();
+}
+
+var fileData =
+ atob(cx.canvas.toDataURL("image/png").substring("data:text/png;base64,".length + 1));
+var size = fileData.length;
+var testBinaryData = "";
+
+// This might fail if we dramatically improve the png encoder. If that happens
+// please increase the complexity or size of the image generated above to ensure
+// that we're testing with files that are large enough.
+ok(size > 65536, "test data sufficiently large");
+
+SpecialPowers.createFiles([{name: "basicTestFile", data: fileData}],
+ basicTest);
+
+function basicTest(files) {
+ var fileFile = files[0];
+
+ // Test that basic properties work
+ var memFile = cx.canvas.mozGetAsFile("image/png");
+ testSlice(memFile, size, "image/png", fileData, "memFile");
+ testSlice(fileFile, size, "", fileData, "fileFile");
+
+ // Try loading directly from slice into an image
+ for (var i = 0; i < 256; i++) {
+ testBinaryData += String.fromCharCode(i);
+ }
+ while (testBinaryData.length < 20000) {
+ testBinaryData += testBinaryData;
+ }
+
+ // image in the middle
+ SpecialPowers.createFiles([{name: "middleTestFile",
+ data: testBinaryData + fileData + testBinaryData}],
+ imageMiddleTest);
+}
+
+function imageMiddleTest(files) {
+ var imgfile = files[0];
+ is(imgfile.size, size + testBinaryData.length * 2, "correct file size (middle)");
+ var img = new Image;
+ img.src = URL.createObjectURL(imgfile.slice(testBinaryData.length, testBinaryData.length + size));
+ img.onload = imageLoadHandler;
+ expectedTestCount++;
+
+ // image at start
+ SpecialPowers.createFiles([{name: "startTestFile",
+ data: fileData + testBinaryData}],
+ imageStartTest);
+}
+
+function imageStartTest(files) {
+ var imgfile = files[0];
+ is(imgfile.size, size + testBinaryData.length, "correct file size (start)");
+ var img = new Image;
+ img.src = URL.createObjectURL(imgfile.slice(0, size));
+ img.onload = imageLoadHandler;
+ expectedTestCount++;
+
+ // image at end
+ SpecialPowers.createFiles([{name: "endTestFile",
+ data: testBinaryData + fileData}],
+ imageEndTest);
+}
+
+function imageEndTest(files) {
+ var imgfile = files[0];
+ is(imgfile.size, size + testBinaryData.length, "correct file size (end)");
+ var img = new Image;
+ img.src = URL.createObjectURL(imgfile.slice(testBinaryData.length, testBinaryData.length + size));
+ img.onload = imageLoadHandler;
+ expectedTestCount++;
+
+ // image past end
+ SpecialPowers.createFiles([{name: "pastEndTestFile",
+ data: testBinaryData + fileData}],
+ imagePastEndTest);
+}
+
+function imagePastEndTest(files) {
+ var imgfile = files[0];
+ is(imgfile.size, size + testBinaryData.length, "correct file size (past end)");
+ var img = new Image;
+ img.src = URL.createObjectURL(imgfile.slice(testBinaryData.length, testBinaryData.length + size + 1000));
+ img.onload = imageLoadHandler;
+ expectedTestCount++;
+}
+
+</script>
+</pre>
+</body>
+</html>
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 000000000..5e2f4ec30
--- /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 000000000..b0c2b01db
--- /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 type="application/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=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_getTranslationNodes.html b/dom/base/test/test_getTranslationNodes.html
new file mode 100644
index 000000000..e19a0cea7
--- /dev/null
+++ b/dom/base/test/test_getTranslationNodes.html
@@ -0,0 +1,227 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for nsIDOMWindowUtils.getTranslationNodes</title>
+ <script type="application/javascript" 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.wrap(window).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+
+
+ 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"
+ src="data:text/html,<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 000000000..8a815f49f
--- /dev/null
+++ b/dom/base/test/test_getTranslationNodes_limit.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for nsIDOMWindowUtils.getTranslationNodes</title>
+ <script type="application/javascript" 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.wrap(window).
+ QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+ getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+
+ 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 000000000..3d670b92d
--- /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 type="application/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=799875">Mozilla Bug 799875</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe src="data:text/html,<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 000000000..a315fe03e
--- /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 type="application/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=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 000000000..d5349829a
--- /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 type="application/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=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 000000000..ae4f46b94
--- /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 type="application/javascript" 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="data:text/html,<script>function f() { history.length; } window.onload = function() { var f = window.f; document.open(); document.close(); parent.continueTest(f); }</script>" </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 000000000..6eeaf52cf
--- /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 type="application/javascript" 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 000000000..68c8620db
--- /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 type="application/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=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 000000000..a466bd765
--- /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 type="application/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=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 000000000..dbd87471f
--- /dev/null
+++ b/dom/base/test/test_htmlcopyencoder.html
@@ -0,0 +1,196 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Test on the html copy encoder</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=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.Cc["@mozilla.org/layout/htmlCopyEncoder;1"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+ 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 000000000..57559fc00
--- /dev/null
+++ b/dom/base/test/test_htmlcopyencoder.xhtml
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+-->
+<head>
+ <title>Test the html copy encoder with XHTML</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=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.Cc["@mozilla.org/layout/htmlCopyEncoder;1"]
+ .createInstance(SpecialPowers.Ci.nsIDocumentEncoder);
+ 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_referrer.html b/dom/base/test/test_iframe_referrer.html
new file mode 100644
index 000000000..3c46096b9
--- /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 type="application/javascript" 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;version=1.7">
+
+ 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;version=1.7" 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 000000000..c65058fe8
--- /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 type="application/javascript" 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;version=1.7">
+
+ 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;version=1.7" 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 000000000..c4bd1d6ac
--- /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 type="application/javascript" 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;version=1.7">
+
+ 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;version=1.7" 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_img_referrer.html b/dom/base/test/test_img_referrer.html
new file mode 100644
index 000000000..16259bcc3
--- /dev/null
+++ b/dom/base/test/test_img_referrer.html
@@ -0,0 +1,189 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test img policy attribute for Bug 1166910</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<!--
+Testing that img referrer attribute is honoured correctly
+* Speculative parser loads (generate-img-policy-test)
+* regular loads (generate-img-policy-test2)
+* loading a single image multiple times with different policies (generate-img-policy-test3)
+* testing setAttribute and .referrer (generate-setAttribute-test)
+* regression tests that meta referrer is still working even if attribute referrers are enabled
+https://bugzilla.mozilla.org/show_bug.cgi?id=1166910
+-->
+
+<script type="application/javascript;version=1.7">
+
+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" ||
+ event.data.contains("childLoadComplete")) {
+ 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, aExpectedImg, aName) {
+ doXHR('/tests/dom/base/test/img_referrer_testserver.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, aExpectedImg[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/img_referrer_testserver.sjs?action=resetState',
+ advance,
+ function(xhr) {
+ ok(false, "error in reset state");
+ SimpleTest.finish();
+ });
+}
+
+/**
+ * testing if img referrer attribute is honoured (1165501)
+ */
+var tests = (function() {
+
+ // enable referrer attribute
+ yield SpecialPowers.pushPrefEnv({"set": [['network.http.enablePerElementReferrer', true]]}, advance);
+
+ var iframe = document.getElementById("testframe");
+ var sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test";
+
+ // setting img unsafe-url and meta origin - unsafe-url shall prevail (should use speculative load)
+ yield resetState();
+ var name = 'unsaf-url-with-meta-in-origin';
+ yield iframe.src = sjs + "&imgPolicy=" + escape('unsafe-url') + "&name=" + name + "&policy=" + escape('origin');
+ yield checkIndividualResults("unsafe-url (img) with origin in meta", ["full"], [name]);
+
+ // setting img no-referrer and meta default - no-referrer shall prevail (should use speculative load)
+ yield resetState();
+ name = 'no-referrer-with-meta-in-origin';
+ yield iframe.src = sjs + "&imgPolicy=" + escape('no-referrer')+ "&name=" + name + "&policy=" + escape('origin');
+ yield checkIndividualResults("no-referrer (img) with default in meta", ["none"], [name]);
+
+ // test referrer policy in regular load
+ yield resetState();
+ sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test2";
+ name = 'regular-load-unsafe-url';
+ yield iframe.src = sjs + "&imgPolicy=" + escape('unsafe-url') + "&name=" + name;
+ yield checkIndividualResults("unsafe-url in img", ["full"], [name]);
+
+ // test referrer policy in regular load with multiple images
+ var policies = ['unsafe-url', 'origin', 'no-referrer'];
+ var expected = ["full", "origin", "none"];
+ yield resetState();
+ sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test3";
+ name = 'multiple-images-'+policies[0]+'-'+policies[1]+'-'+policies[2];
+ yield iframe.src = sjs + "&imgPolicy1=" + escape(policies[0]) + "&imgPolicy2=" + escape(policies[1]) + "&imgPolicy3=" + escape(policies[2]) + "&name=" + name;
+ yield checkIndividualResults(policies[0]+", "+policies[1]+" and "+policies[2]+" in img", expected, [name+policies[0], name+policies[1], name+policies[2]]);
+
+ policies = ['origin', 'no-referrer', 'unsafe-url'];
+ expected = ["origin", "none", "full"];
+ yield resetState();
+ sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test3";
+ name = 'multiple-images-'+policies[0]+'-'+policies[1]+'-'+policies[2];
+ yield iframe.src = sjs + "&imgPolicy1=" + escape(policies[0]) + "&imgPolicy2=" + escape(policies[1]) + "&imgPolicy3=" + escape(policies[2]) + "&name=" + name;
+ yield checkIndividualResults(policies[0]+", "+policies[1]+" and "+policies[2]+" in img", expected, [name+policies[0], name+policies[1], name+policies[2]]);
+
+ policies = ['no-referrer', 'origin', 'unsafe-url'];
+ expected = ["none", "origin", "full"];
+ yield resetState();
+ sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test3";
+ name = 'multiple-images-'+policies[0]+'-'+policies[1]+'-'+policies[2];
+ yield iframe.src = sjs + "&imgPolicy1=" + escape(policies[0]) + "&imgPolicy2=" + escape(policies[1]) + "&imgPolicy3=" + escape(policies[2]) + "&name=" + name;
+ yield checkIndividualResults(policies[0]+", "+policies[1]+" and "+policies[2]+" in img", expected, [name+policies[0], name+policies[1], name+policies[2]]);
+
+ // regression tests that meta referrer is still working even if attribute referrers are enabled
+ yield resetState();
+ sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test4";
+ name = 'regular-load-no-referrer-meta';
+ yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name;
+ yield checkIndividualResults("no-referrer in meta (no img referrer policy), speculative load", ["none"], [name]);
+
+ yield resetState();
+ sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test5";
+ name = 'regular-load-no-referrer-meta';
+ yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name;
+ yield checkIndividualResults("no-referrer in meta (no img referrer policy), regular load", ["none"], [name]);
+
+ //test setAttribute
+ yield resetState();
+ sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-setAttribute-test1";
+ name = 'set-referrer-policy-attribute-before-src';
+ yield iframe.src = sjs + "&imgPolicy=" + escape('no-referrer') + "&policy=" + escape('unsafe-url') + "&name=" + name;
+ yield checkIndividualResults("no-referrer in img", ["none"], [name]);
+
+ yield resetState();
+ sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-setAttribute-test2";
+ name = 'set-referrer-policy-attribute-after-src';
+ yield iframe.src = sjs + "&imgPolicy=" + escape('no-referrer') + "&policy=" + escape('unsafe-url') + "&name=" + name;
+ yield checkIndividualResults("no-referrer in img", ["none"], [name]);
+
+ yield resetState();
+ sjs =
+ "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-setAttribute-test2";
+ name = 'set-invalid-referrer-policy-attribute-before-src-invalid';
+ yield iframe.src = sjs + "&imgPolicy=" + escape('invalid') + "&policy=" + escape('unsafe-url') + "&name=" + name;
+ yield checkIndividualResults("unsafe-url in meta, invalid in img", ["full"], [name]);
+
+ yield resetState();
+ sjs =
+ "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-setAttribute-test2";
+ name = 'set-invalid-referrer-policy-attribute-before-src-invalid';
+ yield iframe.src = sjs + "&imgPolicy=" + escape('default') + "&policy=" + escape('unsafe-url') + "&name=" + name;
+ yield checkIndividualResults("unsafe-url in meta, default in img", ["full"], [name]);
+
+ // complete. Be sure to yield so we don't call this twice.
+ yield SimpleTest.finish();
+})();
+
+</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 000000000..a000e9355
--- /dev/null
+++ b/dom/base/test/test_innersize_scrollport.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 919437</title>
+ <script type="application/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=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.setScrollPositionClampingScrollPortSize(newWidth, newHeight);
+ is(window.innerWidth, newWidth, "innerWidth not updated to scroll port width");
+ is(window.innerHeight, newHeight, "innerHeight not updated to scroll port height");
+
+ var innerWidthGetter = Object.getOwnPropertyDescriptor(window, "innerWidth").get;
+ var innerHeightGetter = Object.getOwnPropertyDescriptor(window, "innerHeight").get;
+
+ window.innerWidth = oldWidth;
+ window.innerHeight = oldHeight;
+ is(window.innerWidth, oldWidth, "Should have redefined innerWidth");
+ is(window.innerHeight, oldHeight, "Should have redefined innerWidth");
+ is(innerWidthGetter.call(window), newWidth, "innerWidth clobbered by direct set");
+ is(innerHeightGetter.call(window), newHeight, "innerHeight clobbered by direct set");
+
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", run, false);
+
+</script>
+</pre>
+</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 000000000..816fb331c
--- /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 000000000..e7875e3af
--- /dev/null
+++ b/dom/base/test/test_intersectionobservers.html
@@ -0,0 +1,1214 @@
+<!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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="next()">
+<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">
+
+ SpecialPowers.setIntPref("layout.visibility.min-notify-intersection-observers-interval-ms", 0);
+
+ 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: fn
+ });
+ }
+
+ var callbacks = [];
+ function callDelayed(fn, delay) {
+ callbacks.push({
+ fn: fn,
+ time: +new Date() + delay
+ });
+ }
+
+ requestAnimationFrame(function tick() {
+ var i = callbacks.length;
+ while (i--) {
+ var cb = callbacks[i];
+ if (+new Date() >= cb.time) {
+ SimpleTest.executeSoon(cb.fn);
+ callbacks.splice(i, 1);
+ }
+ }
+ requestAnimationFrame(tick);
+ });
+
+ function expect(val) {
+ return {
+ to: {
+ throwException: function (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: function (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: function () {
+ var callbacks = [];
+ var fn = function () {
+ fn.callCount++;
+ fn.lastCall = { args: arguments };
+ if (callbacks.length) {
+ callbacks.shift()();
+ }
+ };
+ fn.callCount = 0;
+ fn.lastCall = { args: [] };
+ fn.waitForNotification = (fn) => {
+ callbacks.push(fn);
+ };
+ 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 not 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(0);
+ done();
+ }, ASYNC_TIMEOUT);
+ });
+
+
+ it('does not trigger if target is not a descendant of the intersection root in the containing block chain',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: parentEl});
+
+ parentEl.style.position = 'static';
+ io.observe(targetEl2);
+ callDelayed(function() {
+ expect(spy.callCount).to.be(0);
+ done();
+ }, ASYNC_TIMEOUT);
+ });
+
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ ], 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ }
+ ], 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ function(done) {
+ targetEl1.style.left = '-25px';
+ callDelayed(function() {
+ expect(spy.callCount).to.be(2);
+ done();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ }
+ ], 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(2);
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ }
+ ], 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(3);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ expect(records[1].target).to.be(targetEl3);
+ expect(records[1].intersectionRatio).to.be(0.5);
+ expect(records[2].target).to.be(targetEl4);
+ expect(records[2].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(2);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ expect(records[1].target).to.be(targetEl4);
+ expect(records[1].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(3);
+ 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[2].target).to.be(targetEl4);
+ expect(records[2].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(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].target).to.be(targetEl2);
+ done();
+ }, ASYNC_TIMEOUT);
+ },
+ 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].target).to.be(targetEl1);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[1].target).to.be(targetEl2);
+ done();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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].target).to.be(targetEl3);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[1].target).to.be(targetEl4);
+ done();
+ }, ASYNC_TIMEOUT);
+ }
+ ], done);
+
+ });
+
+
+ it('handles zero-size targets within the root coordinate space',
+ function(done) {
+
+ io = new IntersectionObserver(function(records) {
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ }, {root: rootEl});
+
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '0px';
+ targetEl1.style.width = '0px';
+ targetEl1.style.height = '0px';
+ io.observe(targetEl1);
+ });
+
+
+ it('handles root/target elements not yet in the DOM', function(done) {
+
+ rootEl.parentNode.removeChild(rootEl);
+ targetEl1.parentNode.removeChild(targetEl1);
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ io.observe(targetEl1);
+ callDelayed(done, 0);
+ },
+ function(done) {
+ document.getElementById('fixtures').appendChild(rootEl);
+ callDelayed(function() {
+ expect(spy.callCount).to.be(0);
+ done();
+ }, ASYNC_TIMEOUT);
+ },
+ function(done) {
+ parentEl.insertBefore(targetEl1, targetEl2);
+ 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);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ }, ASYNC_TIMEOUT);
+ },
+ function(done) {
+ grandParentEl.parentNode.removeChild(grandParentEl);
+ 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);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ }, ASYNC_TIMEOUT);
+ },
+ function(done) {
+ rootEl.appendChild(targetEl1);
+ 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);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ }, ASYNC_TIMEOUT);
+ },
+ function(done) {
+ rootEl.parentNode.removeChild(rootEl);
+ 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);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ }, ASYNC_TIMEOUT);
+ }
+ ], 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;
+ }, 0);
+ });
+
+
+ it('supports CSS transitions and transforms', function(done) {
+
+ targetEl1.style.top = '220px';
+ targetEl1.style.left = '220px';
+
+ io = new IntersectionObserver(function(records) {
+ 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)';
+ }, 0);
+ });
+
+
+ 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");
+ });
+
+ });
+
+ describe('observe subframe', function () {
+
+ it('should not trigger if target and root are not in the same document',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ targetEl4.onload = function () {
+ targetEl5 = targetEl4.contentDocument.getElementById('target5');
+ io.observe(targetEl5);
+ callDelayed(function() {
+ expect(spy.callCount).to.be(0);
+ done();
+ }, ASYNC_TIMEOUT);
+ }
+
+ targetEl4.src = "intersectionobserver_iframe.html";
+
+ });
+
+ 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 should is set to null for cross-origin observations', function(done) {
+
+ window.onmessage = function (e) {
+ expect(e.data).to.be.ok();
+ done();
+ };
+
+ targetEl4.src = "http://example.org/tests/dom/base/test/intersectionobserver_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() {
+ lastestRecords = lastestRecords.concat(io.takeRecords());
+ });
+
+ callDelayed(function() {
+ expect(lastestRecords.length).to.be(1);
+ expect(lastestRecords[0].intersectionRatio).to.be(1);
+ done();
+ }, ASYNC_TIMEOUT);
+ });
+
+ });
+
+
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ }
+ ], 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();
+ }, ASYNC_TIMEOUT);
+ },
+ 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();
+ }, ASYNC_TIMEOUT);
+ }
+ ], 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.parentNode.removeChild(styles);
+ }
+
+
+ /**
+ * Removes the DOM fixtures from the page and resets the global references.
+ */
+ function removeFixtures() {
+ var fixtures = document.getElementById('fixtures');
+ fixtures.parentNode.removeChild(fixtures);
+
+ rootEl = null;
+ grandParentEl = null;
+ parentEl = null;
+ targetEl1 = null;
+ targetEl2 = null;
+ targetEl3 = null;
+ targetEl4 = null;
+ }
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>
diff --git a/dom/base/test/test_ipc_messagemanager_blob.html b/dom/base/test/test_ipc_messagemanager_blob.html
new file mode 100644
index 000000000..74eab2945
--- /dev/null
+++ b/dom/base/test/test_ipc_messagemanager_blob.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for OOP Blobs in MessageManager</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+ <script type="application/javascript;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ const childFrameURL =
+ "data:text/html,<!DOCTYPE HTML><html><body></body></html>";
+
+ function childFrameScript() {
+ "use strict";
+
+ addMessageListener("test:ipcClonedMessage", function(message) {
+ if (!(message.json instanceof Components.interfaces.nsIDOMBlob)) {
+ sendAsyncMessage(message.name, message.json);
+ return;
+ }
+
+ let reader = new FileReader();
+ reader.addEventListener("load", function() {
+ let response = reader.result == "this is a great success!" ?
+ message.json :
+ "error";
+ sendAsyncMessage(message.name, response);
+ });
+ reader.readAsText(message.json);
+ });
+ }
+
+ function runTests() {
+ function done() {
+ SpecialPowers.removePermission("browser", document);
+ SimpleTest.finish();
+ }
+
+ ok("Browser prefs set.");
+
+ let iframe = document.createElement("iframe");
+ SpecialPowers.wrap(iframe).mozbrowser = true;
+ iframe.id = "iframe";
+ iframe.src = childFrameURL;
+
+ iframe.addEventListener("mozbrowserloadend", function() {
+ ok(true, "Got iframe load event.");
+
+ const blobString = "this is a great success!";
+
+ const messages = [
+ "hi!",
+ "",
+ 2,
+ -.04,
+ 3432987324987239872948732982,
+ true,
+ false,
+ null,
+ 0,
+
+ // Make sure this one is always last.
+ new Blob(["this ", "is ", "a ", "great ", "success!"],
+ {"type" : "text\/plain"}),
+ ];
+ let receivedMessageIndex = 0;
+
+ let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+ mm.addMessageListener("test:ipcClonedMessage", function(message) {
+ let data = message.json;
+
+ if (data instanceof Blob) {
+ is(receivedMessageIndex, messages.length - 1, "Blob is last");
+ is (data.size,
+ messages[receivedMessageIndex].size,
+ "Correct blob size");
+ is (data.type,
+ messages[receivedMessageIndex].type,
+ "Correct blob type");
+
+ let result1, result2;
+
+ let reader1 = new FileReader();
+ reader1.onload = function() {
+ result1 = reader1.result == blobString ? reader1.result : "bad1";
+ if (result2) {
+ is(result1, result2, "Same results");
+ done();
+ }
+ };
+
+ let reader2 = new FileReader();
+ reader2.onload = function() {
+ result2 = reader2.result == blobString ? reader2.result : "bad2";
+ if (result1) {
+ is(result1, result2, "Same results");
+ done();
+ }
+ };
+
+ reader1.readAsText(data);
+ reader2.readAsText(messages[receivedMessageIndex]);
+ return;
+ }
+
+ is(message.json,
+ messages[receivedMessageIndex++],
+ "Got correct round-tripped response");
+ });
+ mm.loadFrameScript("data:,(" + childFrameScript.toString() + ")();",
+ false);
+
+ for (let message of messages) {
+ mm.sendAsyncMessage("test:ipcClonedMessage", message);
+ }
+ });
+
+ document.body.appendChild(iframe);
+ }
+
+ addEventListener("load", function() {
+ info("Got load event.");
+
+ SpecialPowers.addPermission("browser", true, document);
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.ipc.browser_frames.oop_by_default", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["browser.pagethumbnails.capturing_disabled", true]
+ ]
+ }, runTests);
+ });
+ </script>
+</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 000000000..c13492fd1
--- /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 type="application/javascript" 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;version=1.8">
+
+ 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;version=1.7" 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 000000000..58f70668a
--- /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 type="application/javascript" 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;version=1.8">
+
+ 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;version=1.7" 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_messagePort.html b/dom/base/test/test_messagePort.html
new file mode 100644
index 000000000..6f37e683e
--- /dev/null
+++ b/dom/base/test/test_messagePort.html
@@ -0,0 +1,115 @@
+<!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 type="application/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=912456">Mozilla Bug 912456</a>
+<script type="application/javascript">
+
+ function testTransfer() {
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ window.addEventListener('message', receiveMessage, false);
+ function receiveMessage(evt) {
+ ok(evt.data.port, "Port has been received!");
+
+ var a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ try {
+ evt.data.port.postMessage({port: a.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: a.port2}, [a.port2, a.port2]);
+ ok(false, "PostMessage should throw - no duplicate!");
+ } catch(e) {
+ ok(true, "PostMessage should throw - no duplicate!");
+ }
+
+ evt.data.port.postMessage({port: a.port2}, [a.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, false);
+ 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 a = new MessageChannel();
+ ok(a, "MessageChannel created");
+
+ evt.ports[0].postMessage(42, [a.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_principal.html b/dom/base/test/test_messagemanager_principal.html
new file mode 100644
index 000000000..cd5528e67
--- /dev/null
+++ b/dom/base/test/test_messagemanager_principal.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Principal in MessageManager</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+
+ var permManager = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+
+ SimpleTest.waitForExplicitFinish();
+
+ const childFrameURL =
+ "data:text/html,<!DOCTYPE HTML><html><body></body></html>";
+
+ function childFrameScript() {
+ "use strict";
+
+ addMessageListener("test:ipcMessage", function(message) {
+ sendAsyncMessage(message.name, "principal: " + (message.principal ? "OK" : "KO"));
+
+ sendAsyncMessage(message.name, "principal.appId: " +
+ ("appId" in message.principal ? "OK" : "KO"));
+
+ sendAsyncMessage(message.name, "principal.origin: " +
+ ("origin" in message.principal ? "OK" : "KO"));
+
+ sendAsyncMessage(message.name, "principal.isInIsolatedMozBrowserElement: " +
+ ("isInIsolatedMozBrowserElement" in message.principal ? "OK" : "KO"));
+
+ sendAsyncMessage(message.name, "DONE");
+ });
+ }
+
+ function runTests() {
+ ok("Browser prefs set.");
+
+ let iframe = document.createElement("iframe");
+ SpecialPowers.wrap(iframe).mozbrowser = true;
+ iframe.id = "iframe";
+ iframe.src = childFrameURL;
+
+ iframe.addEventListener("mozbrowserloadend", function() {
+ ok(true, "Got iframe load event.");
+
+ let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+ mm.addMessageListener("test:ipcMessage", function(message) {
+ // We need to wrap to access message.json, and unwrap to do the
+ // identity check.
+ var msg = SpecialPowers.unwrap(SpecialPowers.wrap(message).json);
+ if (/OK$/.exec(msg)) {
+ ok(true, msg);
+ } else if(/KO$/.exec(msg)) {
+ ok(true, false);
+ } else if (/DONE/.exec(msg)) {
+ permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ SimpleTest.finish();
+ }
+ });
+ mm.loadFrameScript("data:,(" + childFrameScript.toString() + ")();",
+ false);
+
+ mm.sendAsyncMessage("test:ipcMessage", 42, null, window.document.nodePrincipal);
+ });
+
+ document.body.appendChild(iframe);
+ }
+
+ addEventListener("load", function() {
+ info("Got load event.");
+
+ permManager.addFromPrincipal(window.document.nodePrincipal, "browser",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["browser.pagethumbnails.capturing_disabled", true]
+ ]
+ }, runTests);
+ });
+ </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 000000000..c50cdfeb3
--- /dev/null
+++ b/dom/base/test/test_messagemanager_send_principal.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Principal in MessageManager</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+ const Cu = Components.utils;
+
+ var permManager = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+
+ SimpleTest.waitForExplicitFinish();
+
+ const childFrameURL =
+ "data:text/html,<!DOCTYPE HTML><html><body></body></html>";
+
+ function childFrameScript() {
+ "use strict";
+
+ const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+
+ addMessageListener("test:content", function(message) {
+ sendAsyncMessage("test:result", "is nsIPrincipal: " +
+ (message.data instanceof Ci.nsIPrincipal ? "OK" : "KO"));
+
+ sendAsyncMessage("test:result", "principal.appId: " +
+ ("appId" in message.data ? "OK" : "KO"));
+
+ sendAsyncMessage("test:result", "principal.origin: " +
+ ("origin" in message.data ? "OK" : "KO"));
+
+ sendAsyncMessage("test:result", "principal.isInIsolatedMozBrowserElement: " +
+ ("isInIsolatedMozBrowserElement" in message.data ? "OK" : "KO"));
+ });
+
+ addMessageListener("test:system", function(message) {
+ sendAsyncMessage("test:result", "isSystemPrincipal: " +
+ (secMan.isSystemPrincipal(message.data) ? "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.createElement("iframe");
+ SpecialPowers.wrap(iframe).mozbrowser = true;
+ iframe.id = "iframe";
+ iframe.src = childFrameURL;
+
+ let sb = new Cu.Sandbox(['http://foo.example.com', 'http://bar.example.com']);
+ let ep = Components.utils.getObjectPrincipal(sb);
+
+ iframe.addEventListener("mozbrowserloadend", function() {
+ ok(true, "Got iframe load event.");
+
+ let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+ 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)) {
+ permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ SimpleTest.finish();
+ }
+ });
+ mm.loadFrameScript("data:,(" + childFrameScript.toString() + ")();",
+ false);
+
+ mm.sendAsyncMessage("test:content", window.document.nodePrincipal);
+
+ let system = Cc["@mozilla.org/systemprincipal;1"].
+ createInstance(Ci.nsIPrincipal);
+ mm.sendAsyncMessage("test:system", system);
+
+ mm.sendAsyncMessage("test:ep", ep);
+
+ let nullP = Cc["@mozilla.org/nullprincipal;1"].
+ createInstance(Ci.nsIPrincipal);
+ mm.sendAsyncMessage("test:null", nullP);
+ });
+
+ document.body.appendChild(iframe);
+ }
+
+ addEventListener("load", function() {
+ info("Got load event.");
+
+ permManager.addFromPrincipal(window.document.nodePrincipal, "browser",
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["browser.pagethumbnails.capturing_disabled", true]
+ ]
+ }, runTests);
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_messagemanager_targetchain.html b/dom/base/test/test_messagemanager_targetchain.html
new file mode 100644
index 000000000..3f3f8f60f
--- /dev/null
+++ b/dom/base/test/test_messagemanager_targetchain.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for EventTarget chain of MessageManagers</title>
+ <script type="application/javascript"
+ 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>
+
+ <script type="application/javascript;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ const browserFrameURL = "file_empty.html";
+ const contentFrameURL =
+ "data:text/html,<!DOCTYPE HTML><html><body><button id=\"target\">target</button></body></html>";
+
+ function frameScript() {
+ "use strict";
+ addEventListener("test-event", function (e) {
+ sendSyncMessage("test-event");
+ }, true);
+ }
+
+ function runTests() {
+ // messageIndex is incremented for each message/event received
+ let messageIndex = 0;
+
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("mozbrowser", true);
+ iframe.setAttribute("src", browserFrameURL);
+
+ iframe.addEventListener("mozbrowserloadend", function () {
+ info("First iframe loaded");
+ // First message manager
+ let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+ mm.addMessageListener("test-event", function onEvent(message) {
+ is(messageIndex, 0,
+ "first mm should be the first one to receive the test event");
+ messageIndex++;
+ });
+ mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false);
+
+ // Document in the middle
+ let doc1 = SpecialPowers.wrap(iframe).contentDocument;
+ doc1.addEventListener("test-event", function (e) {
+ ok(false, "content document shouldn't receive test event from child");
+ }, true);
+
+ let iframe2 = doc1.createElement("iframe");
+ iframe2.setAttribute("mozbrowser", true);
+ iframe2.setAttribute("src", browserFrameURL);
+
+ iframe2.addEventListener("mozbrowserloadend", function () {
+ info("Second iframe loaded");
+ // Second message manager
+ let mm2 = SpecialPowers.getBrowserFrameMessageManager(iframe2);
+ mm2.addMessageListener("test-event", function onEvent(message) {
+ is(messageIndex, 1,
+ "second mm should be the second one to receive the test event");
+ messageIndex++;
+ });
+ mm2.loadFrameScript("data:,(" + frameScript.toString() +")();", false);
+
+ // Third is the regular iframe
+ let doc2 = SpecialPowers.wrap(iframe2).contentDocument;
+ let iframe3 = doc2.createElement("iframe");
+ iframe3.setAttribute("src", contentFrameURL);
+
+ iframe3.addEventListener("load", function (e) {
+ info("Third iframe loaded");
+ let doc3 = SpecialPowers.wrap(iframe3).contentDocument;
+ let target = doc3.getElementById("target");
+ target.addEventListener("test-event", function onEvent(e) {
+ is(messageIndex, 2,
+ "target should be the last one to receive the test event");
+ messageIndex++;
+ SimpleTest.finish();
+ });
+
+ // Fire test event after load
+ SimpleTest.executeSoon(function () {
+ var event = new Event("test-event");
+ SpecialPowers.dispatchEvent(iframe3.contentWindow, target, event);
+ });
+ });
+ doc2.body.appendChild(iframe3);
+ });
+ doc1.body.appendChild(iframe2);
+ });
+ document.addEventListener("test-event", function (e) {
+ ok(false, "top document shouldn't receive test event from child");
+ }, true);
+ document.body.appendChild(iframe);
+ }
+
+ addEventListener("load", function() {
+ var principal = SpecialPowers.wrap(document).nodePrincipal;
+ SpecialPowers.pushPermissions([
+ { type: "browser", allow: 1, context: { url: principal.URI.spec,
+ originAttributes: {
+ appId: principal.appId
+ }}},
+ { type: "browser", allow: 1, context: { url: principal.URI.spec,
+ originAttributes: {
+ appId: principal.appId,
+ inIsolatedMozBrowser: true }}}
+ ], () => {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["dom.ipc.browser_frames.oop_by_default", false],
+ ]
+ }, runTests);
+ });
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_viewport0.html b/dom/base/test/test_meta_viewport0.html
new file mode 100644
index 000000000..0e0d8991c
--- /dev/null
+++ b/dom/base/test/test_meta_viewport0.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ let tests = [];
+
+ function fuzzeq(a, b, msg) {
+ ok(Math.abs(a - b) < 1e-6, msg);
+ }
+
+ tests.push(function test1() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ let info = getViewportInfo(800, 480);
+ fuzzeq(info.defaultZoom, 0.1, "initial scale is unspecified");
+ fuzzeq(info.minZoom, 0.1, "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");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test2() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.5),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ 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 nextTest() {
+ if (tests.length)
+ (tests.shift())();
+ else
+ SimpleTest.finish();
+ }
+ addEventListener("load", nextTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_viewport1.html b/dom/base/test/test_meta_viewport1.html
new file mode 100644
index 000000000..678931d4e
--- /dev/null
+++ b/dom/base/test/test_meta_viewport1.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ let tests = [];
+
+ tests.push(function test1() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test2() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.5),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ 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 nextTest() {
+ if (tests.length)
+ (tests.shift())();
+ else
+ SimpleTest.finish();
+ }
+ addEventListener("load", nextTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_viewport2.html b/dom/base/test/test_meta_viewport2.html
new file mode 100644
index 000000000..c31d1909e
--- /dev/null
+++ b/dom/base/test/test_meta_viewport2.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ let tests = [];
+
+ tests.push(function test1() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test2() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.5),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ 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 nextTest() {
+ if (tests.length)
+ (tests.shift())();
+ else
+ SimpleTest.finish();
+ }
+ addEventListener("load", nextTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_viewport3.html b/dom/base/test/test_meta_viewport3.html
new file mode 100644
index 000000000..cf1153d49
--- /dev/null
+++ b/dom/base/test/test_meta_viewport3.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ let tests = [];
+
+ tests.push(function test1() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test2() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.5),
+ function() {
+ // 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");
+
+ nextTest();
+ });
+ });
+
+ 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 nextTest() {
+ if (tests.length)
+ (tests.shift())();
+ else
+ SimpleTest.finish();
+ }
+ addEventListener("load", nextTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_viewport4.html b/dom/base/test/test_meta_viewport4.html
new file mode 100644
index 000000000..9e53419ae
--- /dev/null
+++ b/dom/base/test/test_meta_viewport4.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ let tests = [];
+
+ tests.push(function test1() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test2() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.5),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ 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 nextTest() {
+ if (tests.length)
+ (tests.shift())();
+ else
+ SimpleTest.finish();
+ }
+ addEventListener("load", nextTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_viewport5.html b/dom/base/test/test_meta_viewport5.html
new file mode 100644
index 000000000..739e5b7fe
--- /dev/null
+++ b/dom/base/test/test_meta_viewport5.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ let tests = [];
+
+ tests.push(function test1() {
+ let info = getViewportInfo(800, 480);
+ is(info.allowZoom, true, "user-scalable values are case-sensitive; 'NO' is not valid");
+
+ nextTest();
+ });
+
+ 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 nextTest() {
+ if (tests.length)
+ (tests.shift())();
+ else
+ SimpleTest.finish();
+ }
+ addEventListener("load", nextTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_viewport6.html b/dom/base/test/test_meta_viewport6.html
new file mode 100644
index 000000000..b003ed2c0
--- /dev/null
+++ b/dom/base/test/test_meta_viewport6.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ let tests = [];
+
+ tests.push(function test1() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test2() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.5),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ 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 nextTest() {
+ if (tests.length)
+ (tests.shift())();
+ else
+ SimpleTest.finish();
+ }
+ addEventListener("load", nextTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_meta_viewport7.html b/dom/base/test/test_meta_viewport7.html
new file mode 100644
index 000000000..373674c53
--- /dev/null
+++ b/dom/base/test/test_meta_viewport7.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>meta viewport test</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ let tests = [];
+
+ tests.push(function test1() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test2() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ 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");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test3() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ updateViewport("user-scalable=no");
+ let info = getViewportInfo(800, 480);
+ is(info.allowZoom, false, "zooming is explicitly disabled");
+
+ nextTest();
+ });
+ });
+
+ tests.push(function test4() {
+ SpecialPowers.pushPrefEnv(scaleRatio(1.0),
+ function() {
+ updateViewport("user-scalable=yes");
+ let info = getViewportInfo(800, 480);
+ is(info.allowZoom, true, "zooming is explicitly allowed");
+
+ nextTest();
+ });
+ });
+
+ 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 updateViewport(content) {
+ let meta = document.querySelector("meta[name=viewport]");
+ meta.content = content;
+ }
+
+ function nextTest() {
+ if (tests.length) {
+ (tests.shift())();
+ } else {
+ SimpleTest.finish();
+ }
+ }
+ addEventListener("load", nextTest);
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/test_mozMatchesSelector.html b/dom/base/test/test_mozMatchesSelector.html
new file mode 100644
index 000000000..3f163e07a
--- /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_mozbrowser_apis_allowed.html b/dom/base/test/test_mozbrowser_apis_allowed.html
new file mode 100644
index 000000000..86ff3e2ff
--- /dev/null
+++ b/dom/base/test/test_mozbrowser_apis_allowed.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Verify mozbrowser APIs are allowed with browser permission</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="mozbrowser_api_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<script type="application/javascript;version=1.8">
+ add_task(function*() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { "set": [["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]] },
+ resolve);
+ });
+ });
+
+ add_task(function*() {
+ // Create <iframe mozbrowser>
+ let frame = yield loadFrame({
+ mozbrowser: "true",
+ // FIXME: Bug 1270790
+ remote: true
+ });
+
+ // Verify that mozbrowser APIs are accessible
+ for (let method in METHODS) {
+ let { alwaysFails } = METHODS[method];
+ if (alwaysFails) {
+ ok(!(method in frame), `frame does not have method ${method}, ` +
+ `needs more permissions`);
+ } else {
+ ok(method in frame, `frame has method ${method}`);
+ }
+ }
+ for (let attribute of ATTRIBUTES) {
+ ok(attribute in frame, `frame has attribute ${attribute}`);
+ }
+ });
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_mozbrowser_apis_blocked.html b/dom/base/test/test_mozbrowser_apis_blocked.html
new file mode 100644
index 000000000..303c48625
--- /dev/null
+++ b/dom/base/test/test_mozbrowser_apis_blocked.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Verify mozbrowser APIs are blocked without browser permission</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="mozbrowser_api_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<script type="application/javascript;version=1.8">
+ add_task(function*() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { "set": [["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]] },
+ resolve);
+ });
+ });
+
+ add_task(function*() {
+ // Create <iframe mozbrowser>
+ let frame = yield loadFrame({
+ mozbrowser: "true"
+ });
+
+ // Verify that mozbrowser APIs are not accessible
+ for (let method in METHODS) {
+ ok(!(method in frame), `frame does not have method ${method}`);
+ }
+ for (let attribute of ATTRIBUTES) {
+ ok(!(attribute in frame), `frame does not have attribute ${attribute}`);
+ }
+ });
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_mozfiledataurl.html b/dom/base/test/test_mozfiledataurl.html
new file mode 100644
index 000000000..b842336d8
--- /dev/null
+++ b/dom/base/test/test_mozfiledataurl.html
@@ -0,0 +1,225 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
+ <title>Test for File urls</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="start()">
+<p id="display">
+<iframe id=inner></iframe>
+<iframe id=iframe></iframe>
+<img id=img onload="gen.send(event);">
+<audio id=audio onloadeddata="gen.send(event);">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+try {
+ URL.createObjectURL(undefined);
+} catch(e) { }
+
+window.addEventListener("message", function(e) {
+ gen.send(JSON.parse(e.data));
+}, false);
+
+const innerSameSiteURI = "file_mozfiledataurl_inner.html";
+const innerCrossSiteURI = "http://example.com/tests/dom/base/test/file_mozfiledataurl_inner.html"
+
+var fileNames = ["file_mozfiledataurl_img.jpg",
+ "file_mozfiledataurl_audio.ogg",
+ "file_mozfiledataurl_doc.html",
+ "file_mozfiledataurl_text.txt"];
+
+function start() {
+ let xhr = new XMLHttpRequest;
+ xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
+ xhr.send();
+ let basePath = xhr.responseText;
+
+ let fullFileNames = [];
+ for (let name of fileNames) {
+ fullFileNames.push(basePath + name);
+ }
+
+ var script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("create_file_objects.js"));
+
+ script.addMessageListener("created-file-objects", function handler(files) {
+ script.removeMessageListener("created-file-objects", handler);
+ gen = runTest(files);
+ gen.next();
+ });
+
+ script.sendAsyncMessage("create-file-objects", {fileNames: fullFileNames});
+};
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest([imgFile, audioFile, docFile, xhrFile]) {
+ inner = document.getElementById('inner');
+ img = document.getElementById('img');
+ audio = document.getElementById('audio');
+ iframe = document.getElementById('iframe');
+ inner.onload = function() { gen.send("inner loaded"); };
+
+ // Attempt to load a image in this document
+ var fileurl = URL.createObjectURL(imgFile);
+ img.src = fileurl;
+ var e = (yield);
+ is(e.type, "load", "loaded successfully");
+ is(img.width, 120, "correct width");
+ is(img.height, 90, "correct height");
+
+ // Revoke url and attempt to load a image in this document
+ img.src = "file_mozfiledataurl_img.jpg";
+ is((yield).type, "load", "successfull reset image");
+ URL.revokeObjectURL(fileurl);
+ todo(false, "urls need to act like 404s, not fail to parse");
+/* img.src = fileurl;
+ var e = (yield);
+ is(e.type, "error", "failed successfully");
+ isnot(img.width, 120, "correct error width");
+ isnot(img.height, 90, "correct error height");
+*/
+ // Generate new fileurl and make sure it's different from the old
+ var oldFileurl = fileurl;
+ fileurl = URL.createObjectURL(imgFile);
+ isnot(fileurl, oldFileurl, "URL.createObjectURL generated the same url twice");
+
+ // Attempt to load an image in a different same-origin document
+ inner.src = innerSameSiteURI;
+ yield undefined;
+ inner.contentWindow.postMessage(JSON.stringify({img:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "load", "loaded successfully");
+ is(res.width, 120, "correct width");
+ is(res.height, 90, "correct height");
+
+ // Attempt to load an image in a different cross-origin document
+ inner.src = innerCrossSiteURI;
+ yield undefined;
+ inner.contentWindow.postMessage(JSON.stringify({img:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "error", "failed successfully");
+ isnot(res.width, 120, "correct error width");
+ isnot(res.height, 90, "correct error height");
+
+ // Attempt to load an audio in this document
+ fileurl = URL.createObjectURL(audioFile);
+ audio.src = fileurl;
+ var e = (yield);
+ is(e.type, "loadeddata", "loaded successfully");
+
+ // Revoke url and attempt to load a audio in this document
+ audio.src = "file_mozfiledataurl_audio.ogg";
+ is((yield).type, "loadeddata", "successfully reset audio");
+ URL.revokeObjectURL(fileurl);
+ todo(false, "urls need to act like 404s, not fail to parse");
+/* img.src = fileurl;
+ var e = (yield);
+ is(e.type, "error", "failed successfully");
+ isnot(img.width, 120, "correct error width");
+ isnot(img.height, 90, "correct error height");
+*/
+ // Generate new fileurl and make sure it's different from the old
+ var oldFileurl = fileurl;
+ fileurl = URL.createObjectURL(audioFile);
+ isnot(fileurl, oldFileurl, "URL.createObjectURL generated the same url twice");
+
+ // Attempt to load an audio in a different same-origin document
+ inner.src = innerSameSiteURI;
+ yield undefined;
+ inner.contentWindow.postMessage(JSON.stringify({audio:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "loadeddata", "loaded successfully");
+
+ // Attempt to load an audio in a different cross-origin document
+ inner.src = innerCrossSiteURI;
+ yield undefined;
+ inner.contentWindow.postMessage(JSON.stringify({audio:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "error", "failed successfully");
+
+ // Attempt to load a HTML document in an iframe in this document
+ iframe.onload = function() { gen.next(); };
+ iframe.src = "file_mozfiledataurl_doc.html";
+ yield undefined;
+ is(iframe.contentDocument.getElementsByTagName("p")[0].textContent,
+ "This here is a document!",
+ "iframe loaded successfully");
+ is(iframe.contentDocument.getElementById("img").width, 120,
+ "image in iframe width");
+ is(iframe.contentDocument.getElementById("img").height, 90,
+ "image in iframe height");
+
+ // Attempt to load a HTML document in an iframe in this document, using file url
+ fileurl = URL.createObjectURL(docFile);
+ iframe.src = fileurl;
+ yield undefined;
+ is(iframe.contentDocument.getElementsByTagName("p")[0].textContent,
+ "This here is a document!",
+ "iframe loaded successfully");
+ isnot(iframe.contentDocument.getElementById("img").width, 120,
+ "failed image in iframe width");
+ isnot(iframe.contentDocument.getElementById("img").height, 90,
+ "failed image in iframe height");
+
+ // Attempt to load a HTML document in an iframe in inner document
+ inner.src = innerSameSiteURI;
+ is((yield), "inner loaded", "correct gen.next()");
+ inner.contentWindow.postMessage(JSON.stringify({iframe:"file_mozfiledataurl_doc.html"}), "*");
+ var res = (yield);
+ is(res.type, "load", "loaded successfully");
+ is(res.text, "This here is a document!", "loaded successfully");
+ is(res.imgWidth, 120, "correct width");
+
+ // Attempt to load a HTML document in an iframe in inner document, using file url
+ inner.contentWindow.postMessage(JSON.stringify({iframe:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "load", "loaded successfully");
+ is(res.text, "This here is a document!", "loaded successfully");
+ isnot(res.imgWidth, 120, "correct width");
+
+ // Attempt to load a HTML document in an iframe in inner cross-site document, using file url
+ inner.src = innerCrossSiteURI;
+ is((yield), "inner loaded", "correct gen.next()");
+ inner.contentWindow.postMessage(JSON.stringify({iframe:fileurl}), "*");
+ var res = (yield);
+ is(res.type, "error", "load failed successfully");
+
+ // Attempt to load file url using XHR
+ fileurl = URL.createObjectURL(xhrFile);
+ xhr = new XMLHttpRequest;
+ xhr.onload = function() { gen.send("XHR finished"); };
+ xhr.open("GET", fileurl);
+ xhr.send();
+ is((yield), "XHR finished", "correct gen.next()");
+ xhr.responseText == "Yarr, here be plaintext file, ya landlubber\n";
+
+ // Attempt to load file url using XHR in inner document
+ inner.src = innerSameSiteURI;
+ is((yield), "inner loaded", "correct gen.next()");
+ inner.contentWindow.postMessage(JSON.stringify({xhr:fileurl}), "*");
+ var res = (yield);
+ is(res.didThrow, undefined, "load successful");
+ is(res.text, "Yarr, here be plaintext file, ya landlubber\n", "load successful");
+
+ // Attempt to load file url using XHR
+ inner.src = innerCrossSiteURI;
+ is((yield), "inner loaded", "correct gen.next()");
+ inner.contentWindow.postMessage(JSON.stringify({xhr:fileurl}), "*");
+ var res = (yield);
+ is(res.didError, true, "load failed successfully");
+
+ SimpleTest.finish();
+
+ yield undefined;
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_mutationobserver_anonymous.html b/dom/base/test/test_mutationobserver_anonymous.html
new file mode 100644
index 000000000..98ff6e3a4
--- /dev/null
+++ b/dom/base/test/test_mutationobserver_anonymous.html
@@ -0,0 +1,265 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1034110
+-->
+<head>
+ <title>Test for Bug 1034110</title>
+ <script type="application/javascript" 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=1034110">Mozilla Bug 1034110</a>
+<style type="text/css">
+ #pseudo.before::before { content: "before"; }
+ #pseudo.after::after { content: "after"; }
+</style>
+<div id="pseudo"></div>
+<video id="video"></video>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/** Test for Bug 1034110 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function getWalker(node) {
+ return SpecialPowers.createDOMWalker(node, true);
+}
+
+function getFirstChild(parent) {
+ return SpecialPowers.unwrap(getWalker(parent).firstChild);
+}
+
+function getLastChild(parent) {
+ return SpecialPowers.unwrap(getWalker(parent).lastChild);
+}
+
+function assertSamePseudoElement(which, node1, node2) {
+ is(SpecialPowers.wrap(node1).nodeName, "_moz_generated_content_" + which,
+ "Correct pseudo element type");
+ is(node1, node2,
+ "Referencing the same ::after element");
+}
+
+window.onload = function () {
+ testOneAdded();
+};
+
+function testOneAdded() {
+ let parent = document.getElementById("pseudo");
+ var m = new MutationObserver(function(records, observer) {
+ is(records.length, 1, "Correct number of records");
+ is(records[0].type, "nativeAnonymousChildList", "Correct record type");
+ is(records[0].target, parent, "Correct target");
+
+ is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
+ is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+ observer.disconnect();
+ testAddedAndRemoved();
+ });
+
+ SpecialPowers.observeMutationEvents(m, parent, true);
+ parent.className = "before";
+}
+
+function testAddedAndRemoved() {
+ let parent = document.getElementById("pseudo");
+ let originalBeforeElement = getFirstChild(parent);
+ var m = new MutationObserver(function(records, observer) {
+ is(records.length, 2, "Correct number of records");
+ is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+ is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+ is(records[0].target, parent, "Correct target (1)");
+ is(records[1].target, parent, "Correct target (2)");
+
+ // Two records are sent - one for removed and one for added.
+ is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
+ is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+ assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
+
+ is(records[1].addedNodes.length, 1, "Should have got addedNodes");
+ assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
+ is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+ observer.disconnect();
+ testRemoved();
+ });
+
+ SpecialPowers.observeMutationEvents(m, parent, true);
+ parent.className = "after";
+}
+
+function testRemoved() {
+ let parent = document.getElementById("pseudo");
+ let originalAfterElement = getLastChild(parent);
+ var m = new MutationObserver(function(records, observer) {
+ is(records.length, 1, "Correct number of records");
+ is(records[0].type, "nativeAnonymousChildList", "Correct record type");
+ is(records[0].target, parent, "Correct target");
+
+ is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
+ is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+ assertSamePseudoElement("after", records[0].removedNodes[0], originalAfterElement);
+
+ observer.disconnect();
+ testMultipleAdded();
+ });
+
+ SpecialPowers.observeMutationEvents(m, parent, true);
+ parent.className = "";
+}
+
+function testMultipleAdded() {
+ let parent = document.getElementById("pseudo");
+ var m = new MutationObserver(function(records, observer) {
+ is(records.length, 2, "Correct number of records");
+ is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+ is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+ is(records[0].target, parent, "Correct target (1)");
+ is(records[1].target, parent, "Correct target (2)");
+
+ is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
+ is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+ is(records[1].addedNodes.length, 1, "Should have got addedNodes");
+ assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
+ is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+ observer.disconnect();
+ testRemovedDueToDisplay();
+ });
+
+ SpecialPowers.observeMutationEvents(m, parent, true);
+ parent.className = "before after";
+}
+
+function testRemovedDueToDisplay() {
+ let parent = document.getElementById("pseudo");
+ let originalBeforeElement = getFirstChild(parent);
+ let originalAfterElement = getLastChild(parent);
+ var m = new MutationObserver(function(records, observer) {
+ is(records.length, 2, "Correct number of records");
+ is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+ is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+ is(records[0].target, parent, "Correct target (1)");
+ is(records[1].target, parent, "Correct target (2)");
+
+ is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
+ is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+ assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
+
+ is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
+ is(records[1].removedNodes.length, 1, "Should have got removedNodes");
+ assertSamePseudoElement("after", records[1].removedNodes[0], originalAfterElement);
+
+ observer.disconnect();
+ testAddedDueToDisplay();
+ });
+
+ SpecialPowers.observeMutationEvents(m, parent, true);
+ parent.style.display = "none";
+}
+
+function testAddedDueToDisplay() {
+ let parent = document.getElementById("pseudo");
+ var m = new MutationObserver(function(records, observer) {
+ is(records.length, 2, "Correct number of records");
+ is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+ is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+ is(records[0].target, parent, "Correct target (1)");
+ is(records[1].target, parent, "Correct target (2)");
+
+ is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
+ is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+ is(records[1].addedNodes.length, 1, "Should have got addedNodes");
+ assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
+ is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+ observer.disconnect();
+ testDifferentTargetNoSubtree();
+ });
+
+ SpecialPowers.observeMutationEvents(m, parent, true);
+ parent.style.display = "block";
+}
+
+function testDifferentTargetNoSubtree() {
+ let parent = document.getElementById("pseudo");
+ var m = new MutationObserver(function(records, observer) {
+ ok(false,
+ "No mutation should fire when observing on a parent without subtree option.");
+ });
+ SpecialPowers.observeMutationEvents(m, document, true);
+ parent.style.display = "none";
+
+ // Wait for the actual mutation to come through, making sure that
+ // the original observer never fires.
+ var m2 = new MutationObserver(function(records, observer) {
+ ok(!getFirstChild(parent), "Pseudo element has been removed, but no mutation");
+ ok(!getLastChild(parent), "Pseudo element has been removed, but no mutation");
+ observer.disconnect();
+ testSubtree();
+ });
+ SpecialPowers.observeMutationEvents(m2, parent, true);
+}
+
+function testSubtree() {
+ let parent = document.getElementById("pseudo");
+ var m = new MutationObserver(function(records, observer) {
+ is(records.length, 2, "Correct number of records");
+ is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+ is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+ is(records[0].target, parent, "Correct target (1)");
+ is(records[1].target, parent, "Correct target (2)");
+
+ is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+ assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
+ is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+ is(records[1].addedNodes.length, 1, "Should have got addedNodes");
+ assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
+ is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+ observer.disconnect();
+ testDictionaryWithoutChromePriv();
+ });
+ SpecialPowers.observeMutationEvents(m, document, true, true);
+ parent.style.display = "block";
+}
+
+function testDictionaryWithoutChromePriv()
+{
+ var m = new MutationObserver(function() {});
+ try {
+ m.observe(document, { childList: true, get nativeAnonymousChildList() { throw "Foo1"; } } );
+ ok(true, "Shouldn't throw!");
+ } catch(ex) {
+ ok(false, "Did throw " + ex);
+ }
+
+ try {
+ m.observe(document, { childList: true, get animations() { throw "Foo2"; } } );
+ ok(true, "Shouldn't throw!");
+ } catch(ex) {
+ ok(false, "Did throw " + ex);
+ }
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_mutationobservers.html b/dom/base/test/test_mutationobservers.html
new file mode 100644
index 000000000..bde07c79c
--- /dev/null
+++ b/dom/base/test/test_mutationobservers.html
@@ -0,0 +1,935 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=641821
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 641821</title>
+ <script type="application/javascript" 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.indexOf("PASSED") >= 0) {
+ 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: function() {
+ document.getElementById("log").appendChild(document.createTextNode("DONE"));
+ },
+ waitForExplicitFinish: function() {}
+ }
+}
+
+function then(thenFn) {
+ setTimeout(function() {
+ if (thenFn) {
+ setTimeout(thenFn, 0);
+ } else {
+ SimpleTest.finish();
+ }
+ }, 0);
+}
+
+var m;
+var m2;
+var m3;
+var m4;
+
+// Checks basic parameter validation and normal 'this' handling.
+// Tests also basic attribute handling.
+function runTest() {
+ m = new M(function(){});
+ ok(m, "MutationObserver supported");
+
+ var e = null;
+ try {
+ m.observe(document, {});
+ } catch (ex) {
+ e = ex;
+ }
+ ok(e, "Should have thrown an exception");
+ is(e.name, "TypeError", "Should have thrown TypeError");
+
+ e = null;
+ try {
+ m.observe(document, { childList: true, attributeOldValue: true });
+ } catch (ex) {
+ e = ex;
+ }
+ ok(!e, "Shouldn't have thrown an exception");
+
+ e = null;
+ try {
+ m.observe(document, { childList: true, attributeFilter: ["foo"] });
+ } catch (ex) {
+ e = ex;
+ }
+ ok(!e, "Shouldn't have thrown an exception");
+
+ e = null;
+ try {
+ m.observe(document, { childList: true, characterDataOldValue: true });
+ } catch (ex) {
+ e = ex;
+ }
+ ok(!e, "Shouldn't have thrown an exception");
+
+ e = null;
+ try {
+ m.observe(document);
+ } catch (ex) {
+ e = ex;
+ }
+ ok(e, "Should have thrown an exception");
+
+ 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"] });
+ is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true);
+ is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter.length, 1)
+ is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeFilter[0], "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 });
+ is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributes, true)
+ is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].attributeOldValue, true)
+ is(SpecialPowers.wrap(div).getBoundMutationObservers()[0].getObservingInfo()[0].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 });
+
+ is(SpecialPowers.wrap(div).getBoundMutationObservers().length, 3)
+ is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].characterData, true)
+ is(SpecialPowers.wrap(div).getBoundMutationObservers()[2].getObservingInfo()[0].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.removeChild(div.firstChild);
+}
+
+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 });
+ is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].childList, true)
+ is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterData, true)
+ is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].characterDataOldValue, true)
+ is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo()[0].subtree, true)
+ ok(SpecialPowers.compare(SpecialPowers.wrap(df).getBoundMutationObservers()[0].mutationCallback, callback))
+ m.observe(div, { childList: true });
+ is(SpecialPowers.wrap(df).getBoundMutationObservers()[0].getObservingInfo().length, 2)
+
+ // 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(testAdoptNode);
+ 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 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(testModalDialog);
+}
+
+function testModalDialog() {
+ var didHandleCallback = false;
+ 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();
+ m = null;
+ didHandleCallback = true;
+ });
+ m.observe(div, { childList: true });
+ div.innerHTML = "<span><span>foo</span></span>";
+ try {
+ window.showModalDialog("mutationobserver_dialog.html");
+ ok(didHandleCallback, "Should have called the callback while showing modal dialog!");
+ } catch(e) {
+ todo(false, "showModalDialog not implemented on this platform");
+ }
+ 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 });
+ // Note, [0] points to a mutation observer which is there for a leak test!
+ ok(SpecialPowers.compare(SpecialPowers.wrap(div).getBoundMutationObservers()[1], m));
+ 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() {
+ var 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() {
+ var m = new M(function(records, observer) {
+ is(records.length, 1);
+ is(records[0].type, "attributes", "Should have got attributes");
+ observer.disconnect();
+ then(testInsideShadowDOM);
+ });
+ m.observe(div, {
+ attributes: true,
+ childList: true,
+ characterData: true,
+ subtree: true
+ })
+ var sr = div.createShadowRoot();
+ 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 testInsideShadowDOM() {
+ var m = new M(function(records, observer) {
+ is(records.length, 4);
+ is(records[0].type, "childList");
+ is(records[1].type, "attributes");
+ is(records[2].type, "characterData");
+ is(records[3].type, "childList");
+ observer.disconnect();
+ then(testMarquee);
+ });
+ var sr = div.createShadowRoot();
+ m.observe(sr, {
+ attributes: true,
+ childList: true,
+ characterData: true,
+ subtree: true
+ });
+
+ sr.innerHTML = "<div" + ">text</" + "div>";
+ sr.firstChild.setAttribute("foo", "bar");
+ sr.firstChild.firstChild.data = "text2";
+ sr.firstChild.appendChild(document.createElement("div"));
+ div.setAttribute("foo", "bar2");
+
+}
+
+function testMarquee() {
+ var 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");
+ var 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");
+ var 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");
+ var 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");
+ var 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 nativeAnonymousChildList
+ try {
+ var mo = new M(function(records, observer) { });
+ mo.observe(div, { nativeAnonymousChildList: 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 000000000..e2cc572ca
--- /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 type="application/javascript" 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 000000000..d2c5133e8
--- /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 type="text/javascript" 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();
+ }, false);
+
+ document.getElementById('content').appendChild(ifr);
+ }
+ );
+
+ SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</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 000000000..b48e640e6
--- /dev/null
+++ b/dom/base/test/test_navigator_hardwareConcurrency.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Navigator.hardwareConcurrency</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ var x = navigator.hardwareConcurrency;
+ is(typeof x, "number", "hardwareConcurrency should be a number.");
+ ok(x > 0, "hardwareConcurrency should be greater than 0.");
+
+ </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 000000000..7b986aecb
--- /dev/null
+++ b/dom/base/test/test_navigator_language.html
@@ -0,0 +1,212 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=889335
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for NavigatorLanguage</title>
+ <script type="application/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=889335">Mozilla Bug 889335</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript;version=1.7">
+ "use strict";
+
+ SimpleTest.waitForExplicitFinish();
+
+ /** Test for NavigatorLanguage **/
+ var actualLanguageChangesFromHandler = 0;
+ var actualLanguageChangesFromAVL = 0;
+ var expectedLanguageChanges = 0;
+
+ var testValues = [
+ { accept_languages: 'foo', language: 'foo', languages: ['foo'] },
+ { accept_languages: '', language: '', languages: [] },
+ { 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(data) {
+ SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', data.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 > 0) {
+ 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.src = 'data:text/html,<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.xul b/dom/base/test/test_navigator_resolve_identity_xrays.xul
new file mode 100644
index 000000000..cf3e23d9f
--- /dev/null
+++ b/dom/base/test/test_navigator_resolve_identity_xrays.xul
@@ -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=985827
+-->
+<window title="Mozilla Bug 985827"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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();
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ addLoadEvent(function() {
+ var iframe = document.getElementById("t");
+
+ var dir = "chrome://mochitests/content/chrome/dom/base/test/";
+ iframe.src = dir + "file_navigator_resolve_identity_xrays.xul";
+ iframe.onload = function() { finish(); };
+
+ function finish() {
+ SimpleTest.finish();
+ }
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/test_noAudioNotification.html b/dom/base/test/test_noAudioNotification.html
new file mode 100644
index 000000000..18ac833a8
--- /dev/null
+++ b/dom/base/test/test_noAudioNotification.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for video controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var observer = {
+ observe: function(subject, topic, data) {
+ ok(false, "should not receive audio-playback notification!");
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var video = document.createElement("video");
+video.loop = true;
+video.src = "noaudio.webm";
+
+video.onplay = video.onpause = function() {
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ runTest();
+ });
+ });
+ });
+};
+
+var tests = [
+ function() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ video.play();
+ },
+
+ function() {
+ video.pause();
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_noAudioNotificationOnMutedElement.html b/dom/base/test/test_noAudioNotificationOnMutedElement.html
new file mode 100644
index 000000000..e9cdd2759
--- /dev/null
+++ b/dom/base/test/test_noAudioNotificationOnMutedElement.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ if (expectedNotification !== null) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ } else {
+ ok(false, "should not receive audio-playback notification!");
+ }
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.loop = true;
+
+var tests = [
+ function() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+
+ audio.src = "audio.ogg";
+ // Wait for the audio metadata to become available so that we have an audio track.
+ audio.onloadedmetadata = runTest;
+ },
+
+ // Verify that muting and unmuting dispatches the events as expected.
+ function() {
+ expectedNotification = 'active';
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-nonaudible';
+ audio.muted = true;
+ },
+
+ function() {
+ expectedNotification = 'active';
+ audio.muted = false;
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ audio.pause();
+ },
+
+ // Verify that no events are dispatched when muted.
+ function() {
+ expectedNotification = 'active';
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-nonaudible';
+ audio.muted = true;
+ },
+
+ function() {
+ expectedNotification = null;
+ audio.onpause = function() {
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ audio.onpause = null;
+ runTest();
+ });
+ });
+ });
+ };
+ audio.pause();
+ },
+
+ function() {
+ expectedNotification = null;
+ audio.muted = false;
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ runTest();
+ });
+ });
+ });
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_noAudioNotificationOnMutedOrVolume0Element.html b/dom/base/test/test_noAudioNotificationOnMutedOrVolume0Element.html
new file mode 100644
index 000000000..0e4cc93f0
--- /dev/null
+++ b/dom/base/test/test_noAudioNotificationOnMutedOrVolume0Element.html
@@ -0,0 +1,162 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ if (expectedNotification !== null) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ } else {
+ ok(false, "should not receive audio-playback notification!");
+ }
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.loop = true;
+
+var tests = [
+ function() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+
+ audio.src = "audio.ogg";
+ // Wait for the audio metadata to become available so that we have an audio track.
+ audio.onloadedmetadata = runTest;
+ },
+
+ // Verify that unmuting when the volume is 0 doesn't dispatch the events.
+ function() {
+ expectedNotification = 'active';
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-nonaudible';
+ audio.muted = true;
+ },
+
+ function() {
+ expectedNotification = null;
+ audio.volume = 0;
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ runTest();
+ });
+ });
+ });
+ },
+
+ function() {
+ expectedNotification = null;
+ audio.muted = false;
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ runTest();
+ });
+ });
+ });
+ },
+
+ function() {
+ expectedNotification = 'active';
+ audio.volume = 0.5;
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ audio.pause();
+ },
+
+ // Verify that raising the volume when muted doesn't dispatch the events.
+ function() {
+ expectedNotification = 'active';
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-nonaudible';
+ audio.muted = true;
+ },
+
+ function() {
+ expectedNotification = null;
+ audio.volume = 0;
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ runTest();
+ });
+ });
+ });
+ },
+
+ function() {
+ expectedNotification = null;
+ audio.volume = 0.5;
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ runTest();
+ });
+ });
+ });
+ },
+
+ function() {
+ expectedNotification = 'active';
+ audio.muted = false;
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ audio.pause();
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_noAudioNotificationOnVolume0Element.html b/dom/base/test/test_noAudioNotificationOnVolume0Element.html
new file mode 100644
index 000000000..530dc88bf
--- /dev/null
+++ b/dom/base/test/test_noAudioNotificationOnVolume0Element.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ if (expectedNotification !== null) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ } else {
+ ok(false, "should not receive audio-playback notification!");
+ }
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.loop = true;
+
+var tests = [
+ function() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+
+ audio.src = "audio.ogg";
+ // Wait for the audio metadata to become available so that we have an audio track.
+ audio.onloadedmetadata = runTest;
+ },
+
+ // Verify that muting and unmuting dispatches the events as expected.
+ function() {
+ expectedNotification = 'active';
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-nonaudible';
+ audio.volume = 0;
+ },
+
+ function() {
+ expectedNotification = 'active';
+ audio.volume = 1;
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ audio.pause();
+ },
+
+ // Verify that no events are dispatched when volume is set to 0..
+ function() {
+ expectedNotification = 'active';
+ audio.play();
+ },
+
+ function() {
+ expectedNotification = 'inactive-nonaudible';
+ audio.volume = 0;
+ },
+
+ function() {
+ expectedNotification = null;
+ audio.onpause = function() {
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ audio.onpause = null;
+ runTest();
+ });
+ });
+ });
+ };
+ audio.pause();
+ },
+
+ function() {
+ expectedNotification = null;
+ audio.volume = 1;
+ // Yield to the event loop a few times to make sure that audio-playback is not dispatched.
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.executeSoon(function() {
+ runTest();
+ });
+ });
+ });
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_noWebAudioNotification.html b/dom/base/test/test_noWebAudioNotification.html
new file mode 100644
index 000000000..794f92d57
--- /dev/null
+++ b/dom/base/test/test_noWebAudioNotification.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for video controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("Testing an event not happening");
+
+var observer = {
+ observe: function(subject, topic, data) {
+ ok(false, "should not receive audio-playback notification!");
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var ac;
+
+var tests = [
+ function() {
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ ac = new AudioContext();
+ setTimeout(runTest, 100);
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+runTest();
+
+</script>
+</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 000000000..a8aa8934f
--- /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 type="application/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=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.parentNode.removeChild(testtag);
+
+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_nonascii_blob_url.html b/dom/base/test/test_nonascii_blob_url.html
new file mode 100644
index 000000000..ce5d050e2
--- /dev/null
+++ b/dom/base/test/test_nonascii_blob_url.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test blob URL for non-ascii domain</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="fileutils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content"></div>
+<script class="testbody" type="text/javascript;version=1.7">
+
+var iframe = document.createElement('iframe');
+iframe.src = 'http://xn--exmple-cua.test/tests/dom/base/test/file_nonascii_blob_url.html';
+iframe.onload = function() {
+ iframe.contentWindow.postMessage('hello world', '*');
+ onmessage = function(e) {
+ is(e.data, 'hello world', "Blob URL for non-ascii domain works");
+ SimpleTest.finish();
+ }
+}
+
+document.getElementById('content').appendChild(iframe);
+SimpleTest.waitForExplicitFinish();
+
+</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 000000000..6548668c0
--- /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 type="application/javascript" 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("chrome://browser/content/browser.xul");
+ 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 000000000..f3f1e6196
--- /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 type="application/javascript" 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_orientation_alternate.html b/dom/base/test/test_orientation_alternate.html
new file mode 100644
index 000000000..7276ad82c
--- /dev/null
+++ b/dom/base/test/test_orientation_alternate.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for ScreenOrientation API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="orientationcommon.js"></script>
+<div id="log"></div>
+<script>
+async_test(function(t) {
+ var originalOrientation = screen.orientation.type;
+ var alternateOrientation = originalOrientation == "portrait-primary" ?
+ "landscape-primary" : "portrait-primary";
+ var p = specialPowersLock(alternateOrientation);
+ p.then(function() {
+ t.step(function() { assert_equals(screen.orientation.type, alternateOrientation); });
+ return specialPowersLock(originalOrientation);
+ }).then(function() {
+ t.step(function() { assert_equals(screen.orientation.type, originalOrientation); });
+ return specialPowersUnlock();
+ }).then(function() {
+ t.done();
+ }).catch(t.step_func(function(err) {
+ assert_unreached("Error setting orientation: " + err);
+ t.done();
+ }));
+}, "Test locking and unlocking orientation.");
+</script>
diff --git a/dom/base/test/test_orientation_frame.html b/dom/base/test/test_orientation_frame.html
new file mode 100644
index 000000000..748b3264e
--- /dev/null
+++ b/dom/base/test/test_orientation_frame.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for ScreenOrientation API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="orientationcommon.js"></script>
+<div id="log"></div>
+<iframe id="frame"></iframe>
+<script>
+async_test(function(t) {
+ var originalOrientation = screen.orientation.type;
+ var alternateOrientation = originalOrientation == "portrait-primary" ?
+ "landscape-primary" : "portrait-primary";
+
+ window.addEventListener("message", function(event) {
+ t.step(function() { assert_array_equals(event.data, [alternateOrientation, originalOrientation]); });
+ t.done();
+ }, false);
+
+ var frame = document.getElementById("frame");
+ frame.src = "file_record_orientation.html";
+
+ frame.onload = function() {
+ var p = specialPowersLock(alternateOrientation);
+ p.then(function() {
+ return specialPowersLock(originalOrientation);
+ }).then(function() {
+ return specialPowersUnlock();
+ }).then(function() {
+ frame.contentWindow.postMessage("report", "*");
+ }).catch(t.step_func(function(err) {
+ assert_unreached("Error setting orientation: " + err);
+ t.done();
+ }));
+ };
+}, "Test orientation change event in frame.");
+</script>
diff --git a/dom/base/test/test_orientation_frame_lock.html b/dom/base/test/test_orientation_frame_lock.html
new file mode 100644
index 000000000..f8b7a65d0
--- /dev/null
+++ b/dom/base/test/test_orientation_frame_lock.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for ScreenOrientation API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="orientationcommon.js"></script>
+<div id="log"></div>
+<iframe sandbox="allow-scripts allow-orientation-lock" id="frame"></iframe>
+<script>
+async_test(function(t) {
+ var originalOrientation = screen.orientation.type;
+ var alternateOrientation = originalOrientation == "portrait-primary" ?
+ "landscape-primary" : "portrait-primary";
+
+ var orientationChanges = [];
+ window.screen.orientation.onchange = function() {
+ orientationChanges.push(screen.orientation.type);
+ };
+
+ window.addEventListener("message", function(event) {
+ t.step(function() {
+ assert_equals(event.data, "success");
+ assert_array_equals(orientationChanges, [ alternateOrientation ]);
+ });
+
+ // Return the orientation to its original settings.
+ var p = specialPowersLock(originalOrientation);
+ p.then(function() {
+ return specialPowersUnlock();
+ }).then(function() {
+ t.done();
+ }).catch(t.step_func(function(err) {
+ assert_unreached("Error setting orientation: " + err);
+ t.done();
+ }));
+ }, false);
+
+ var frame = document.getElementById("frame");
+ frame.src = "http://example.com/tests/dom/base/test/file_lock_orientation.html";
+
+ frame.onload = function() {
+ assert_array_equals(orientationChanges, []);
+ frame.contentWindow.postMessage(alternateOrientation, "*");
+ };
+}, "Test orientation lock from within a frame with sandbox permission.");
+</script>
diff --git a/dom/base/test/test_orientation_sandbox_no_lock.html b/dom/base/test/test_orientation_sandbox_no_lock.html
new file mode 100644
index 000000000..e020a6daa
--- /dev/null
+++ b/dom/base/test/test_orientation_sandbox_no_lock.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for ScreenOrientation API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="orientationcommon.js"></script>
+<div id="log"></div>
+<iframe sandbox="allow-scripts" id="frame"></iframe>
+<script>
+async_test(function(t) {
+ var originalOrientation = screen.orientation.type;
+ var alternateOrientation = originalOrientation == "portrait-primary" ?
+ "landscape-primary" : "portrait-primary";
+
+ var orientationChanges = [];
+ window.screen.orientation.onchange = function() {
+ orientationChanges.push(screen.orientation.type);
+ };
+
+ window.addEventListener("message", function(event) {
+ t.step(function() {
+ assert_equals(event.data, "error");
+ assert_array_equals(orientationChanges, []);
+ t.done();
+ });
+ }, false);
+
+ var frame = document.getElementById("frame");
+ frame.src = "http://example.com/tests/dom/base/test/file_lock_orientation.html";
+
+ frame.onload = function() {
+ assert_array_equals(orientationChanges, []);
+ frame.contentWindow.postMessage(alternateOrientation, "*");
+ };
+}, "Test orientation lock from within a frame without sandbox permission.");
+</script>
diff --git a/dom/base/test/test_pluginAudioNotification.html b/dom/base/test/test_pluginAudioNotification.html
new file mode 100644
index 000000000..399a4307f
--- /dev/null
+++ b/dom/base/test/test_pluginAudioNotification.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+<iframe></iframe>
+
+<script type="application/javascript">
+
+// Copied from /dom/plugins/test/mochitest/utils.js
+function getTestPlugin(pluginName) {
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ var name = pluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+// Copied from /dom/plugins/test/mochitest/utils.js
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+ if (!oldEnabledState) {
+ return;
+ }
+ var plugin = getTestPlugin(pluginName);
+ while (plugin.enabledState != newEnabledState) {
+ // Run a nested event loop to wait for the preference change to
+ // propagate to the child. Yuck!
+ SpecialPowers.Services.tm.currentThread.processNextEvent(true);
+ }
+ SimpleTest.registerCleanupFunction(function() {
+ SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+ });
+}
+setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+var iframe = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ SimpleTest.executeSoon(runTest);
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var tests = [
+ function() {
+ iframe = document.querySelector("iframe");
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ iframe.src = "file_pluginAudio.html";
+ },
+
+ function() {
+ info("=== Mute plugin ===");
+ ok(!iframe.contentWindow.pluginMuted(), "Plugin should not be muted");
+ iframe.contentWindow.toggleMuteState(true);
+ ok(iframe.contentWindow.pluginMuted(), "Plugin should be muted");
+ expectedNotification = 'inactive-nonaudible';
+ },
+
+ function() {
+ info("=== unmute plugin ==");
+ ok(iframe.contentWindow.pluginMuted(), "Plugin should be muted");
+ iframe.contentWindow.toggleMuteState(false);
+ ok(!iframe.contentWindow.pluginMuted(), "Plugin should not be muted");
+ expectedNotification = 'active';
+ },
+
+ function() {
+ info("=== stop audio ==");
+ expectedNotification = 'inactive-pause';
+ iframe.contentWindow.stopAudio();
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+onload = runTest;
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_pluginMutedBeforePlay.html b/dom/base/test/test_pluginMutedBeforePlay.html
new file mode 100644
index 000000000..658875dc9
--- /dev/null
+++ b/dom/base/test/test_pluginMutedBeforePlay.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Mute window before the plugin starts playing</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+<iframe></iframe>
+
+<script type="application/javascript">
+
+// Copied from /dom/plugins/test/mochitest/utils.js
+function getTestPlugin(pluginName) {
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ var name = pluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+// Copied from /dom/plugins/test/mochitest/utils.js
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+ if (!oldEnabledState) {
+ return;
+ }
+ var plugin = getTestPlugin(pluginName);
+ while (plugin.enabledState != newEnabledState) {
+ // Run a nested event loop to wait for the preference change to
+ // propagate to the child. Yuck!
+ SpecialPowers.Services.tm.currentThread.processNextEvent(true);
+ }
+ SimpleTest.registerCleanupFunction(function() {
+ SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+ });
+}
+setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var iframe = document.querySelector("iframe");
+ iframe.src = "file_pluginAudioNonAutoStart.html";
+
+ function muteBeforePlay() {
+ ok(!iframe.contentWindow.pluginMuted(), "Plugin should not be muted");
+ iframe.contentWindow.toggleMuteState(true);
+
+ iframe.contentWindow.startAudio();
+ ok(iframe.contentWindow.pluginMuted(), "Plugin should still be muted after playing");
+
+ iframe.contentWindow.stopAudio();
+ SimpleTest.finish();
+ }
+
+ iframe.onload = function() {
+ ok(true, "Already load iframe.");
+ muteBeforePlay();
+ }
+}
+
+onload = runTest;
+
+</script>
+</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 000000000..41d41be58
--- /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 type="text/javascript" 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();
+}, false);
+
+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 000000000..5ababc1d4
--- /dev/null
+++ b/dom/base/test/test_postMessage_originAttributes.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test window.postMessages from system principal to window with non-default originAttributes</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.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(function*() {
+ yield SpecialPowers.pushPrefEnv(
+ { "set": [["network.disable.ipc.security", true]] });
+});
+
+add_task(function*() {
+ let iframe = document.querySelector("#target-iframe");
+
+ let win = SpecialPowers.wrap(iframe).contentWindow;
+ let docShell = win.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIDocShell);
+
+ // Add private browsing ID to docShell origin and load document.
+ docShell.setOriginAttributes({privateBrowsingId: 1});
+
+ yield 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");
+ yield new Promise(resolve => setTimeout(resolve, 0));
+
+ SimpleTest.endMonitorConsole();
+ yield 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 000000000..ab3e4ecec
--- /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 type="application/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=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, false);
+ 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, false);
+ 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) {
+ ok(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.html b/dom/base/test/test_postMessages.html
new file mode 100644
index 000000000..13a284270
--- /dev/null
+++ b/dom/base/test/test_postMessages.html
@@ -0,0 +1,654 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for postMessages cloning/transferring objects</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<input id="fileList" type="file"></input>
+<script type="application/javascript;version=1.7">
+
+function setup_tests() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
+ ["javascript.options.wasm", true]]}, next);
+}
+
+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().toSource(), bProps.sort().toSource(), 'Props match - using toSource()');
+
+ 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.toSource(), b.toSource(), 'Matching using toSource()');
+ }
+}
+
+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().indexOf('Android') != -1) {
+ 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);
+
+ fileList.getFilesAndDirectories().then(function(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");
+
+ const wasmTextToBinary = SpecialPowers.unwrap(SpecialPowers.Cu.getJSTestingFunctions().wasmTextToBinary);
+ const fooModuleCode = wasmTextToBinary(`(module
+ (func $foo (result i32) (i32.const 42))
+ (export "foo" $foo)
+ )`, 'new-format');
+
+ 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) {
+ if (!obj.clonableObjectsEveryWhere) {
+ resolve();
+ return;
+ }
+
+ 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, []).then(function(received) {
+ compare(received.data, object.data);
+ runClonableTest();
+ });
+ }
+
+ runClonableTest();
+ })
+
+ // clonable same process
+ .then(function() {
+ return new Promise(function(resolve, reject) {
+ if (!obj.clonableObjectsSameProcess) {
+ resolve();
+ return;
+ }
+
+ 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, []).then(function(received) {
+ 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) {
+ ok(received.ports.length, 1, "MessagePort has been transferred");
+ mc.port2.postMessage("hello world");
+ received.ports[0].onmessage = function(e) {
+ ok(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);
+ });
+ })
+
+ // 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();
+ });
+}
+
+// PostMessage to the same window.
+function test_windowToWindow() {
+ info("Testing window to window");
+ var resolve;
+
+ onmessage = function(e) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e.data, ports: e.ports });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: true,
+ send: function(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+
+ try {
+ postMessage(what, '*', ports);
+ } catch(e) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished: function() {
+ onmessage = null;
+ next();
+ }
+ });
+}
+
+// PostMessage to iframe
+function test_windowToIframe() {
+ info("Testing window to iframe");
+ test_windowToIframeURL('iframe_postMessages.html');
+}
+
+// PostMessage to cross-origin iframe
+function test_windowToCrossOriginIframe() {
+ info("Testing window to cross-origin iframe");
+ test_windowToIframeURL('http://example.com/tests/dom/base/test/iframe_postMessages.html');
+}
+
+// iframe helper class
+function test_windowToIframeURL(url) {
+ var resolve;
+
+ onmessage = function(e) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e.data, ports: e.ports });
+ }
+
+ var ifr = document.createElement('iframe');
+ ifr.src = url;
+ ifr.onload = function() {
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: true,
+ send: function(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ ifr.contentWindow.postMessage(what, '*', ports);
+ } catch(e) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished: function() {
+ document.body.removeChild(ifr);
+ onmessage = null;
+ next();
+ }
+ });
+ }
+ document.body.appendChild(ifr);
+}
+
+// PostMessage for Workers
+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(e) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e.data, ports: e.ports });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: true,
+ transferableObjects: true,
+ send: function(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ w.postMessage(what, ports);
+ } catch(e) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished: function() {
+ onmessage = null;
+ next();
+ }
+ });
+ }
+}
+
+// PostMessage for BroadcastChannel
+function test_broadcastChannel() {
+ info("Testing broadcastChannel");
+
+ var bc1 = new BroadcastChannel('postMessagesTest');
+ var bc2 = new BroadcastChannel('postMessagesTest');
+
+ var resolve;
+
+ bc2.onmessage = function(e) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e.data, ports: [] });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: false,
+ transferableObjects: false,
+ send: function(what, ports) {
+ return new Promise(function(r, rr) {
+ if (ports.length) {
+ rr();
+ return;
+ }
+
+ resolve = r;
+ bc1.postMessage(what);
+ });
+ },
+
+ finished: function() {
+ onmessage = null;
+ next();
+ }
+ });
+}
+
+// PostMessage for BroadcastChannel in workers
+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(e) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e.data, ports: e.ports });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: false,
+ transferableObjects: false,
+ send: function(what, ports) {
+ return new Promise(function(r, rr) {
+ if (ports.length) {
+ rr();
+ return;
+ }
+
+ resolve = r;
+ bc.postMessage(what);
+ });
+ },
+
+ finished: function() {
+ onmessage = null;
+ next();
+ }
+ });
+ }
+}
+
+// PostMessage for MessagePort
+function test_messagePort() {
+ info("Testing messagePort");
+
+ var mc = new MessageChannel();
+ var resolve;
+
+ mc.port2.onmessage = function(e) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e.data, ports: e.ports });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: false,
+ transferableObjects: true,
+ send: function(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ mc.port1.postMessage(what, ports);
+ } catch(e) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished: function() {
+ onmessage = null;
+ next();
+ }
+ });
+}
+
+// PostMessage for MessagePort in Workers
+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(e) {
+ if (!resolve) {
+ ok(false, "Unexpected message!");
+ return;
+ }
+
+ let tmp = resolve;
+ resolve = null;
+ tmp({ data: e.data, ports: e.ports });
+ }
+
+ runTests({
+ clonableObjectsEveryWhere: true,
+ clonableObjectsSameProcess: false,
+ transferableObjects: true,
+ send: function(what, ports) {
+ return new Promise(function(r, rr) {
+ resolve = r;
+ try {
+ mc.port1.postMessage(what, ports);
+ } catch(e) {
+ resolve = null;
+ rr();
+ }
+ });
+ },
+
+ finished: function() {
+ onmessage = null;
+ next();
+ }
+ });
+ }
+}
+
+var tests = [
+ setup_tests,
+
+ create_fileList,
+ create_directory,
+ create_wasmModule,
+
+ test_windowToWindow,
+ test_windowToIframe,
+ test_windowToCrossOriginIframe,
+
+ test_workers,
+
+ test_broadcastChannel,
+ test_broadcastChannel_inWorkers,
+
+ test_messagePort,
+ test_messagePort_inWorkers,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+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 000000000..c921bcc1c
--- /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"
+"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 type="application/javascript" 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 000000000..46a048934
--- /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 type="text/javascript" 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, false);
+ 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_range_bounds.html b/dom/base/test/test_range_bounds.html
new file mode 100644
index 000000000..bebb81d8e
--- /dev/null
+++ b/dom/base/test/test_range_bounds.html
@@ -0,0 +1,288 @@
+<!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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/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=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(rect,i) {
+ is(_getRect(rectlist[i]),_getRect(rect),
+ 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 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();
+
+ 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 000000000..fe1088526
--- /dev/null
+++ b/dom/base/test/test_reentrant_flush.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=709256
+-->
+<head>
+ <title>Test for Bug 709256</title>
+ <script type="application/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=709256">Mozilla Bug 709256</a>
+<p id="display">
+<iframe id="test"
+ style="width: 100px" src="data:text/html,<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, false);
+ 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");
+
+ is(resizeHandlerRan, true, "Resize handler should have run");
+
+ win.removeEventListener("resize", handleResize, false);
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_referrer_redirect.html b/dom/base/test/test_referrer_redirect.html
new file mode 100644
index 000000000..b5d968b6f
--- /dev/null
+++ b/dom/base/test/test_referrer_redirect.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test anchor and area policy attribute for Bug 1184781</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <!--
+ Testing referrer headers after redirects.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1184781
+ -->
+
+ <script type="application/javascript;version=1.8">
+
+ const SJS = "://example.com/tests/dom/base/test/referrer_testserver.sjs?";
+ const PARAMS = ["ATTRIBUTE_POLICY", "NEW_ATTRIBUTE_POLICY", "META_POLICY"];
+
+ const testCases = [
+ {ACTION: ["generate-img-redirect-policy-test", "generate-iframe-redirect-policy-test"],
+ TESTS: [
+ {
+ ATTRIBUTE_POLICY: "no-referrer",
+ NAME: "no-referrer-with-no-meta",
+ DESC: "no-referrer (img/iframe) with no meta",
+ RESULT: "none"
+ },
+ {
+ ATTRIBUTE_POLICY: "origin",
+ NAME: "origin-with-no-meta",
+ DESC: "origin (img/iframe) with no meta",
+ RESULT: "origin"
+ },
+ {
+ ATTRIBUTE_POLICY: "unsafe-url",
+ NAME: "unsafe-url-with-no-meta",
+ DESC: "unsafe-url (img/iframe) with no meta",
+ RESULT: "full"
+ },
+ {
+ META_POLICY: "unsafe-url",
+ NAME: "unsafe-url-in-meta",
+ DESC: "unsafe-url in meta",
+ RESULT: "full"
+ },
+ {
+ META_POLICY: "origin",
+ NAME: "origin-in-meta",
+ DESC: "origin in meta",
+ RESULT: "origin"
+ },
+ {
+ META_POLICY: "no-referrer",
+ NAME: "no-referrer-in-meta",
+ DESC: "no-referrer in meta",
+ RESULT: "none"
+ },
+ {
+ META_POLICY: "origin-when-cross-origin",
+ NAME: "origin-when-cross-origin-in-meta",
+ DESC: "origin-when-cross-origin in meta",
+ RESULT: "origin"
+ }]}
+ ];
+ </script>
+ <script type="application/javascript;version=1.7" 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_root_iframe.html b/dom/base/test/test_root_iframe.html
new file mode 100644
index 000000000..ce99cc228
--- /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 type="application/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=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 Window,
+ "Should have a subframe window for a root iframe");
+ </script>
+</pre>
+</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 000000000..8b94d7273
--- /dev/null
+++ b/dom/base/test/test_sandboxed_blob_uri.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Principal in MessageManager</title>
+ <script type="application/javascript" 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;version=1.7">
+ "use strict";
+
+ const Cu = Components.utils;
+
+ 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_screen_orientation.html b/dom/base/test/test_screen_orientation.html
new file mode 100644
index 000000000..ea3016eef
--- /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 type="application/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=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 000000000..cfbb90c8d
--- /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_sendQueryContentAndSelectionSetEvent.html b/dom/base/test/test_sendQueryContentAndSelectionSetEvent.html
new file mode 100644
index 000000000..369ed1c9d
--- /dev/null
+++ b/dom/base/test/test_sendQueryContentAndSelectionSetEvent.html
@@ -0,0 +1,250 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for nsIDOMWindowUtils.sendQueryContentEvent() and .sendSelectionSetEvent()</title>
+ <script type="application/javascript" 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 id="editor" contenteditable>abc<br>def</div>
+<pre id="test">
+<script type="application/javascript">
+
+var editor = document.getElementById("editor");
+
+SimpleTest.waitForExplicitFinish();
+
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+
+const kLineBreak = kIsWin ? "\r\n" : "\n";
+
+var gUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+function escape(aStr)
+{
+ var result = aStr.replace("\n", "\\n");
+ return result.replace("\r", "\\r");
+}
+
+function runTests()
+{
+ 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
+ window.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 = {};
+ var left2 = {};
+ var top2 = {};
+ var width2 = {};
+ var height2 = {};
+ textRectArray.getCharacterRect(0, left, top, width, height);
+ ok(textRect.top, top.value,
+ "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same top that returns QUERY_TEXT_RECT");
+ ok(textRect.left, left.value,
+ "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same left that returns QUERY_TEXT_RECT");
+ textRectArray.getCharacterRect(1, left2, top2, width2, height2);
+ ok(textRect.width, width.value + width2.value,
+ "sendQueryContentEvent(QUERY_TEXT_RECT_ARRAY) should return same width that QUERY_TEXT_RECT is returned for offset 1 and 2");
+ ok(textRect.height, height.value,
+ "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
+ 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");
+
+ 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");
+
+ 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();
+}
+
+SimpleTest.waitForFocus(runTests);
+
+</script>
+</pre>
+</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 000000000..70a1d96c0
--- /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 type="application/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=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 000000000..71e653da7
--- /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 type="text/javascript" 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 000000000..b790eff06
--- /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 000000000..b0e6a9d4e
--- /dev/null
+++ b/dom/base/test/test_settimeout_inner.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=936129
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 936129</title>
+ <script type="application/javascript" 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);
+ }
+ iframe.src = "about:blank";
+ }
+ iframe.src = "data:text/html,<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="data:text/html,<script>window.onload = function runTest1() { document.open(); setTimeout('parent.test1Done();'); document.close(); }</script>"></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 000000000..babdf8570
--- /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 type="application/javascript" 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("data:text/html,<script>opener.setTimeout(opener.basicOpenerTest, 0, this)</" + "script>");
+ // 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 = "data:text/html,<script>opener.setTimeout(opener.continueOpenerTest, 0, this);</" + "script>";
+ }
+
+ 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 = "data:text/html,<script>opener.setTimeout(opener.continueOpenerTest2, 0, this);</" + "script>";
+ }
+
+ 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 = "data:text/html,Loaded";
+ // 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.textContent != "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_simplecontentpolicy.html b/dom/base/test/test_simplecontentpolicy.html
new file mode 100644
index 000000000..9fa5dd3fd
--- /dev/null
+++ b/dom/base/test/test_simplecontentpolicy.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for nsISimpleContentPolicy</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=1128798">Mozilla Bug 1128798</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var lastContentType = -1;
+const testURL = window.location.href + "/this/is/the/test/url";
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+var beaconUrl = "http://mochi.test:8888/tests/dom/tests/mochitest/beacon/beacon-handler.sjs";
+
+var chromeURL = SimpleTest.getTestFileURL("file_simplecontentpolicy.js");
+var script = SpecialPowers.loadChromeScript(chromeURL);
+
+// Try creating different request types. Most of these are not
+// top-level because they run in the test iframe.
+var tests = [["SCRIPT", false],
+ ["IMAGE", false],
+ ["STYLESHEET", false],
+ ["OBJECT", false],
+ ["DOCUMENT", true],
+ ["SUBDOCUMENT", false],
+ ["XBL", false],
+ ["XMLHTTPREQUEST", false],
+ ["BEACON", false]];
+var curTest = -1;
+
+var div;
+
+SimpleTest.waitForExplicitFinish();
+
+script.addMessageListener("ready", function(msg) {
+ SpecialPowers.pushPrefEnv({'set': [["beacon.enabled", true]]}, runNextTest);
+});
+
+script.addMessageListener("shouldLoad", function(msg) {
+ var isTopLevel = tests[curTest][1];
+ var type = "TYPE_" + tests[curTest][0];
+ is(msg.contentType, Ci.nsIContentPolicy[type], "Content policies triggered for " + type);
+ is(msg.isTopLevel, isTopLevel, "isTopLevel is set correctly");
+
+ if (tests[curTest] == "XBL") {
+ //XXX Removing binding to work-around a memory leak (bugs 478528, 499735).
+ div.style.MozBinding = "";
+ }
+
+ runNextTest();
+});
+
+function runNextTest() {
+ curTest++;
+ if (curTest < tests.length) {
+ var method = "request_" + tests[curTest][0].toLowerCase();
+ try {
+ window[method]();
+ } catch(e) {}
+ }
+ else {
+ script.sendAsyncMessage("finished");
+
+ SimpleTest.finish();
+ }
+}
+
+// Request creating functions
+
+function request_script() {
+ var content = $("content");
+
+Math.sin(1);
+ 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_document() {
+ top.location.href = testURL;
+}
+
+function request_subdocument() {
+ var content = $("content");
+
+ var frame = document.createElement("iframe");
+ frame.setAttribute("src", testURL);
+ content.appendChild(frame);
+}
+
+function request_xbl() {
+ var content = $("content");
+
+ div = document.createElement("div");
+ div.style.MozBinding = "url(" + testURL + ")";
+ $('test').appendChild(div);
+ div.offsetLeft; // Flush styles.
+}
+
+function request_xmlhttprequest() {
+ var request = new XMLHttpRequest();
+ request.open("GET", testURL, false);
+ request.send(null);
+}
+
+function request_beacon() {
+ navigator.sendBeacon(testURL, "bacon would have been a better name than beacon");
+}
+
+</script>
+</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 000000000..31fa0f076
--- /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 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=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, false);
+</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 000000000..f87973214
--- /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 type="application/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=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 000000000..e9bcba2ea
--- /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 type="application/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=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_timer_flood.html b/dom/base/test/test_timer_flood.html
new file mode 100644
index 000000000..b74b04cf2
--- /dev/null
+++ b/dom/base/test/test_timer_flood.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test Behavior During Timer Flood</title>
+ <script type="application/javascript" 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();
+
+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 000000000..8d735b453
--- /dev/null
+++ b/dom/base/test/test_title.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Test for titles</title>
+ <script type="text/javascript" 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="xul1" src="data:application/vnd.mozilla.xul+xml,<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>"></iframe>
+ <iframe id="xul2" src="data:application/vnd.mozilla.xul+xml,<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>"></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) {
+ 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>");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_treewalker_nextsibling.xml b/dom/base/test/test_treewalker_nextsibling.xml
new file mode 100644
index 000000000..a10ab31a0
--- /dev/null
+++ b/dom/base/test/test_treewalker_nextsibling.xml
@@ -0,0 +1,97 @@
+<?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() {
+ const nsIDOMNodeFilter = SpecialPowers.Ci.nsIDOMNodeFilter;
+ var walker = document.createTreeWalker(
+ document,
+ nsIDOMNodeFilter.SHOW_TEXT | nsIDOMNodeFilter.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, nsIDOMNodeFilter.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_user_select.html b/dom/base/test/test_user_select.html
new file mode 100644
index 000000000..c2b117597
--- /dev/null
+++ b/dom/base/test/test_user_select.html
@@ -0,0 +1,341 @@
+<!DOCTYPE>
+<html>
+<head>
+<title>-moz-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 { -moz-user-select: none; }
+n { display: none; }
+a { position:absolute; bottom: 0; right:0; }
+.text { -moz-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="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" src="data:text/html,<body>aaaa<span style='-moz-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, 140);
+ 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, 140);
+ 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);
+
+ // ======================================================
+ // ================== 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 bbbb", e); // XXX this doesn't seem right - bug 1247799
+ 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 bbb", 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();
+}
+window.onload = function() { setTimeout(test, 0); };
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_viewport_scroll.html b/dom/base/test/test_viewport_scroll.html
new file mode 100644
index 000000000..9b812360b
--- /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 type="application/javascript" 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 = "data:text/html,<html><body style='height:2000px; width:2000px;'>";
+standards.src = "data:text/html,<!DOCTYPE HTML><html><body style='height:2000px; width:2000px;'>";
+xml.src = "data:text/xml,<?xml-stylesheet href='data:text/css,html { display:block; height:2000px; width:2000px; }'?><foo><html></html></foo>";
+
+function subtest(winProp, elemProp, win, correctElement, elemToSet, otherElem1, otherElem2) {
+ win.scrollTo(50, 50);
+ elemToSet[elemProp] = 100;
+ if (elemToSet == correctElement) {
+ is(win[winProp], 100, "Setting " + elemToSet.name + "." + elemProp + " should scroll");
+ is(elemToSet[elemProp], 100, "Reading back " + elemToSet.name + "." + elemProp + " after scrolling");
+ } else {
+ is(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 000000000..67ad367ef
--- /dev/null
+++ b/dom/base/test/test_viewsource_forbidden_in_object.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=973837
+-->
+<head>
+<meta charset="utf-8">
+<title>Tests for Bug 973837</title>
+<script type="text/javascript" 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: feed:view-source should not be allowed in an object.",
+ URI: "feed:view-source:file_general_document.html"
+ },
+ {
+ desc: "Test 3: jar:view-source should not be allowed in an object",
+ URI: "jar:view-source:file_general_document.html/!/"
+ },
+ {
+ desc: "Test 4: pcast:view-source should not be allowed in an object",
+ URI: "pcast:view-source:file_general_document.html"
+ },
+ {
+ desc: "Test 5: pcast:feed:view-source should not be allowed in an object",
+ URI: "pcast:feed: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 000000000..fdd4ac03c
--- /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 type="text/javascript" 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 000000000..28ef46511
--- /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 type="text/javascript" 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 000000000..7a1407434
--- /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 type="application/javascript" 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, false);
+</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 000000000..590506d2e
--- /dev/null
+++ b/dom/base/test/test_warning_for_blocked_cross_site_request.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=713980
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 713980</title>
+ <script type="text/javascript" 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 -->
+ <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"; }
+ </style>
+</head>
+<body>
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var tests = {
+ font : {
+ uri_test : "font_bad",
+ result : null
+ },
+ xhr : {
+ uri_test : "http://invalid",
+ result : null
+ },
+}
+
+function testsComplete() {
+ for (var testName in tests) {
+ var test = tests[testName];
+ if (test.result == null)
+ 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];
+ 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 == "CORS", "Got warning message with category \"" + aMsg.category + "\", expected \"CORS\"");
+ // Got the message we wanted - make sure it is destined for a valid inner window
+ 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);
+
+// 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.
+var badDiv = document.createElement('div');
+badDiv.setAttribute('id', 'bad_webfont');
+document.body.appendChild(badDiv);
+</script>
+
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_webSocket_sandbox.html b/dom/base/test/test_webSocket_sandbox.html
new file mode 100644
index 000000000..b343fa784
--- /dev/null
+++ b/dom/base/test/test_webSocket_sandbox.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1252751</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="container"></div>
+<iframe id="frame"></iframe>
+<script type="application/javascript;version=1.8">
+var urls = [ "https://example.com/tests/dom/base/test/iframe_webSocket_sandbox.html",
+ "https://example.com/tests/dom/base/test/iframe_webSocket_sandbox.html?nested",
+ "https://example.com/tests/dom/base/test/iframe_webSocket_sandbox.html?popup" ];
+
+onmessage = function(e) {
+ is(e.data, "WS Throws!", "ws://URI cannot be used by a https iframe");
+ runTest();
+}
+
+function runTest() {
+ if (!urls.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ document.getElementById("frame").src = urls.shift();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_webaudioNotification.html b/dom/base/test/test_webaudioNotification.html
new file mode 100644
index 000000000..d010e916b
--- /dev/null
+++ b/dom/base/test/test_webaudioNotification.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+<iframe></iframe>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+var iframe = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ SimpleTest.executeSoon(runTest);
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var tests = [
+ function() {
+ iframe = document.querySelector("iframe");
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ iframe.src = "file_webaudioLoop.html";
+ expectedNotification = 'active';
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ iframe.contentWindow.suspendAC();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ iframe.contentWindow.resumeAC();
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ iframe.contentWindow.closeAC();
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+onload = runTest;
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_webaudioNotificationStopOnNavigation.html b/dom/base/test/test_webaudioNotificationStopOnNavigation.html
new file mode 100644
index 000000000..237d67402
--- /dev/null
+++ b/dom/base/test/test_webaudioNotificationStopOnNavigation.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for audio controller in windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+<iframe></iframe>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+var iframe = null;
+
+var observer = {
+ observe: function(subject, topic, data) {
+ is(topic, "audio-playback", "audio-playback received");
+ is(data, expectedNotification, "This is the right notification");
+ runTest();
+ }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var tests = [
+ function() {
+ iframe = document.querySelector("iframe");
+ observerService.addObserver(observer, "audio-playback", false);
+ ok(true, "Observer set");
+ runTest();
+ },
+
+ function() {
+ expectedNotification = 'active';
+ iframe.src = "file_webaudioLoop2.html";
+ },
+
+ function() {
+ expectedNotification = 'inactive-pause';
+ iframe.src = "data:text/html,page without audio";
+ },
+
+ function() {
+ observerService.removeObserver(observer, "audio-playback");
+ ok(true, "Observer removed");
+ runTest();
+ }
+];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+onload = runTest;
+
+</script>
+</body>
+</html>
+
diff --git a/dom/base/test/test_websocket1.html b/dom/base/test/test_websocket1.html
new file mode 100644
index 000000000..1894f5314
--- /dev/null
+++ b/dom/base/test/test_websocket1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+ <title>WebSocket test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="websocket_helpers.js"></script>
+ <script type="text/javascript" src="websocket_tests.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+var tests = [
+ test1, // client tries to connect to a http scheme location;
+ test2, // assure serialization of the connections;
+ test3, // client tries to connect to an non-existent ws server;
+ test4, // client tries to connect using a relative url;
+ test5, // client uses an invalid protocol value;
+ test6, // counter and encoding check;
+ test7, // onmessage event origin property check
+ test8, // client calls close() and the server sends the close frame (with no
+ // code or reason) in acknowledgement;
+ test9, // client closes the connection before the ws connection is established;
+ test10, // client sends a message before the ws connection is established;
+];
+
+function testWebSocket() {
+ doTest();
+}
+
+SimpleTest.requestFlakyTimeout("The web socket tests are really fragile, but avoiding timeouts might be hard, since it's testing stuff on the network. " +
+ "Expect all sorts of flakiness in this test...");
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
diff --git a/dom/base/test/test_websocket2.html b/dom/base/test/test_websocket2.html
new file mode 100644
index 000000000..5711057cf
--- /dev/null
+++ b/dom/base/test/test_websocket2.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+ <title>WebSocket test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="websocket_helpers.js"></script>
+ <script type="text/javascript" src="websocket_tests.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+var tests = [
+ test11, // a simple hello echo;
+ test12, // client sends a message containing unpaired surrogates
+ test13, //server sends an invalid message;
+ test14, // server sends the close frame, it doesn't close the tcp connection
+ // and it keeps sending normal ws messages;
+ test15, // server closes the tcp connection, but it doesn't send the close
+ // frame;
+ test16, // client calls close() and tries to send a message;
+ test17, // see bug 572975 - all event listeners set
+ test18, // client tries to connect to an http resource;
+ test19, // server closes the tcp connection before establishing the ws
+ // connection;
+ test20, // see bug 572975 - only on error and onclose event listeners set
+];
+
+function testWebSocket() {
+ doTest();
+}
+
+SimpleTest.requestFlakyTimeout("The web socket tests are really fragile, but avoiding timeouts might be hard, since it's testing stuff on the network. " +
+ "Expect all sorts of flakiness in this test...");
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
diff --git a/dom/base/test/test_websocket3.html b/dom/base/test/test_websocket3.html
new file mode 100644
index 000000000..57482dae7
--- /dev/null
+++ b/dom/base/test/test_websocket3.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+ <title>WebSocket test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="websocket_helpers.js"></script>
+ <script type="text/javascript" src="websocket_tests.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+var tests = [
+ test21, // see bug 572975 - same as test 17, but delete strong event listeners
+ // when receiving the message event;
+ test22, // server takes too long to establish the ws connection;
+ test23, // should detect WebSocket on window object;
+ test24, // server rejects sub-protocol string
+ test25, // ctor with valid empty sub-protocol array
+ test26, // ctor with invalid sub-protocol array containing 1 empty element
+ test27, // ctor with invalid sub-protocol array containing an empty element in
+ // list
+ test28, // ctor using valid 1 element sub-protocol array
+ test29, // ctor using all valid 5 element sub-protocol array
+ test30, // ctor using valid 1 element sub-protocol array with element server
+ // will reject
+];
+
+function testWebSocket() {
+ doTest();
+}
+
+SimpleTest.requestFlakyTimeout("The web socket tests are really fragile, but avoiding timeouts might be hard, since it's testing stuff on the network. " +
+ "Expect all sorts of flakiness in this test...");
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
diff --git a/dom/base/test/test_websocket4.html b/dom/base/test/test_websocket4.html
new file mode 100644
index 000000000..4bc700fb8
--- /dev/null
+++ b/dom/base/test/test_websocket4.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+ <title>WebSocket test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="websocket_helpers.js"></script>
+ <script type="text/javascript" src="websocket_tests.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+var tests = [
+ test31, // ctor using valid 2 element sub-protocol array with 1 element server
+ // will reject and one server will accept
+ test32, // ctor using invalid sub-protocol array that contains duplicate items
+ test33, // test for sending/receiving custom close code (but no close reason)
+ test34, // test for receiving custom close code and reason
+ test35, // test for sending custom close code and reason
+ test36, // negative test for sending out of range close code
+ test37, // negative test for too long of a close reason
+ test38, // ensure extensions attribute is defined
+ test39, // a basic wss:// connectivity test
+ test40, // negative test for wss:// with no cert
+];
+
+function testWebSocket() {
+ doTest();
+}
+
+SimpleTest.requestFlakyTimeout("The web socket tests are really fragile, but avoiding timeouts might be hard, since it's testing stuff on the network. " +
+ "Expect all sorts of flakiness in this test...");
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
diff --git a/dom/base/test/test_websocket5.html b/dom/base/test/test_websocket5.html
new file mode 100644
index 000000000..70a948821
--- /dev/null
+++ b/dom/base/test/test_websocket5.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
+ <title>WebSocket test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="websocket_helpers.js"></script>
+ <script type="text/javascript" src="websocket_tests.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<script class="testbody" type="text/javascript">
+
+var tests = [
+ test41, // HSTS
+ test42, // non-char utf-8 sequences
+ test43, // Test setting binaryType attribute
+ test44, // Test sending/receving binary ArrayBuffer
+ test45, // Test sending/receving binary Blob
+ test46, // Test that we don't dispatch incoming msgs once in CLOSING state
+ test47, // Make sure onerror/onclose aren't called during close()
+ test48, // see bug 1227136 - client calls close() from onopen() and waits
+ // until WebSocketChannel::mSocketIn is nulled out on socket thread
+];
+
+function testWebSocket() {
+ doTest();
+}
+
+SimpleTest.requestFlakyTimeout("The web socket tests are really fragile, but avoiding timeouts might be hard, since it's testing stuff on the network. " +
+ "Expect all sorts of flakiness in this test...");
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+<div id="feedback">
+</div>
+
+</body>
+</html>
diff --git a/dom/base/test/test_websocket_basic.html b/dom/base/test/test_websocket_basic.html
new file mode 100644
index 000000000..e3269382e
--- /dev/null
+++ b/dom/base/test/test_websocket_basic.html
@@ -0,0 +1,255 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Basic WebSocket test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body onload="testWebSocket()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=472529">Mozilla Bug 472529</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const kUrl = "ws://mochi.test:8888/tests/dom/base/test/file_websocket_basic";
+
+var gTestElement;
+var ws;
+
+function forcegc() {
+ SpecialPowers.forceGC();
+ SpecialPowers.gc();
+}
+
+function testWebSocket() {
+ gTestElement = document.getElementById("test");
+
+ SimpleTest.executeSoon(testWebSocket1);
+}
+
+/**
+ * Sends message keywords, then receives their values.
+ */
+function testWebSocket1() {
+ gTestElement.textContent = "Running testWebSocket1()";
+
+ var results = ["test",
+ "/tests/dom/base/test/file_websocket_basic",
+ "http://mochi.test:8888",
+ "end"];
+
+ ws = new WebSocket(kUrl, "test");
+ is(ws.url, kUrl, "[1] WebSocket.url");
+ ws.onopen = function(e) {
+ const params = ["protocol", "resource", "origin", "end"];
+
+ gTestElement.textContent += "\nSending :";
+ for (var i = 0; i < params.length; ++i) {
+ gTestElement.textContent += " " + params[i];
+ ws.send(params[i]);
+ }
+
+ // Set this before onmessage() is called, so it is displayed once only.
+ gTestElement.textContent += "\nReceived:";
+ };
+ ws.onclose = function(e) {
+ is(results.length, 0, "[1] Number of unreceived messages");
+ ok(e.wasClean, "[1] Connection closed cleanly");
+
+ SimpleTest.executeSoon(testWebSocket2);
+ };
+ ws.onerror = function(e) {
+ ok(false, "[1] onerror() should not have been called!");
+ gTestElement.textContent += "\nonerror() should not have been called!";
+ SimpleTest.executeSoon(SimpleTest.finish);
+ };
+ ws.onmessage = function(e) {
+ is(e.data, results[0], "[1] Received message");
+ gTestElement.textContent += " " + e.data;
+ results.shift();
+ };
+}
+
+/**
+ * Sends 1000+1 test messages, then receives them.
+ */
+function testWebSocket2() {
+ gTestElement.textContent = "Running testWebSocket2()";
+
+ const displayInterval = 100;
+ const testCount = 1000;
+ const testMessage = "test message 2.";
+
+ var messageCount = 0;
+
+ ws = new WebSocket(kUrl, "test");
+ ws.onopen = function(e) {
+ gTestElement.textContent += "\nSending :";
+ for (var i = 1; i <= testCount; ++i) {
+ if (i % displayInterval == 1) {
+ gTestElement.textContent += " " + i;
+ }
+ ws.send(testMessage + i);
+ }
+ gTestElement.textContent += " end";
+ ws.send("end");
+
+ // Set this before onmessage() is called, so it is displayed once only.
+ gTestElement.textContent += "\nReceived:";
+ };
+ ws.onclose = function(e) {
+ is(messageCount, testCount + 1, "[2] Number of received messages");
+ ok(e.wasClean, "[2] Connection closed cleanly");
+
+ SimpleTest.executeSoon(testWebSocket3);
+ };
+ ws.onerror = function(e) {
+ ok(false, "[2] onerror() should not have been called!");
+ gTestElement.textContent += "\nonerror() should not have been called!";
+ SimpleTest.executeSoon(SimpleTest.finish);
+ };
+ ws.onmessage = function(e) {
+ ++messageCount;
+ if (messageCount > testCount)
+ is(e.data, "end", "[2] Received message");
+ else
+ is(e.data, testMessage + messageCount, "[2] Received message");
+ if (messageCount % displayInterval == 1) {
+ gTestElement.textContent += " " + messageCount;
+ }
+ };
+}
+
+/**
+ * Sends testcount+1 test messages, then receives them, calling forcegc() at each step.
+ */
+function testWebSocket3() {
+ gTestElement.textContent = "Running testWebSocket3() [can take a little while]";
+
+ const displayInterval = 10;
+ const testCount = 10;
+ const testMessage = "test message 3.";
+
+ var messageCount = 0;
+
+ ws = new WebSocket(kUrl, "test");
+ // Set this before onopen() is called,
+ // otherwise its display would be delayed by forcegc() calls...
+ gTestElement.textContent += "\nSending :";
+ ws.onopen = function(e) {
+ for (var i = 1; i <= testCount; ++i) {
+ forcegc();
+ if (i % displayInterval == 1) {
+ // Actual display is delayed by forcegc() calls...
+ gTestElement.textContent += " " + i;
+ }
+ ws.send(testMessage + i);
+ }
+ forcegc();
+ gTestElement.textContent += " end";
+ ws.send("end");
+
+ // Set this before onmessage() is called, so it is displayed once only.
+ gTestElement.textContent += "\nReceived:";
+ };
+ ws.onclose = function(e) {
+ is(messageCount, testCount + 1, "[3] Number of received messages");
+ ok(e.wasClean, "[3] Connection closed cleanly");
+
+ SimpleTest.executeSoon(testWebSocket4);
+ };
+ ws.onerror = function(e) {
+ ok(false, "[3] onerror() should not have been called!");
+ gTestElement.textContent += "\nonerror() should not have been called!";
+ SimpleTest.executeSoon(SimpleTest.finish);
+ };
+ ws.onmessage = function(e) {
+ forcegc();
+ ++messageCount;
+ if (messageCount > testCount)
+ is(e.data, "end", "[3] Received message");
+ else
+ is(e.data, testMessage + messageCount, "[3] Received message");
+ if (messageCount % displayInterval == 1) {
+ // Actual display is delayed by forcegc() call(s)...
+ gTestElement.textContent += " " + messageCount;
+ }
+ };
+}
+
+/**
+ * Sends a huge test message, then receives it, then closes the WebSocket from client-side.
+ */
+function testWebSocket4() {
+ gTestElement.textContent = "Running testWebSocket4()";
+
+ // String length = 13 + ((10,000 - 1) * 26) + 11 = 259,998 = almost 254 KiB.
+ const longString = "messageStart " + new Array(10000).join(" -huge WebSocket message- ") + " messageEnd";
+
+ ws = new WebSocket(kUrl, "test");
+ ws.onopen = function(e) {
+ is(this, ws, "[4, onopen()] 'this' should point to the WebSocket.");
+ gTestElement.textContent += "\nSending the huge message";
+ ws.send(longString);
+ };
+ ws.onclose = function(e) {
+ is(this, ws, "[4, onclose()] 'this' should point to the WebSocket.");
+ ok(e.wasClean, "[4] Connection closed cleanly");
+
+ SimpleTest.executeSoon(testWebSocket5);
+ };
+ ws.onerror = function(e) {
+ is(this, ws, "[4, onerror()] 'this' should point to the WebSocket.");
+ ok(false, "[4, onerror()] should not have been called!");
+ gTestElement.textContent += "\nonerror() should not have been called!";
+ SimpleTest.executeSoon(SimpleTest.finish);
+ };
+ ws.onmessage = function(e) {
+ is(this, ws, "[4, onmessage()] 'this' should point to the WebSocket.");
+ // Do not use |is(e.data, longString, "...");| that results in a _very_ long line.
+ is(e.data.length, longString.length, "[4] Length of received message");
+ ok(e.data == longString, "[4] Content of received message");
+ gTestElement.textContent += "\nReceived the huge message";
+ this.close();
+ };
+}
+
+/**
+ * Closes the WebSocket from client-side, then sends a test message that should be buffered.
+ */
+function testWebSocket5() {
+ gTestElement.textContent = "Running testWebSocket5()";
+
+ ws = new WebSocket(kUrl, "test");
+ ws.onopen = function(e) {
+ is(this.bufferedAmount, 0, "[5] Length of empty buffer before closing");
+ this.close();
+ };
+ ws.onclose = function(e) {
+ ok(e.wasClean, "[5] Connection closed cleanly");
+ is(this.bufferedAmount, 0, "[5] Length of empty buffer after closing");
+
+ var msg = "test message to be buffered";
+ this.send(msg);
+ is(this.bufferedAmount, msg.length, "[5] Length of buffered message sent after closing");
+
+ gTestElement.textContent += "\ntestWebSocket5() completed";
+
+ SimpleTest.executeSoon(SimpleTest.finish);
+ };
+ ws.onerror = function(e) {
+ ok(false, "[5] onerror() should not have been called!");
+ gTestElement.textContent += "\nonerror() should not have been called!";
+ SimpleTest.executeSoon(SimpleTest.finish);
+ };
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/test_websocket_frame.html b/dom/base/test/test_websocket_frame.html
new file mode 100644
index 000000000..59ab85907
--- /dev/null
+++ b/dom/base/test/test_websocket_frame.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Basic websocket frame interception test</title>
+ <script type="application/javascript" 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 class="testbody" type="text/javascript">
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const URI = "ws://mochi.test:8888/tests/dom/base/test/file_websocket_basic";
+
+var frameReceivedCounter = 0;
+var frameSentCounter = 0;
+var webSocketCreatedCounter = 0;
+var webSocketOpenedCounter = 0;
+var webSocketMessageAvailableCounter = 0;
+var webSocketClosedCounter = 0;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var tests = [
+ { payload: "Hello world!" },
+ { payload: (function() { var buffer = ""; for (var i = 0; i < 120; ++i) buffer += i; return buffer; }()) },
+]
+
+var innerId =
+ window.top.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+ok(innerId, "We have a valid innerWindowID: " + innerId);
+
+var service = Cc["@mozilla.org/websocketevent/service;1"]
+ .getService(Ci.nsIWebSocketEventService);
+ok(!!service, "We have the nsIWebSocketEventService");
+
+var listener = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebSocketEventListener]),
+
+ webSocketCreated: function(aWebSocketSerialID, aURI, aProtocols) {
+ info("WebSocketCreated");
+
+ is(aURI, URI, "URI matches");
+ is(aProtocols, "frame", "Protocol matches");
+
+ webSocketCreatedCounter++;
+ },
+
+ webSocketOpened: function(aWebSocketSerialID, aEffectiveURI, aProtocols, aExtensions) {
+ info("WebSocketOpened");
+
+ is(aEffectiveURI, URI, "EffectiveURI matches");
+ is(aProtocols, "frame", "Protocol matches");
+ is(aExtensions, "permessage-deflate", "No extensions");
+
+ webSocketOpenedCounter++;
+ },
+
+ webSocketMessageAvailable: function(aWebSocketSerialID, aData, aMessageType) {
+ info("WebSocketMessageAvailable");
+
+ if (tests.length) {
+ is(aData, tests[0].payload, "Message matches!");
+ is(aMessageType, Ci.nsIWebSocketEventListener.TYPE_STRING, "The type is 'string'");
+
+ webSocketMessageAvailableCounter++;
+
+ tests.shift();
+ if (tests.length) {
+ ws.send(tests[0].payload);
+ } else {
+ ws.send("end");
+ }
+ }
+ },
+
+ webSocketClosed: function(aWebSocketSerialID, aWasClean,
+ aCode, aReason) {
+ info("WebSocketClosed");
+
+ ok(aWasClean, "The socket is closed in a clean state");
+ is(aCode, 1000, "Exit code 1000");
+ ok(!aReason.length, "No reason");
+
+ webSocketClosedCounter++;
+ checkListener();
+ },
+
+ frameReceived: function(aWebSocketSerialID, aFrame) {
+ ok(!!aFrame, "We have received a frame");
+
+ if (tests.length) {
+ ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
+ is(aFrame.finBit, true, "Checking finBit");
+ is(aFrame.rsvBit1, true, "Checking rsvBit1");
+ is(aFrame.rsvBit2, false, "Checking rsvBit2");
+ is(aFrame.rsvBit3, false, "Checking rsvBit3");
+ is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
+ is(aFrame.maskBit, false, "Checking maskBit");
+ is(aFrame.mask, 0, "Checking mask");
+ is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
+ }
+
+ frameReceivedCounter++;
+ },
+
+ frameSent: function(aWebSocketSerialID, aFrame) {
+ ok(!!aFrame, "We have sent a frame");
+
+ if (tests.length) {
+ ok(aFrame.timeStamp, "Checking timeStamp: " + aFrame.timeStamp);
+ is(aFrame.finBit, true, "Checking finBit");
+ is(aFrame.rsvBit1, true, "Checking rsvBit1");
+ is(aFrame.rsvBit2, false, "Checking rsvBit2");
+ is(aFrame.rsvBit3, false, "Checking rsvBit3");
+ is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
+ is(aFrame.maskBit, true, "Checking maskBit");
+ ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
+ is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
+ }
+
+ frameSentCounter++;
+ }
+};
+
+service.addListener(innerId, listener);
+ok(true, "Listener added");
+
+function checkListener() {
+ service.removeListener(innerId, listener);
+
+ ok(frameReceivedCounter, "We received some frames!");
+ ok(frameSentCounter, "We sent some frames!");
+ ok(webSocketCreatedCounter, "We have a create notification");
+ ok(webSocketOpenedCounter, "We have a open notification");
+ ok(webSocketMessageAvailableCounter, "We have a messageAvailable notification");
+ ok(webSocketClosedCounter, "We have a close notification");
+ SimpleTest.finish();
+}
+
+var ws = new WebSocket(URI, "frame");
+ws.onopen = function(e) {
+ info("onopen");
+
+ ws.send(tests[0].payload);
+}
+
+ws.onclose = function(e) {
+ info("onclose");
+}
+
+ws.onmessage = function(e) {
+ info("onmessage");
+ if (tests.length) {
+ is(e.data, tests[0].payload, "Wrong data");
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/test_websocket_hello.html b/dom/base/test/test_websocket_hello.html
new file mode 100644
index 000000000..c1874ab98
--- /dev/null
+++ b/dom/base/test/test_websocket_hello.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <title>Basic websocket test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="testWebSocket()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=472529">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var ws;
+
+function testWebSocket () {
+ ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_hello");
+ ws.onopen = function(e) {
+ ws.send("data");
+ }
+ ws.onclose = function(e) {
+ }
+ ws.onerror = function(e) {
+ ok(false, "onerror called!");
+ SimpleTest.finish();
+ }
+ ws.onmessage = function(e) {
+ is(e.data, "Hello world!", "Wrong data");
+ ws.close();
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<div>
+
+
+</div>
+
+
+</body>
+</html>
diff --git a/dom/base/test/test_websocket_permessage_deflate.html b/dom/base/test/test_websocket_permessage_deflate.html
new file mode 100644
index 000000000..5c40600c4
--- /dev/null
+++ b/dom/base/test/test_websocket_permessage_deflate.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Basic test of permessage compression websocket extension</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="loadDeflate()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=792831">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var ws;
+var textMessage = "This is a text message";
+var binaryMessage = "This is a binary message";
+var testIdx = 0;
+var sendText = true;
+
+tests = [
+ // enable PMCE
+ [ true, true, "ws://mochi.test:8888/tests/dom/base/test/file_websocket_permessage_deflate" ],
+ // disable PMCE
+ [ false, false, "ws://mochi.test:8888/tests/dom/base/test/file_websocket_permessage_deflate_disabled" ],
+ // server rejects offered PMCE
+ [ true, false, "ws://mochi.test:8888/tests/dom/base/test/file_websocket_permessage_deflate_rejected" ],
+ // server returns parameters in the handshake
+ [ true, true, "ws://mochi.test:8888/tests/dom/base/test/file_websocket_permessage_deflate_params" ]
+]
+
+function ab2str(buf) {
+ return String.fromCharCode.apply(null, new Uint16Array(buf));
+}
+
+function str2ab(str) {
+ var buf = new ArrayBuffer(str.length*2);
+ var bufView = new Uint16Array(buf);
+ for (var i=0, strLen=str.length; i<strLen; i++) {
+ bufView[i] = str.charCodeAt(i);
+ }
+ return buf;
+}
+
+function sendMessage() {
+ if (sendText) {
+ ws.send(textMessage);
+ } else {
+ ws.binaryType = "arraybuffer";
+ ws.send(str2ab(binaryMessage));
+ }
+}
+
+function testDeflate() {
+ ws = new WebSocket(tests[testIdx][2]);
+
+ ws.onopen = function(e) {
+ if (tests[testIdx][1]) {
+ is(ws.extensions, "permessage-deflate", "permessage-deflate not negotiated!");
+ } else {
+ is(ws.extensions, "", "permessage-deflate should not be negotiated!");
+ }
+
+ sendMessage();
+ }
+
+ ws.onclose = function(e) {
+ if (!e.wasClean) {
+ ok(false, "Connection should be closed cleanly!");
+ SimpleTest.finish();
+ }
+ }
+
+ ws.onerror = function(e) {
+ ok(false, "onerror called!");
+ SimpleTest.finish();
+ }
+
+ ws.onmessage = function(e) {
+ if (sendText) {
+ is(e.data, textMessage, "Text message not received successfully!");
+ sendText = false;
+ sendMessage();
+ } else {
+ ok(e.data instanceof ArrayBuffer, "Should receive an arraybuffer!");
+ is(ab2str(e.data), binaryMessage, "Binary message not received successfully!");
+ ws.close();
+
+ sendText = true;
+ testIdx++;
+ if (testIdx < tests.length) {
+ loadDeflate();
+ } else {
+ SimpleTest.finish();
+ }
+ }
+ }
+}
+
+function loadDeflate() {
+ SpecialPowers.pushPrefEnv({"set":[['network.websocket.extensions.permessage-deflate', tests[testIdx][0]]]}, testDeflate);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</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 000000000..702043839
--- /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 type="application/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=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, false);
+</script>
+</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 000000000..d16a103fb
--- /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 type="application/javascript" 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 000000000..ad1cf7687
--- /dev/null
+++ b/dom/base/test/test_window_define_nonconfigurable.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1107443
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1107443</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1107443 **/
+ try {
+ Object.defineProperty(window, "nosuchprop", { value: 5, configurable: false });
+ throw "didn't throw";
+ } catch (e) {
+ is(e instanceof TypeError, true,
+ "defineProperty(window) with a non-configurable property should " +
+ "throw a TypeError, instead got: " + e);
+ is(Object.getOwnPropertyDescriptor(window, "nosuchprop"), undefined,
+ 'Window should not have property after an attempt to define it failed');
+ }
+
+ Object.defineProperty(window, "nosuchprop", { value: 6 });
+ var desc = Object.getOwnPropertyDescriptor(window, "nosuchprop");
+ is(typeof(desc), "object", "Should have a property now");
+ todo_is(desc.configurable, true, "Property should be configurable");
+ is(desc.writable, false, "Property should be readonly");
+ is(desc.value, 6, "Property should have the right value");
+
+ Object.defineProperty(window, "nosuchprop2", { value: 7, configurable: true });
+ desc = Object.getOwnPropertyDescriptor(window, "nosuchprop2");
+ is(typeof(desc), "object", "Should have a property now");
+ is(desc.configurable, true, "Property should be configurable");
+ is(desc.writable, false, "Property should be readonly");
+ is(desc.value, 7, "Property 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 000000000..95ea9e6ee
--- /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 type="application/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=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 000000000..f8ea6728c
--- /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 type="application/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=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 000000000..c2377426c
--- /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 type="application/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=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 000000000..5f9f09291
--- /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 type="application/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=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, false);
+</script>
+</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 000000000..c7450adda
--- /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 type="application/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=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.parentNode.removeChild(y);
+ 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.parentNode.removeChild(y);
+ 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.parentNode.removeChild(y);
+ 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.parentNode.removeChild(y);
+ 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_named_frame_enumeration.html b/dom/base/test/test_window_named_frame_enumeration.html
new file mode 100644
index 000000000..4d3335db4
--- /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 type="application/javascript" 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_orientation.html b/dom/base/test/test_window_orientation.html
new file mode 100644
index 000000000..4921a95cd
--- /dev/null
+++ b/dom/base/test/test_window_orientation.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for window.orientation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="orientationcommon.js"></script>
+<div id="log"></div>
+<script>
+async_test(function(t) {
+ var originalOrientation = screen.orientation.type;
+ var alternateOrientation = originalOrientation == "portrait-primary" ?
+ "landscape-primary" : "portrait-primary";
+
+ var originalWindowOrientation = window.orientation;
+ window.onorientationchange = function() {
+ t.step(function() { assert_not_equals(window.orientation, originalWindowOrientation); });
+
+ var p2 = specialPowersUnlock();
+ p2.then(function() {
+ t.done();
+ }).catch(t.step_func(function(err) {
+ assert_unreached("Error unlocking orientation: " + err);
+ t.done();
+ }));
+ }
+
+ var p1 = specialPowersLock(alternateOrientation);
+ p1.catch(t.step_func(function(err) {
+ assert_unreached("Error locking orientation: " + err);
+ t.done();
+ }));
+}, "Test window.orientation and orientationchange.");
+</script>
diff --git a/dom/base/test/test_window_proto.html b/dom/base/test/test_window_proto.html
new file mode 100644
index 000000000..944f953bc
--- /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 000000000..20aad7aef
--- /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 type="application/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=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 000000000..8e24d8a78
--- /dev/null
+++ b/dom/base/test/test_x-frame-options.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for X-Frame-Options response header</title>
+ <script type="text/javascript" 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 = function() {
+ var harness = SpecialPowers.wrap(document).getElementById("harness");
+
+ // iframe from same origin, no X-F-O header - should load
+ var frame = harness.contentDocument.getElementById("control1");
+ var test1 = frame.contentDocument.getElementById("test").textContent;
+ is(test1, "control1", "test control1");
+
+ // iframe from different origin, no X-F-O header - should load
+ frame = harness.contentDocument.getElementById("control2");
+ var test2 = frame.contentDocument.getElementById("test").textContent;
+ is(test2, "control2", "test control2");
+
+ // iframe from same origin, X-F-O: DENY - should not load
+ frame = harness.contentDocument.getElementById("deny");
+ var test3 = frame.contentDocument.getElementById("test");
+ is(test3, null, "test deny");
+
+ // iframe from same origin, X-F-O: SAMEORIGIN - should load
+ frame = harness.contentDocument.getElementById("sameorigin1");
+ var test4 = frame.contentDocument.getElementById("test").textContent;
+ is(test4, "sameorigin1", "test sameorigin1");
+
+ // iframe from different origin, X-F-O: SAMEORIGIN - should not load
+ frame = harness.contentDocument.getElementById("sameorigin2");
+ var test5 = frame.contentDocument.getElementById("test");
+ is(test5, null, "test sameorigin2");
+
+ // iframe from different origin, X-F-O: SAMEORIGIN, SAMEORIGIN - should not load
+ frame = harness.contentDocument.getElementById("sameorigin5");
+ var test6 = frame.contentDocument.getElementById("test");
+ is(test6, null, "test sameorigin5");
+
+ // iframe from same origin, X-F-O: SAMEORIGIN, SAMEORIGIN - should load
+ frame = harness.contentDocument.getElementById("sameorigin6");
+ var test7 = frame.contentDocument.getElementById("test").textContent;
+ is(test7, "sameorigin6", "test sameorigin6");
+
+ // iframe from same origin, X-F-O: SAMEORIGIN,SAMEORIGIN, SAMEORIGIN - should load
+ frame = harness.contentDocument.getElementById("sameorigin7");
+ var test8 = frame.contentDocument.getElementById("test").textContent;
+ is(test8, "sameorigin7", "test sameorigin7");
+
+ // iframe from same origin, X-F-O: SAMEORIGIN,SAMEORIGIN, SAMEORIGIN - should not load
+ frame = harness.contentDocument.getElementById("sameorigin8");
+ var test9 = frame.contentDocument.getElementById("test");
+ is(test9, null, "test sameorigin8");
+
+ // iframe from same origin, X-F-O: DENY,SAMEORIGIN - should not load
+ frame = harness.contentDocument.getElementById("mixedpolicy");
+ var test10 = frame.contentDocument.getElementById("test");
+ is(test10, null, "test mixedpolicy");
+
+ // iframe from different origin, allow-from: this origin - should load
+ frame = harness.contentDocument.getElementById("allow-from-allow");
+ var test11 = frame.contentDocument.getElementById("test").textContent;
+ is(test11, "allow-from-allow", "test allow-from-allow");
+
+ // iframe from different origin, with allow-from: other - should not load
+ frame = harness.contentDocument.getElementById("allow-from-deny");
+ var test12 = frame.contentDocument.getElementById("test");
+ is(test12, null, "test allow-from-deny");
+
+ // iframe from different origin, X-F-O: SAMEORIGIN, multipart - should not load
+ frame = harness.contentDocument.getElementById("sameorigin-multipart");
+ var test13 = frame.contentDocument.getElementById("test");
+ is(test13, null, "test sameorigin-multipart");
+
+ // iframe from same origin, X-F-O: SAMEORIGIN, multipart - should load
+ frame = harness.contentDocument.getElementById("sameorigin-multipart2");
+ var test14 = frame.contentDocument.getElementById("test").textContent;
+ is(test14, "sameorigin-multipart2", "test sameorigin-multipart2");
+
+
+ // frames from bug 836132 tests
+ {
+ frame = harness.contentDocument.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");
+ }
+ }
+ for (var i = 1; i<=14; i++) {
+ frame = harness.contentDocument.getElementById("allow-from-deny-" + i);
+ var theTestResult = frame.contentDocument.getElementById("test");
+ is(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
+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.onload = function() {
+ var test = win.document.getElementById("sameorigin3")
+ .contentDocument.getElementById("test");
+ ok(test != null, "frame under javascript: URL should have loaded.");
+ win.close();
+
+ // run last test
+ testFrameInDataURI();
+ }
+ win.location.href = "javascript:document.write('"+html+"');document.close();";
+}
+
+// test that a document can be framed under a data: URL opened by the
+// same site as the frame
+var testFrameInDataURI = function() {
+ 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>';
+ var win = window.open();
+ win.onload = function() {
+ var test = win.document.getElementById("sameorigin4")
+ .contentDocument.getElementById("test");
+ ok(test != null, "frame under data: URL should have loaded.");
+ win.close();
+
+ SimpleTest.finish();
+ }
+ win.location.href = "data:text/html,"+html;
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// load the test harness
+document.getElementById("harness").src = "file_x-frame-options_main.html";
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/base/test/test_xbl_userdata.xhtml b/dom/base/test/test_xbl_userdata.xhtml
new file mode 100644
index 000000000..852298853
--- /dev/null
+++ b/dom/base/test/test_xbl_userdata.xhtml
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test for getUserData/setUserData support in XBL</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<style type="text/css">
+#t {
+-moz-binding: url(#xbl);
+}
+</style>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="xbl" inheritstyle="false">
+<implementation><constructor><![CDATA[
+ this.textContent = !!(this.getUserData && this.setUserData);
+]]></constructor></implementation>
+</binding>
+</bindings>
+
+</head>
+<body>
+<p id="display"></p>
+
+<pre id="test">
+<script class="testbody">
+<![CDATA[
+"use strict";
+
+var url = 'data:text/html;charset=utf-8,' +
+ encodeURIComponent('<div id=t style="-moz-binding:url(' + location + '#xbl)"></div>');
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test1);
+
+function test1() {
+ var iframe = document.createElement('iframe');
+ iframe.src = url;
+ iframe.onload = function() {
+ var t = iframe.contentWindow.t;
+ is(!!(t.getUserData || t.setUserData), false,
+ "getUserData and setUserData should not be visible from the regular content");
+ is(t.textContent, "true",
+ "getUserData and setUserData should be visible from XBL");
+ document.body.removeChild(iframe);
+ SimpleTest.finish();
+ };
+ document.body.appendChild(iframe);
+}
+
+]]>
+</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 000000000..9b7f25d0f
--- /dev/null
+++ b/dom/base/test/test_youtube_flash_embed.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1240471
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1240471</title>
+ <script type="application/javascript" 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() {
+ SpecialPowers.pushPrefEnv({
+ "set": [["full-screen-api.unprefix.enabled", true]]
+ }, function() {
+ // 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 000000000..4b7915159
--- /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 000000000..61d4458be
--- /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 000000000..a6b9e340b
--- /dev/null
+++ b/dom/base/test/unit/2_original.xml
@@ -0,0 +1,15 @@
+<?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 000000000..16eeb817f
--- /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 000000000..c3eeadb58
--- /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 000000000..906ac89ee
--- /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 000000000..27ed21921
--- /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 000000000..eb9c1bd65
--- /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 000000000..e556c61e5
--- /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 000000000..2df257ca7
--- /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 000000000..4c9c61b5d
--- /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 000000000..b985da960
--- /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 000000000..bc408b431
--- /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 000000000..30c8b47de
--- /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 000000000..9346d5d17
--- /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 000000000..936dd950b
--- /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 000000000..e9917cfdc
--- /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 000000000..ebd60b08c
--- /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 000000000..d3b68520d
--- /dev/null
+++ b/dom/base/test/unit/head_utilities.js
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://testing-common/httpd.js");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+const nsIDocumentEncoder = Components.interfaces.nsIDocumentEncoder;
+const replacementChar = Components.interfaces.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 ios = Components.classes['@mozilla.org/network/io-service;1']
+ .getService(Components.interfaces.nsIIOService);
+ var chann = NetUtil.newChannel({
+ uri: ios.newFileURI(file),
+ loadUsingSystemPrincipal: true
+ });
+ chann.contentCharset = aCharset;
+
+ /*var inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Components.interfaces.nsIScriptableInputStream);
+ inputStream.init(chann.open2());
+ return inputStream.read(file.fileSize);
+ */
+
+ var inputStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Components.interfaces.nsIConverterInputStream);
+ inputStream.init(chann.open2(), 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 000000000..3d445b748
--- /dev/null
+++ b/dom/base/test/unit/head_xml.js
@@ -0,0 +1,156 @@
+/* -*- 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 = Components.interfaces;
+const C = Components.classes;
+
+const nsILocalFile = I.nsILocalFile;
+const nsIProperties = I.nsIProperties;
+const nsIFileInputStream = I.nsIFileInputStream;
+const nsIInputStream = I.nsIInputStream;
+
+const nsIDOMParser = I.nsIDOMParser;
+const nsIDOMSerializer = I.nsIDOMSerializer;
+const nsIDOMDocument = I.nsIDOMDocument;
+const nsIDOMElement = I.nsIDOMElement;
+const nsIDOMNode = I.nsIDOMNode;
+const nsIDOMCharacterData = I.nsIDOMCharacterData;
+const nsIDOMAttr = I.nsIDOMAttr;
+const nsIDOMNodeList = I.nsIDOMNodeList;
+const nsIDOMXULElement = I.nsIDOMXULElement;
+const nsIDOMProcessingInstruction = I.nsIDOMProcessingInstruction;
+
+function DOMParser() {
+ var parser = C["@mozilla.org/xmlextras/domparser;1"].createInstance(nsIDOMParser);
+ parser.init();
+ 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;
+ }
+
+ do_check_eq(file instanceof nsILocalFile, 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 DOMParser().parseFromString(data, "application/xml");
+ }
+
+ do_check_eq(data instanceof nsIInputStream, true);
+
+ return DOMParser().parseFromStream(data, "UTF-8", data.available(),
+ "application/xml");
+}
+
+function DOMSerializer() {
+ return C["@mozilla.org/xmlextras/xmlserializer;1"]
+ .createInstance(nsIDOMSerializer);
+}
+
+function SerializeXML(node) {
+ return DOMSerializer().serializeToString(node);
+}
+
+function roundtrip(obj) {
+ if (typeof(obj) == "string") {
+ return SerializeXML(ParseXML(obj));
+ }
+
+ do_check_eq(obj instanceof nsIDOMNode, 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 + "'");
+ }
+ do_check_eq(att.QueryInterface(nsIDOMAttr).value,
+ att2.QueryInterface(nsIDOMAttr).value);
+ }
+ }
+}
+
+function do_check_equiv(dom1, dom2) {
+ do_check_eq(dom1.nodeType, dom2.nodeType);
+ // There's no classinfo around, so we'll need to do some QIing to
+ // make sure the right interfaces are flattened as needed.
+ switch (dom1.nodeType) {
+ case nsIDOMNode.PROCESSING_INSTRUCTION_NODE:
+ do_check_eq(dom1.QueryInterface(nsIDOMProcessingInstruction).target,
+ dom2.QueryInterface(nsIDOMProcessingInstruction).target);
+ do_check_eq(dom1.data, dom2.data);
+ case nsIDOMNode.TEXT_NODE:
+ case nsIDOMNode.CDATA_SECTION_NODE:
+ case nsIDOMNode.COMMENT_NODE:
+ do_check_eq(dom1.QueryInterface(nsIDOMCharacterData).data,
+ dom2.QueryInterface(nsIDOMCharacterData).data);
+ break;
+ case nsIDOMNode.ELEMENT_NODE:
+ do_check_eq(dom1.namespaceURI, dom2.namespaceURI);
+ do_check_eq(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 nsIDOMNode.DOCUMENT_NODE:
+ do_check_eq(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 000000000..4b72f5d50
--- /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 000000000..ddde596a2
--- /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.xul b/dom/base/test/unit/nodelist_data_2.xul
new file mode 100644
index 000000000..247ef0353
--- /dev/null
+++ b/dom/base/test/unit/nodelist_data_2.xul
@@ -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_bloburi.js b/dom/base/test/unit/test_bloburi.js
new file mode 100644
index 000000000..36e2e1eb7
--- /dev/null
+++ b/dom/base/test/unit/test_bloburi.js
@@ -0,0 +1,33 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+var uris = [
+ {
+ uri: "blob:https://example.com/230d5d50-35f9-9745-a64a-15e47b731a81",
+ local: true,
+ },
+ {
+ uri: "rstp://1.2.3.4/some_path?param=a",
+ local: false,
+ },
+ {
+ uri: "moz-fonttable://something",
+ local: true,
+ }
+];
+
+function run_test()
+{
+ for (let i = 0; i < uris.length; i++) {
+ let uri = ios.newURI(uris[i].uri);
+ let handler = ios.getProtocolHandler(uri.scheme).QueryInterface(Ci.nsIProtocolHandler);
+ let flags = handler.protocolFlags;
+
+ do_check_eq(Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE & flags,
+ (uris[i].local) ? Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE : 0);
+ }
+}
+
diff --git a/dom/base/test/unit/test_bug553888.js b/dom/base/test/unit/test_bug553888.js
new file mode 100644
index 000000000..036d51211
--- /dev/null
+++ b/dom/base/test/unit/test_bug553888.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.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");
+ do_check_eq(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 = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ request.open("GET", redirectURL, true);
+ request.setRequestHeader("X-Custom-Header", "present");
+ request.addEventListener("readystatechange", function() {
+ if (request.readyState == 4) {
+ do_check_eq(request.status, 200);
+ server.stop(do_test_finished);
+ }
+ }, false);
+ 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 000000000..448f6fa74
--- /dev/null
+++ b/dom/base/test/unit/test_bug737966.js
@@ -0,0 +1,20 @@
+/* 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 = Components.classes['@mozilla.org/xmlextras/xmlhttprequest;1'].
+ createInstance(Components.interfaces.nsIXMLHttpRequest);
+ xhr.open('GET',
+ 'data:text/xml;charset=abc,' + body,
+ false);
+ xhr.send(null);
+
+ do_check_eq(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 000000000..1a10240a9
--- /dev/null
+++ b/dom/base/test/unit/test_cancelPrefetch.js
@@ -0,0 +1,134 @@
+//Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+var prefetch = Cc["@mozilla.org/prefetch-service;1"].
+ getService(Ci.nsIPrefetchService);
+var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+
+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);
+
+ parser.init();
+ 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", null, null);
+ prefetch.prefetchURI(uri, uri, node1, true);
+
+ do_check_true(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, uri, node1, true);
+ } catch(e) {
+ didFail = 1;
+ }
+
+ do_check_true(didFail == 1, 'Prefetching the same request with the same ' +
+ 'node fails.');
+
+ do_check_true(prefetch.hasMoreElements(), 'There is still request in ' +
+ 'the queue');
+
+ prefetch.cancelPrefetchURI(uri, node1);
+
+ do_check_false(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", null, null);
+ prefetch.prefetchURI(uri, uri, node1, true);
+ prefetch.prefetchURI(uri, uri, node2, true);
+
+ do_check_true(prefetch.hasMoreElements(), 'There are requests in the queue');
+
+ prefetch.cancelPrefetchURI(uri, node1);
+
+ do_check_true(prefetch.hasMoreElements(), 'There is still one more request ' +
+ 'in the queue');
+
+ prefetch.cancelPrefetchURI(uri, node2);
+
+ do_check_false(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", null, null);
+ prefetch.prefetchURI(uri, uri, node1, true);
+
+ do_check_true(prefetch.hasMoreElements(), 'There is a request in the queue');
+
+ var didFail = 0;
+
+ try {
+ prefetch.cancelPrefetchURI(uri, node2);
+ } catch(e) {
+ didFail = 1;
+ }
+ do_check_true(didFail == 1, 'Canceling the request failed');
+
+ do_check_true(prefetch.hasMoreElements(), 'There is still a request ' +
+ 'in the queue');
+
+ prefetch.cancelPrefetchURI(uri, node1);
+ do_check_false(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", null, null);
+ var uri2 = ios.newURI("http://localhost/2", null, null);
+ prefetch.prefetchURI(uri1, uri1, node1, true);
+
+ do_check_true(prefetch.hasMoreElements(), 'There is a request in the queue');
+
+ var didFail = 0;
+
+ try {
+ prefetch.cancelPrefetchURI(uri2, node1);
+ } catch(e) {
+ didFail = 1;
+ }
+ do_check_true(didFail == 1, 'Canceling the request failed');
+
+ do_check_true(prefetch.hasMoreElements(), 'There is still a request ' +
+ 'in the queue');
+
+ prefetch.cancelPrefetchURI(uri1, node1);
+ do_check_false(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 000000000..361392c44
--- /dev/null
+++ b/dom/base/test/unit/test_chromeutils_base64.js
@@ -0,0 +1,105 @@
+"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("utf-8");
+ 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" }),
+ paddedValue + " with padding rejected should throw");
+
+ throws(_ => ChromeUtils.base64URLDecode(input, { padding: "require" }),
+ 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" }),
+ "Decoding should throw for invalid padding policy");
+
+ for (let {decoded, encoded} of binaryTests) {
+ testDecode(encoded, decoded);
+ }
+
+ let textEncoder = new TextEncoder("utf-8");
+ for (let decoded of Object.keys(textTests)) {
+ let expectedBuffer = textEncoder.encode(decoded);
+ testDecode(textTests[decoded], expectedBuffer);
+ }
+}
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 000000000..c8d50bd32
--- /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 000000000..c4f488f18
--- /dev/null
+++ b/dom/base/test/unit/test_error_codes.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+
+var asyncXHR = {
+ load: function() {
+ var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ request.open("GET", "http://localhost:4444/test_error_code.xml", true);
+
+ var self = this;
+ request.addEventListener("error", function(event) { self.onError(event); }, false);
+ request.send(null);
+ },
+ onError: function doAsyncRequest_onError(event) {
+ var request = event.target.channel.QueryInterface(Components.interfaces.nsIRequest);
+ do_check_eq(request.status, gExpectedStatus);
+ gNextTestFunc();
+ }
+}
+
+function run_test() {
+ do_test_pending();
+ do_timeout(0, run_test_pt1);
+}
+
+// network offline
+function run_test_pt1() {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ try {
+ ioService.manageOfflineStatus = false;
+ }
+ catch (e) {
+ }
+ ioService.offline = true;
+ prefs.setBoolPref("network.dns.offline-localhost", false);
+
+ gExpectedStatus = Components.results.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() {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ ioService.offline = false;
+ prefs.clearUserPref("network.dns.offline-localhost");
+
+ gExpectedStatus = Components.results.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_isequalnode.js b/dom/base/test/unit/test_isequalnode.js
new file mode 100644
index 000000000..5d44b5b50
--- /dev/null
+++ b/dom/base/test/unit/test_isequalnode.js
@@ -0,0 +1,435 @@
+/* -*- 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_isEqualNode_setAttribute();
+ test_isEqualNode_clones();
+ test_isEqualNode_variety();
+ test_isEqualNode_normalization();
+ test_isEqualNode_whitespace();
+ test_isEqualNode_namespaces();
+ test_isEqualNode_wholeDoc();
+
+ // XXX should Node.isEqualNode(null) throw or return false?
+ //test_isEqualNode_null();
+
+}
+
+// TEST CODE
+
+var doc, root; // cache for use in all tests
+
+function init()
+{
+ doc = ParseFile("isequalnode_data.xml");
+ root = doc.documentElement;
+}
+
+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);
+
+
+ el(node1).setAttribute("bar", "baz");
+ check_neq_nodes(node1, node2);
+
+ el(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);
+}
+
+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 = el(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));
+}
+
+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]);
+ }
+ }
+}
+
+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);
+}
+
+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);
+}
+
+function test_isEqualNode_namespaces()
+{
+ equality_check_kids("test_ns1", false);
+ equality_check_kids("test_ns2", false);
+
+ // XXX want more tests here!
+}
+
+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);
+ }
+ }
+ }
+}
+
+function test_isEqualNode_wholeDoc()
+{
+ doc = ParseFile("isequalnode_data.xml");
+ var doc2 = ParseFile("isequalnode_data.xml");
+ var tw1 =
+ doc.createTreeWalker(doc, Components.interfaces.nsIDOMNodeFilter.SHOW_ALL,
+ null);
+ var tw2 =
+ doc2.createTreeWalker(doc2, Components.interfaces.nsIDOMNodeFilter.SHOW_ALL,
+ null);
+ do {
+ check_eq_nodes(tw1.currentNode, tw2.currentNode);
+ tw1.nextNode();
+ } while(tw2.nextNode());
+}
+
+// UTILITY FUNCTIONS
+
+function n(node) { return node ? node.QueryInterface(nsIDOMNode) : null; }
+function el(node) { return node ? node.QueryInterface(nsIDOMElement) : null; }
+function at(node) { return node ? node.QueryInterface(nsIDOMAttr) : null; }
+
+
+// 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_nodelist.js b/dom/base/test/unit/test_nodelist.js
new file mode 100644
index 000000000..50c79da72
--- /dev/null
+++ b/dom/base/test/unit/test_nodelist.js
@@ -0,0 +1,394 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+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.
+ */
+
+ 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 a nodelist.
+ do_check_true(doc.getElementsByTagName("*") instanceof nsIDOMNodeList);
+ do_check_true(root.getElementsByTagName("*") instanceof nsIDOMNodeList);
+
+ // Check that getElementsByTagName excludes the element it's called on.
+ do_check_eq(doc.getElementsByTagName("*").length,
+ root.getElementsByTagName("*").length + 1);
+ do_check_eq(doc.getElementById("test2").getElementsByTagName("*").length,
+ 8);
+ do_check_eq(doc.getElementById("test2").getElementsByTagName("test").length,
+ 3);
+
+ // Check that the first element of getElementsByTagName on the document is
+ // the right thing.
+ do_check_eq(doc.getElementsByTagName("*").item(0), root);
+
+ // Check that we get the right things in the right order
+ var numTests = doc.getElementsByTagName("test").length;
+ do_check_eq(numTests, 5);
+
+ for (var i = 1; i <= numTests; ++i) {
+ do_check_true(doc.getElementById("test" + i) instanceof nsIDOMElement);
+ do_check_eq(doc.getElementById("test" + i),
+ doc.getElementsByTagName("test").item(i-1));
+ }
+
+ // Check that we handle tagnames containing ':' correctly
+ do_check_true(doc.getElementsByTagName("foo:test")
+ instanceof nsIDOMNodeList);
+ do_check_eq(doc.getElementsByTagName("foo:test").length, 2);
+
+ do_check_true(doc.getElementsByTagName("foo2:test")
+ instanceof nsIDOMNodeList);
+ do_check_eq(doc.getElementsByTagName("foo2:test").length, 3);
+
+ do_check_true(doc.getElementsByTagName("bar:test")
+ instanceof nsIDOMNodeList);
+ do_check_eq(doc.getElementsByTagName("bar:test").length, 4);
+}
+
+function test_getElementsByTagNameNS()
+{
+ var doc = ParseFile("nodelist_data_1.xml");
+ var root = doc.documentElement;
+
+ // Check that getElementsByTagNameNS returns a nodelist.
+ do_check_true(doc.getElementsByTagNameNS("*", "*") instanceof nsIDOMNodeList);
+ do_check_true(root.getElementsByTagNameNS("*", "*") instanceof nsIDOMNodeList);
+
+ // Check that passing "" and null for the namespace URI gives the same result
+ var list1 = doc.getElementsByTagNameNS("", "test");
+ var list2 = doc.getElementsByTagNameNS(null, "test");
+ do_check_eq(list1.length, list2.length);
+ for (var i = 0; i < list1.length; ++i) {
+ do_check_eq(list1.item(i), list2.item(i));
+ }
+
+ // Check that getElementsByTagNameNS excludes the element it's called on.
+ do_check_eq(doc.getElementsByTagNameNS("*", "*").length,
+ root.getElementsByTagNameNS("*", "*").length + 1);
+ do_check_eq(doc.getElementById("test2")
+ .getElementsByTagNameNS("*", "*").length,
+ 8);
+ do_check_eq(doc.getElementById("test2")
+ .getElementsByTagNameNS("", "test").length,
+ 1);
+ do_check_eq(doc.getElementById("test2")
+ .getElementsByTagNameNS("*", "test").length,
+ 7);
+
+ // Check that the first element of getElementsByTagNameNS on the document is
+ // the right thing.
+ do_check_eq(doc.getElementsByTagNameNS("*", "*").item(0), root);
+ do_check_eq(doc.getElementsByTagNameNS(null, "*").item(0), root);
+
+ // Check that we get the right things in the right order
+
+
+ var numTests = doc.getElementsByTagNameNS("*", "test").length;
+ do_check_eq(numTests, 14);
+
+ for (var i = 1; i <= numTests; ++i) {
+ do_check_true(doc.getElementById("test" + i) instanceof nsIDOMElement);
+ do_check_eq(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");
+ do_check_eq(doc.getElementsByTagNameNS("", "test").length,
+ 3);
+ do_check_eq(test2.getElementsByTagNameNS("", "test").length,
+ 1);
+ do_check_eq(doc.getElementsByTagNameNS("foo", "test").length,
+ 7);
+ do_check_eq(test2.getElementsByTagNameNS("foo", "test").length,
+ 4);
+ do_check_eq(doc.getElementsByTagNameNS("foo2", "test").length,
+ 0);
+ do_check_eq(test2.getElementsByTagNameNS("foo2", "test").length,
+ 0);
+ do_check_eq(doc.getElementsByTagNameNS("bar", "test").length,
+ 4);
+ do_check_eq(test2.getElementsByTagNameNS("bar", "test").length,
+ 2);
+
+ // Check that we handle tagnames containing ':' correctly
+ do_check_true(doc.getElementsByTagNameNS(null, "foo:test")
+ instanceof nsIDOMNodeList);
+ do_check_eq(doc.getElementsByTagNameNS(null, "foo:test").length, 0);
+ do_check_eq(doc.getElementsByTagNameNS("foo", "foo:test").length, 0);
+ do_check_eq(doc.getElementsByTagNameNS("bar", "foo:test").length, 0);
+ do_check_eq(doc.getElementsByTagNameNS("*", "foo:test").length, 0);
+
+ do_check_true(doc.getElementsByTagNameNS(null, "foo2:test")
+ instanceof nsIDOMNodeList);
+ do_check_eq(doc.getElementsByTagNameNS(null, "foo2:test").length, 0);
+ do_check_eq(doc.getElementsByTagNameNS("foo2", "foo2:test").length, 0);
+ do_check_eq(doc.getElementsByTagNameNS("bar", "foo2:test").length, 0);
+ do_check_eq(doc.getElementsByTagNameNS("*", "foo2:test").length, 0);
+
+ do_check_true(doc.getElementsByTagNameNS(null, "bar:test")
+ instanceof nsIDOMNodeList);
+ do_check_eq(doc.getElementsByTagNameNS(null, "bar:test").length, 0);
+ do_check_eq(doc.getElementsByTagNameNS("bar", "bar:test").length, 0);
+ do_check_eq(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");
+ do_check_neq(list1, list2);
+ do_check_eq(list1.length, 0);
+ do_check_eq(list2.length, 0);
+ var newNode = doc.createElementNS("random-bogus-namespace", "foo");
+ doc.documentElement.appendChild(newNode);
+ var newNode = doc.createElementNS("random-bogus-namespace2", "foo");
+ doc.documentElement.appendChild(newNode);
+ do_check_eq(list1.length, 1);
+ do_check_eq(list2.length, 1);
+}
+
+function test_getElementsByAttribute()
+{
+ var doc = ParseFile("nodelist_data_2.xul");
+ var root = doc.documentElement;
+
+ // Sadly, DOMParser can't create XULDocument objects. But at least we have a
+ // XULElement!
+
+ do_check_true(root instanceof nsIDOMXULElement);
+
+ do_check_true(root.getElementsByAttribute("foo", "foo") instanceof
+ nsIDOMNodeList);
+
+ var master1 = doc.getElementById("master1");
+ var master2 = doc.getElementById("master2");
+ var master3 = doc.getElementById("master3");
+ var external = doc.getElementById("external");
+
+ do_check_true(master1 instanceof nsIDOMXULElement);
+ do_check_true(master2 instanceof nsIDOMXULElement);
+ do_check_true(master3 instanceof nsIDOMXULElement);
+ do_check_true(external instanceof nsIDOMXULElement);
+
+ // Basic tests
+ do_check_eq(root.getElementsByAttribute("foo", "foo").length,
+ 14);
+ do_check_eq(master1.getElementsByAttribute("foo", "foo").length,
+ 4);
+
+ do_check_eq(root.getElementsByAttribute("foo", "bar").length,
+ 7);
+ do_check_eq(master1.getElementsByAttribute("foo", "bar").length,
+ 2);
+
+ do_check_eq(root.getElementsByAttribute("bar", "bar").length,
+ 7);
+ do_check_eq(master1.getElementsByAttribute("bar", "bar").length,
+ 2);
+
+ do_check_eq(root.getElementsByAttribute("foo", "*").length,
+ 21);
+ do_check_eq(master1.getElementsByAttribute("foo", "*").length,
+ 6);
+
+ // Test the various combinations of attributes with colons in the name
+ do_check_eq(root.getElementsByAttribute("foo:foo", "foo").length,
+ 16);
+ do_check_eq(master1.getElementsByAttribute("foo:foo", "foo").length,
+ 5);
+ do_check_eq(master2.getElementsByAttribute("foo:foo", "foo").length,
+ 4);
+ do_check_eq(master3.getElementsByAttribute("foo:foo", "foo").length,
+ 4);
+ do_check_eq(external.getElementsByAttribute("foo:foo", "foo").length,
+ 2);
+
+ do_check_eq(root.getElementsByAttribute("foo:foo", "bar").length,
+ 9);
+ do_check_eq(master1.getElementsByAttribute("foo:foo", "bar").length,
+ 2);
+ do_check_eq(master2.getElementsByAttribute("foo:foo", "bar").length,
+ 3);
+ do_check_eq(master3.getElementsByAttribute("foo:foo", "bar").length,
+ 2);
+ do_check_eq(external.getElementsByAttribute("foo:foo", "bar").length,
+ 1);
+
+ do_check_eq(root.getElementsByAttribute("foo:bar", "foo").length,
+ 7);
+ do_check_eq(master1.getElementsByAttribute("foo:bar", "foo").length,
+ 2);
+ do_check_eq(master2.getElementsByAttribute("foo:bar", "foo").length,
+ 2);
+ do_check_eq(master3.getElementsByAttribute("foo:bar", "foo").length,
+ 2);
+ do_check_eq(external.getElementsByAttribute("foo:bar", "foo").length,
+ 1);
+
+ do_check_eq(root.getElementsByAttribute("foo:bar", "bar").length,
+ 14);
+ do_check_eq(master1.getElementsByAttribute("foo:bar", "bar").length,
+ 4);
+ do_check_eq(master2.getElementsByAttribute("foo:bar", "bar").length,
+ 4);
+ do_check_eq(master3.getElementsByAttribute("foo:bar", "bar").length,
+ 4);
+ do_check_eq(external.getElementsByAttribute("foo:bar", "bar").length,
+ 2);
+
+ do_check_eq(root.getElementsByAttribute("foo2:foo", "foo").length,
+ 8);
+ do_check_eq(master1.getElementsByAttribute("foo2:foo", "foo").length,
+ 2);
+ do_check_eq(master2.getElementsByAttribute("foo2:foo", "foo").length,
+ 2);
+ do_check_eq(master3.getElementsByAttribute("foo2:foo", "foo").length,
+ 3);
+ do_check_eq(external.getElementsByAttribute("foo2:foo", "foo").length,
+ 1);
+
+ do_check_eq(root.getElementsByAttribute("foo:foo", "*").length,
+ 25);
+ do_check_eq(master1.getElementsByAttribute("foo:foo", "*").length,
+ 7);
+ do_check_eq(master2.getElementsByAttribute("foo:foo", "*").length,
+ 7);
+ do_check_eq(master3.getElementsByAttribute("foo:foo", "*").length,
+ 6);
+ do_check_eq(external.getElementsByAttribute("foo:foo", "*").length,
+ 3);
+
+ do_check_eq(root.getElementsByAttribute("foo2:foo", "bar").length,
+ 0);
+ do_check_eq(root.getElementsByAttribute("foo:foo", "baz").length,
+ 0);
+}
+
+function test_getElementsByAttributeNS()
+{
+ var doc = ParseFile("nodelist_data_2.xul");
+ var root = doc.documentElement;
+
+ // Sadly, DOMParser can't create XULDocument objects. But at least we have a
+ // XULElement!
+
+ do_check_true(root instanceof nsIDOMXULElement);
+
+ // Check that getElementsByAttributeNS returns a nodelist.
+ do_check_true(root.getElementsByAttributeNS("*", "*", "*") instanceof
+ nsIDOMNodeList);
+
+ var master1 = doc.getElementById("master1");
+ var master2 = doc.getElementById("master2");
+ var master3 = doc.getElementById("master3");
+ var external = doc.getElementById("external");
+
+ do_check_true(master1 instanceof nsIDOMXULElement);
+ do_check_true(master2 instanceof nsIDOMXULElement);
+ do_check_true(master3 instanceof nsIDOMXULElement);
+ do_check_true(external instanceof nsIDOMXULElement);
+
+ // Test wildcard namespace
+ do_check_eq(root.getElementsByAttributeNS("*", "foo", "foo").length,
+ 38);
+ do_check_eq(master1.getElementsByAttributeNS("*", "foo", "foo").length,
+ 11);
+ do_check_eq(master2.getElementsByAttributeNS("*", "foo", "foo").length,
+ 10);
+ do_check_eq(master3.getElementsByAttributeNS("*", "foo", "foo").length,
+ 11);
+
+ do_check_eq(root.getElementsByAttributeNS("*", "foo", "bar").length,
+ 16);
+ do_check_eq(master1.getElementsByAttributeNS("*", "foo", "bar").length,
+ 4);
+ do_check_eq(master2.getElementsByAttributeNS("*", "foo", "bar").length,
+ 5);
+ do_check_eq(master3.getElementsByAttributeNS("*", "foo", "bar").length,
+ 4);
+
+ do_check_eq(root.getElementsByAttributeNS("*", "bar", "bar").length,
+ 21);
+ do_check_eq(master1.getElementsByAttributeNS("*", "bar", "bar").length,
+ 6);
+ do_check_eq(master2.getElementsByAttributeNS("*", "bar", "bar").length,
+ 6);
+ do_check_eq(master3.getElementsByAttributeNS("*", "bar", "bar").length,
+ 6);
+
+ do_check_eq(root.getElementsByAttributeNS("*", "foo", "*").length,
+ 54);
+ do_check_eq(master1.getElementsByAttributeNS("*", "foo", "*").length,
+ 15);
+ do_check_eq(master2.getElementsByAttributeNS("*", "foo", "*").length,
+ 15);
+ do_check_eq(master3.getElementsByAttributeNS("*", "foo", "*").length,
+ 15);
+
+ // Test null namespace. This should be the same as getElementsByAttribute.
+ do_check_eq(root.getElementsByAttributeNS("", "foo", "foo").length,
+ root.getElementsByAttribute("foo", "foo").length);
+ do_check_eq(master1.getElementsByAttributeNS("", "foo", "foo").length,
+ master1.getElementsByAttribute("foo", "foo").length);
+ do_check_eq(master2.getElementsByAttributeNS("", "foo", "foo").length,
+ master2.getElementsByAttribute("foo", "foo").length);
+ do_check_eq(master3.getElementsByAttributeNS("", "foo", "foo").length,
+ master3.getElementsByAttribute("foo", "foo").length);
+
+ // Test namespace "foo"
+ do_check_eq(root.getElementsByAttributeNS("foo", "foo", "foo").length,
+ 24);
+ do_check_eq(master1.getElementsByAttributeNS("foo", "foo", "foo").length,
+ 7);
+ do_check_eq(master2.getElementsByAttributeNS("foo", "foo", "foo").length,
+ 6);
+ do_check_eq(master3.getElementsByAttributeNS("foo", "foo", "foo").length,
+ 7);
+
+ do_check_eq(root.getElementsByAttributeNS("foo", "foo", "bar").length,
+ 9);
+ do_check_eq(master1.getElementsByAttributeNS("foo", "foo", "bar").length,
+ 2);
+ do_check_eq(master2.getElementsByAttributeNS("foo", "foo", "bar").length,
+ 3);
+ do_check_eq(master3.getElementsByAttributeNS("foo", "foo", "bar").length,
+ 2);
+
+ do_check_eq(root.getElementsByAttributeNS("foo", "bar", "foo").length,
+ 7);
+ do_check_eq(master1.getElementsByAttributeNS("foo", "bar", "foo").length,
+ 2);
+ do_check_eq(master2.getElementsByAttributeNS("foo", "bar", "foo").length,
+ 2);
+ do_check_eq(master3.getElementsByAttributeNS("foo", "bar", "foo").length,
+ 2);
+
+ do_check_eq(root.getElementsByAttributeNS("foo", "bar", "bar").length,
+ 14);
+ do_check_eq(master1.getElementsByAttributeNS("foo", "bar", "bar").length,
+ 4);
+ do_check_eq(master2.getElementsByAttributeNS("foo", "bar", "bar").length,
+ 4);
+ do_check_eq(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 000000000..a4f1b4df7
--- /dev/null
+++ b/dom/base/test/unit/test_normalize.js
@@ -0,0 +1,109 @@
+/* -*- 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(""));
+ do_check_eq(x.childNodes.length, 1);
+
+ x.normalize();
+ do_check_eq(x.childNodes.length, 0);
+
+
+ // multiple empty Text nodes
+ x.appendChild(doc.createTextNode(""));
+ x.appendChild(doc.createTextNode(""));
+ do_check_eq(x.childNodes.length, 2);
+
+ x.normalize();
+ do_check_eq(x.childNodes.length, 0);
+
+
+ // empty Text node followed by other Text node
+ x.appendChild(doc.createTextNode(""));
+ x.appendChild(doc.createTextNode("Guaraldi"));
+ do_check_eq(x.childNodes.length, 2);
+
+ x.normalize();
+ do_check_eq(x.childNodes.length, 1);
+ do_check_eq(x.childNodes.item(0).nodeValue, "Guaraldi");
+
+
+ // Text node followed by empty Text node
+ clearKids(x);
+ x.appendChild(doc.createTextNode("Guaraldi"));
+ x.appendChild(doc.createTextNode(""));
+ do_check_eq(x.childNodes.length, 2);
+
+ x.normalize();
+ do_check_eq(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"));
+ do_check_eq(x.childNodes.length, 3);
+
+ x.normalize();
+ do_check_eq(x.childNodes.length, 2);
+ do_check_eq(x.childNodes.item(0).nodeValue, "Guaraldi");
+ do_check_eq(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);
+ do_check_eq(x.childNodes.length, 3);
+ do_check_eq(x.childNodes.item(2).childNodes.length, 1);
+
+ x.normalize();
+ do_check_eq(x.childNodes.length, 2);
+ do_check_eq(x.childNodes.item(0).nodeValue, "Guaraldi");
+ do_check_eq(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 000000000..0ba1e1cd8
--- /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 C_i = Components.interfaces;
+
+const UNORDERED_TYPE = C_i.nsIDOMXPathResult.ANY_UNORDERED_NODE_TYPE;
+
+/**
+ * Determine if the data node has only ignorable white-space.
+ *
+ * @return nsIDOMNodeFilter.FILTER_SKIP if it does.
+ * @return nsIDOMNodeFilter.FILTER_ACCEPT otherwise.
+ */
+function isWhitespace(aNode) {
+ return ((/\S/).test(aNode.nodeValue)) ?
+ C_i.nsIDOMNodeFilter.FILTER_SKIP :
+ C_i.nsIDOMNodeFilter.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 serializer = new DOMSerializer();
+const parser = new DOMParser();
+
+/**
+ * Dump the contents of a document fragment to the console.
+ *
+ * @param aFragment The fragment to serialize.
+ */
+function dumpFragment(aFragment) {
+ dump(serializer.serializeToString(aFragment) + "\n\n");
+}
+
+/**
+ * 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 nsIDOMNode The target node retrieved from the XPath.
+ */
+function evalXPathInDocumentFragment(aContextNode, aPath) {
+ do_check_true(aContextNode instanceof C_i.nsIDOMDocumentFragment);
+ do_check_true(aContextNode.childNodes.length > 0);
+ 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("]")));
+ do_check_true(childIndex > 0);
+ prefix = prefix.substr(0, bracketIndex);
+ }
+
+ var targetType = C_i.nsIDOMNodeFilter.SHOW_ELEMENT;
+ var targetNodeName = prefix;
+ if (prefix.indexOf("processing-instruction(") == 0) {
+ targetType = C_i.nsIDOMNodeFilter.SHOW_PROCESSING_INSTRUCTION;
+ targetNodeName = prefix.substring(prefix.indexOf("(") + 2, prefix.indexOf(")") - 1);
+ }
+ switch (prefix) {
+ case "text()":
+ targetType = C_i.nsIDOMNodeFilter.SHOW_TEXT |
+ C_i.nsIDOMNodeFilter.SHOW_CDATA_SECTION;
+ targetNodeName = null;
+ break;
+ case "comment()":
+ targetType = C_i.nsIDOMNodeFilter.SHOW_COMMENT;
+ targetNodeName = null;
+ break;
+ case "node()":
+ targetType = C_i.nsIDOMNodeFilter.SHOW_ALL;
+ targetNodeName = null;
+ }
+
+ var filter = {
+ count: 0,
+
+ // nsIDOMNodeFilter
+ acceptNode: function acceptNode(aNode) {
+ if (aNode.parentNode != aContextNode) {
+ // Don't bother looking at kids either.
+ return C_i.nsIDOMNodeFilter.FILTER_REJECT;
+ }
+
+ if (targetNodeName && targetNodeName != aNode.nodeName) {
+ return C_i.nsIDOMNodeFilter.FILTER_SKIP;
+ }
+
+ this.count++;
+ if (this.count != childIndex) {
+ return C_i.nsIDOMNodeFilter.FILTER_SKIP;
+ }
+
+ return C_i.nsIDOMNodeFilter.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();
+ do_check_neq(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) {
+ do_check_true(aSourceNode instanceof C_i.nsIDOMElement);
+ do_check_true(aFragment instanceof C_i.nsIDOMDocumentFragment);
+ 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) {
+ do_check_true(doc.documentElement.localName != "parsererror");
+ do_check_true(doc instanceof C_i.nsIDOMXPathEvaluator);
+ do_check_true(doc instanceof C_i.nsIDOMDocument);
+
+ // Clean out whitespace.
+ var walker = doc.createTreeWalker(doc,
+ C_i.nsIDOMNodeFilter.SHOW_TEXT |
+ C_i.nsIDOMNodeFilter.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--) {
+ var node = splits.item(i);
+ node.parentNode.removeChild(node);
+ }
+ splits = null;
+
+ // Replace empty CDATA sections.
+ var emptyData = doc.getElementsByTagName("empty-cdata");
+ for (i = emptyData.length - 1; i >= 0; i--) {
+ var 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;
+ do_check_eq(baseSource.nodeName, "source");
+ var baseResult = baseSource.nextSibling;
+ do_check_eq(baseResult.nodeName, "result");
+ var baseExtract = baseResult.nextSibling;
+ do_check_eq(baseExtract.nodeName, "extract");
+ do_check_eq(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
+ nsIDOMNode.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) {
+ do_check_true(extractFrag.isEqualNode(cutFragment));
+ } else {
+ do_check_eq(extractFrag.firstChild, null);
+ }
+ do_check_true(baseFrag.isEqualNode(resultFrag));
+
+ dump("Ensure the original nodes weren't extracted - test " + i + "\n\n");
+ var walker = doc.createTreeWalker(baseFrag,
+ C_i.nsIDOMNodeFilter.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.
+ do_check_true(foundStart);
+ foundEnd = true;
+ break;
+ }
+ } while (walker.nextNode())
+ do_check_true(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);
+ var startContainer = baseRange.startContainer;
+ var endContainer = baseRange.endContainer;
+ baseRange.deleteContents();
+ do_check_true(baseFrag.isEqualNode(resultFrag));
+
+ dump("Ensure the original nodes weren't deleted - test " + i + "\n\n");
+ walker = doc.createTreeWalker(baseFrag,
+ C_i.nsIDOMNodeFilter.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.
+ do_check_true(foundStart);
+ foundEnd = true;
+ break;
+ }
+ } while (walker.nextNode())
+ do_check_true(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 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 baseResult = baseSource.nextSibling;
+ var baseExtract = baseResult.nextSibling;
+
+ 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) &&
+ (startContainer instanceof C_i.nsIDOMText)) {
+ // Invalid start node
+ try {
+ baseRange.setStart(null, 0);
+ do_throw("Should have thrown NOT_OBJECT_ERR!");
+ } catch (e) {
+ do_check_eq(e.constructor.name, "TypeError");
+ }
+
+ // Invalid start node
+ try {
+ baseRange.setStart({}, 0);
+ do_throw("Should have thrown SecurityError!");
+ } catch (e) {
+ do_check_eq(e.constructor.name, "TypeError");
+ }
+
+ // Invalid index
+ try {
+ baseRange.setStart(startContainer, -1);
+ do_throw("Should have thrown IndexSizeError!");
+ } catch (e) {
+ do_check_eq(e.name, "IndexSizeError");
+ }
+
+ // Invalid index
+ var newOffset = startContainer instanceof C_i.nsIDOMText ?
+ startContainer.nodeValue.length + 1 :
+ startContainer.childNodes.length + 1;
+ try {
+ baseRange.setStart(startContainer, newOffset);
+ do_throw("Should have thrown IndexSizeError!");
+ } catch (e) {
+ do_check_eq(e.name, "IndexSizeError");
+ }
+
+ newOffset--;
+ // Valid index
+ baseRange.setStart(startContainer, newOffset);
+ do_check_eq(baseRange.startContainer, baseRange.endContainer);
+ do_check_eq(baseRange.startOffset, newOffset);
+ do_check_true(baseRange.collapsed);
+
+ // Valid index
+ baseRange.setEnd(startContainer, 0);
+ do_check_eq(baseRange.startContainer, baseRange.endContainer);
+ do_check_eq(baseRange.startOffset, 0);
+ do_check_true(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;
+ var startOffset = baseRange.startOffset;
+ endContainer = baseRange.endContainer;
+ var 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);
+ do_check_eq(baseRange.startContainer, externalRange.endContainer);
+ do_check_eq(baseRange.startOffset, 0);
+ do_check_true(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");
+ do_check_true(doc instanceof C_i.nsIDOMDocument);
+ do_check_eq(doc.childNodes.length, 2);
+ baseRange = doc.createRange();
+ baseRange.setStart(doc.firstChild, 1);
+ baseRange.setEnd(doc.firstChild, 2);
+ var frag = baseRange.extractContents();
+ do_check_eq(frag.childNodes.length, 1);
+ do_check_true(frag.firstChild instanceof C_i.nsIDOMComment);
+ do_check_eq(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_thirdpartyutil.js b/dom/base/test/unit/test_thirdpartyutil.js
new file mode 100644
index 000000000..2c4ef4745
--- /dev/null
+++ b/dom/base/test/unit/test_thirdpartyutil.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test ThirdPartyUtil methods. See mozIThirdPartyUtil.
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+// 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 = Components.results.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) {
+ do_check_eq(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});
+ let channel2 = 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);
+ let filechannel1 = NetUtil.newChannel({uri: fileuri1, loadUsingSystemPrincipal: true});
+ let filechannel2 = NetUtil.newChannel({uri: fileuri2, loadUsingSystemPrincipal: true});
+
+ // Test isThirdPartyURI.
+ do_check_false(util.isThirdPartyURI(uri1, uri1));
+ do_check_true(util.isThirdPartyURI(uri1, uri2));
+ do_check_true(util.isThirdPartyURI(uri2, uri1));
+ do_check_false(util.isThirdPartyURI(fileuri1, fileuri1));
+ do_check_false(util.isThirdPartyURI(fileuri1, fileuri2));
+ do_check_true(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);
+ do_check_false(util.isThirdPartyChannel(channel1));
+ do_check_false(util.isThirdPartyChannel(channel1, uri1));
+ do_check_true(util.isThirdPartyChannel(channel1, uri2));
+
+ let httpchannel1 = channel1.QueryInterface(Ci.nsIHttpChannelInternal);
+ httpchannel1.forceAllowThirdPartyCookie = true;
+ do_check_false(util.isThirdPartyChannel(channel1));
+ do_check_false(util.isThirdPartyChannel(channel1, uri1));
+ do_check_true(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 000000000..e9aff3435
--- /dev/null
+++ b/dom/base/test/unit/test_treewalker.js
@@ -0,0 +1,26 @@
+/* -*- 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 = I.nsIDOMNodeFilter.SHOW_ELEMENT | I.nsIDOMNodeFilter.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 000000000..5a090d598
--- /dev/null
+++ b/dom/base/test/unit/test_xhr_document.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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) {
+ 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 = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
+ parser.init();
+ let doc = parser.parseFromString(docbody, "text/html");
+ let xhr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Ci.nsIXMLHttpRequest);
+ xhr.onload = function() {
+ do_check_eq(xhr.responseText, docbody);
+ server.stop(do_test_finished);
+ };
+ xhr.onerror = function() {
+ do_check_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 000000000..5c53771da
--- /dev/null
+++ b/dom/base/test/unit/test_xhr_origin_attributes.js
@@ -0,0 +1,50 @@
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://testing-common/httpd.js");
+
+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 = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Ci.nsIXMLHttpRequest);
+ 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 = Cc['@mozilla.org/xmlextras/xmlhttprequest;1'].createInstance(Ci.nsIXMLHttpRequest);
+ 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 000000000..e1e55f3f5
--- /dev/null
+++ b/dom/base/test/unit/test_xhr_standalone.js
@@ -0,0 +1,21 @@
+/* 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 = Components.classes['@mozilla.org/xmlextras/xmlhttprequest;1'].
+ createInstance(Components.interfaces.nsIXMLHttpRequest);
+ xhr.open('GET', 'data:,', false);
+ var exceptionThrown = false;
+ try {
+ xhr.responseType = '';
+ xhr.withCredentials = false;
+ } catch (e) {
+ exceptionThrown = true;
+ }
+ do_check_eq(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 000000000..a87c21672
--- /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 nsIDOMDocument" ],
+ [ test3, "ParseXML return value's documentElement is not nsIDOMElement" ],
+ [ test4, "" ],
+ [ test5, "" ],
+ [ test6, "" ],
+ [ null ]
+];
+
+function test1() {
+ return ParseXML("<root/>");
+}
+
+function test2() {
+ return (ParseXML("<root/>") instanceof nsIDOMDocument);
+}
+
+function test3() {
+ return (ParseXML("<root/>").documentElement instanceof nsIDOMElement);
+}
+
+function test4() {
+ var doc = ParseXML("<root/>");
+ do_check_eq(doc.documentElement.namespaceURI, null);
+ return true;
+}
+
+function test5() {
+ var doc = ParseXML("<root xmlns=''/>");
+ do_check_eq(doc.documentElement.namespaceURI, null);
+ return true;
+}
+
+function test6() {
+ var doc = ParseXML("<root xmlns='ns1'/>");
+ do_check_neq(doc.documentElement.namespaceURI, null);
+ do_check_eq(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 000000000..c74d067a8
--- /dev/null
+++ b/dom/base/test/unit/test_xml_serializer.js
@@ -0,0 +1,374 @@
+
+// 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) {
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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;
+ // Have to QI here -- no classinfo flattening in xpcshell, apparently
+ var node = root.firstChild.firstChild.QueryInterface(nsIDOMElement);
+ 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
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(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);
+ do_check_eq(SerializeXML(doc), '<root><child1/></root>');
+
+ doc = ParseXML('<root xmlns="http://www.w3.org/1999/xhtml">' +
+ '<child1 xmlns=""><child2/></child1></root>')
+ root = doc.documentElement;
+
+ // No interface flattening in xpcshell
+ var child1 = root.firstChild.QueryInterface(nsIDOMElement);
+ child1.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns",
+ "http://www.w3.org/1999/xhtml");
+ do_check_serialize(doc);
+ do_check_eq(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;
+ // No interface flattening in xpcshell
+ child1 = root.firstChild.QueryInterface(nsIDOMElement);
+ var child2 = child1.firstChild.QueryInterface(nsIDOMElement);
+ 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);
+ do_check_eq(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="ISO-8859-1"?>'+LB+'<root/>';
+ var str2 = '<?xml version="1.0" encoding="UTF8"?>'+LB+'<root/>';
+ var doc1 = ParseXML(str1);
+ var doc2 = ParseXML(str2);
+
+ var p = Pipe();
+ DOMSerializer().serializeToStream(doc1, p.outputStream, "ISO-8859-1");
+ p.outputStream.close();
+ do_check_eq(ScriptableInput(p).read(-1), str1);
+
+ p = Pipe();
+ DOMSerializer().serializeToStream(doc2, p.outputStream, "ISO-8859-1");
+ p.outputStream.close();
+ do_check_eq(ScriptableInput(p).read(-1), str1);
+
+ p = Pipe();
+ DOMSerializer().serializeToStream(doc1, p.outputStream, "UTF8");
+ p.outputStream.close();
+ do_check_eq(ScriptableInput(p).read(-1), str2);
+
+ p = Pipe();
+ DOMSerializer().serializeToStream(doc2, p.outputStream, "UTF8");
+ p.outputStream.close();
+ do_check_eq(ScriptableInput(p).read(-1), str2);
+}
+
+function test9() {
+ // Test behavior of serializing between given charsets, using
+ // ISO-8859-1-representable text.
+ var contents = '<root>' +
+ '\u00BD + \u00BE == \u00BD\u00B2 + \u00BC + \u00BE' +
+ '</root>';
+ var str1 = '<?xml version="1.0" encoding="ISO-8859-1"?>'+ LB + contents;
+ var str2 = '<?xml version="1.0" encoding="UTF8"?>'+ 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, "ISO-8859-1", str1);
+ checkSerialization(doc2, "ISO-8859-1", str1);
+ checkSerialization(doc3, "ISO-8859-1", str1);
+
+ checkSerialization(doc1, "UTF8", str2);
+ checkSerialization(doc2, "UTF8", str2);
+ checkSerialization(doc3, "UTF8", str2);
+
+ checkSerialization(doc1, "UTF-16", str3);
+ checkSerialization(doc2, "UTF-16", str3);
+ checkSerialization(doc3, "UTF-16", str3);
+}
+
+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="UTF8"?>'+ 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", str2);
+ checkSerialization(doc2, "UTF-16", str2);
+}
+
+function checkSerialization(doc, toCharset, expectedString) {
+ var p = Pipe();
+ DOMSerializer().serializeToStream(doc, p.outputStream, toCharset);
+ p.outputStream.close();
+
+ var cin = C["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(I.nsIConverterInputStream);
+ cin.init(p.inputStream, toCharset, 1024, 0x0);
+
+ // compare the first expectedString.length characters for equality
+ var outString = {};
+ var count = cin.readString(expectedString.length, outString);
+ do_check_true(count == expectedString.length);
+ do_check_true(outString.value == expectedString);
+
+ // if there's anything more in the stream, it's a bug
+ do_check_eq(0, cin.readString(1, outString));
+ do_check_eq(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 000000000..8c6020a70
--- /dev/null
+++ b/dom/base/test/unit/test_xmlserializer.js
@@ -0,0 +1,112 @@
+/* -*- 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/. */
+
+function xmlEncode(aFile, aFlags, aCharset) {
+ if(aFlags == undefined) aFlags = 0;
+ if(aCharset == undefined) aCharset = "UTF-8";
+
+ return do_parse_document(aFile, "text/xml").then(doc => {
+ var encoder = Components.classes["@mozilla.org/layout/documentEncoder;1?type=text/xml"]
+ .createInstance(nsIDocumentEncoder);
+ encoder.setCharset(aCharset);
+ encoder.init(doc, "text/xml", aFlags);
+ return encoder.encodeToString();
+ });
+}
+
+function run_test()
+{
+ var result, expected;
+ const de = Components.interfaces.nsIDocumentEncoder;
+
+ xmlEncode("1_original.xml", de.OutputLFLineBreak).then(result => {
+ expected = loadContentFile("1_result.xml");
+ do_check_eq(expected, result);
+ });
+
+ xmlEncode("2_original.xml", de.OutputLFLineBreak).then(result => {
+ expected = loadContentFile("2_result_1.xml");
+ do_check_eq(expected, result);
+ });
+
+ xmlEncode("2_original.xml", de.OutputCRLineBreak).then(result => {
+ expected = expected.replace(/\n/g, "\r");
+ do_check_eq(expected, result);
+ });
+
+ xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputCRLineBreak).then(result => {
+ expected = expected.replace(/\r/mg, "\r\n");
+ do_check_eq(expected, result);
+ });
+
+ xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputFormatted).then(result => {
+ expected = loadContentFile("2_result_2.xml");
+ do_check_eq(expected, result);
+ });
+
+ xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap).then(result => {
+ expected = loadContentFile("2_result_3.xml");
+ do_check_eq(expected, result);
+ });
+
+ xmlEncode("2_original.xml", de.OutputLFLineBreak | de.OutputWrap).then(result => {
+ expected = loadContentFile("2_result_4.xml");
+ do_check_eq(expected, result);
+ });
+
+ xmlEncode("3_original.xml", de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap).then(result => {
+ expected = loadContentFile("3_result.xml");
+ do_check_eq(expected, result);
+ });
+
+ xmlEncode("3_original.xml", de.OutputLFLineBreak | de.OutputWrap).then(result => {
+ expected = loadContentFile("3_result_2.xml");
+ do_check_eq(expected, result);
+ });
+
+ // tests on namespaces
+ do_parse_document("4_original.xml", "text/xml").then(run_namespace_tests);
+}
+
+function run_namespace_tests(doc) {
+ const de = Components.interfaces.nsIDocumentEncoder;
+ var encoder = Components.classes["@mozilla.org/layout/documentEncoder;1?type=text/xml"]
+ .createInstance(nsIDocumentEncoder);
+ encoder.setCharset("UTF-8");
+ encoder.init(doc, "text/xml", de.OutputLFLineBreak);
+
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_1.xml");
+ do_check_eq(expected, result);
+
+ encoder.setNode(doc.documentElement.childNodes[9]);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_2.xml");
+ do_check_eq(expected, result);
+
+ encoder.setNode(doc.documentElement.childNodes[7].childNodes[1]);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_3.xml");
+ do_check_eq(expected, result);
+
+ encoder.setNode(doc.documentElement.childNodes[11].childNodes[1]);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_4.xml");
+ do_check_eq(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");
+ do_check_eq(expected, result);
+
+ encoder.init(doc, "text/xml", de.OutputLFLineBreak | de.OutputWrap);
+ encoder.setWrapColumn(40);
+ result = encoder.encodeToString();
+ expected = loadContentFile("4_result_6.xml");
+ do_check_eq(expected, result);
+}
diff --git a/dom/base/test/unit/xpcshell.ini b/dom/base/test/unit/xpcshell.ini
new file mode 100644
index 000000000..201b53d91
--- /dev/null
+++ b/dom/base/test/unit/xpcshell.ini
@@ -0,0 +1,55 @@
+[DEFAULT]
+head = head_utilities.js
+tail =
+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.xul
+ test_delete_range.xml
+
+[test_bloburi.js]
+[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_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_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]
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 000000000..3f16a1e4a
--- /dev/null
+++ b/dom/base/test/unit_ipc/test_bug553888_wrap.js
@@ -0,0 +1,4 @@
+function run_test()
+{
+ run_test_in_child("../unit/test_bug553888.js");
+} \ No newline at end of file
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 000000000..1f65969b2
--- /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 000000000..d05d300cf
--- /dev/null
+++ b/dom/base/test/unit_ipc/xpcshell.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+head =
+tail =
+skip-if = toolkit == 'android'
+support-files =
+ !/dom/base/test/unit/test_bug553888.js
+ !/dom/base/test/unit/test_xhr_document.js
+
+[test_bug553888_wrap.js]
+[test_xhr_document_ipc.js]
diff --git a/dom/base/test/variable_style_sheet.sjs b/dom/base/test/variable_style_sheet.sjs
new file mode 100644
index 000000000..def6465ef
--- /dev/null
+++ b/dom/base/test/variable_style_sheet.sjs
@@ -0,0 +1,19 @@
+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/viewport_helpers.js b/dom/base/test/viewport_helpers.js
new file mode 100644
index 000000000..8ad9d2b39
--- /dev/null
+++ b/dom/base/test/viewport_helpers.js
@@ -0,0 +1,3 @@
+function scaleRatio(scale) {
+ return {"set": [["layout.css.devPixelsPerPx", "" + scale]]};
+}
diff --git a/dom/base/test/w3element_traversal.svg b/dom/base/test/w3element_traversal.svg
new file mode 100644
index 000000000..22540b464
--- /dev/null
+++ b/dom/base/test/w3element_traversal.svg
@@ -0,0 +1,70 @@
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+<!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/websocket_helpers.js b/dom/base/test/websocket_helpers.js
new file mode 100644
index 000000000..46cf765d7
--- /dev/null
+++ b/dom/base/test/websocket_helpers.js
@@ -0,0 +1,66 @@
+var current_test = 0;
+
+function shouldNotOpen(e) {
+ var ws = e.target;
+ ok(false, "onopen shouldn't be called on test " + ws._testNumber + "!");
+}
+
+function shouldCloseCleanly(e) {
+ var ws = e.target;
+ ok(e.wasClean, "the ws connection in test " + ws._testNumber + " should be closed cleanly");
+}
+
+function shouldCloseNotCleanly(e) {
+ var ws = e.target;
+ ok(!e.wasClean, "the ws connection in test " + ws._testNumber + " shouldn't be closed cleanly");
+}
+
+function ignoreError(e) {
+}
+
+function CreateTestWS(ws_location, ws_protocol) {
+ var ws;
+
+ try {
+ if (ws_protocol == undefined) {
+ ws = new WebSocket(ws_location);
+ } else {
+ ws = new WebSocket(ws_location, ws_protocol);
+ }
+
+ ws._testNumber = current_test;
+ ok(true, "Created websocket for test " + ws._testNumber +"\n");
+
+ ws.onerror = function(e) {
+ ok(false, "onerror called on test " + e.target._testNumber + "!");
+ }
+
+ } catch (e) {
+ throw e;
+ }
+
+ return ws;
+}
+
+function forcegc() {
+ SpecialPowers.forceGC();
+ SpecialPowers.gc();
+}
+
+function feedback() {
+ $("feedback").innerHTML = "executing test: " + (current_test+1) + " of " + tests.length + " tests.";
+}
+
+function finish() {
+ SimpleTest.finish();
+}
+
+function doTest() {
+ if (current_test >= tests.length) {
+ finish();
+ return;
+ }
+
+ feedback();
+ tests[current_test++]().then(doTest);
+}
diff --git a/dom/base/test/websocket_hybi/file_binary-frames_wsh.py b/dom/base/test/websocket_hybi/file_binary-frames_wsh.py
new file mode 100644
index 000000000..656d0e189
--- /dev/null
+++ b/dom/base/test/websocket_hybi/file_binary-frames_wsh.py
@@ -0,0 +1,18 @@
+from mod_pywebsocket import common
+from mod_pywebsocket import stream
+
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+
+def web_socket_transfer_data(request):
+ messages_to_send = ['Hello, world!', '', all_distinct_bytes()]
+ for message in messages_to_send:
+ # FIXME: Should use better API to send binary messages when pywebsocket supports it.
+ header = stream.create_header(common.OPCODE_BINARY, len(message), 1, 0, 0, 0, 0)
+ request.connection.write(header + message)
+
+
+def all_distinct_bytes():
+ return ''.join([chr(i) for i in xrange(256)])
diff --git a/dom/base/test/websocket_hybi/file_check-binary-messages_wsh.py b/dom/base/test/websocket_hybi/file_check-binary-messages_wsh.py
new file mode 100644
index 000000000..024e3f4d0
--- /dev/null
+++ b/dom/base/test/websocket_hybi/file_check-binary-messages_wsh.py
@@ -0,0 +1,21 @@
+from mod_pywebsocket import common
+from mod_pywebsocket import msgutil
+
+
+def web_socket_do_extra_handshake(request):
+ pass # Always accept.
+
+
+def web_socket_transfer_data(request):
+ expected_messages = ['Hello, world!', '', all_distinct_bytes()]
+
+ for test_number, expected_message in enumerate(expected_messages):
+ message = msgutil.receive_message(request)
+ if type(message) == str and message == expected_message:
+ msgutil.send_message(request, 'PASS: Message #%d.' % test_number)
+ else:
+ msgutil.send_message(request, 'FAIL: Message #%d: Received unexpected message: %r' % (test_number, message))
+
+
+def all_distinct_bytes():
+ return ''.join([chr(i) for i in xrange(256)])
diff --git a/dom/base/test/websocket_hybi/mochitest.ini b/dom/base/test/websocket_hybi/mochitest.ini
new file mode 100644
index 000000000..21d61c8c8
--- /dev/null
+++ b/dom/base/test/websocket_hybi/mochitest.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+ file_binary-frames_wsh.py
+ file_check-binary-messages_wsh.py
+
+[test_receive-arraybuffer.html]
+skip-if = toolkit == 'android'
+[test_receive-blob.html]
+skip-if = toolkit == 'android'
+[test_send-arraybuffer.html]
+skip-if = toolkit == 'android'
+[test_send-blob.html]
+skip-if = toolkit == 'android'
diff --git a/dom/base/test/websocket_hybi/test_receive-arraybuffer.html b/dom/base/test/websocket_hybi/test_receive-arraybuffer.html
new file mode 100644
index 000000000..0bd1cfefc
--- /dev/null
+++ b/dom/base/test/websocket_hybi/test_receive-arraybuffer.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="text/javascript" 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">
+
+function debug(msg) {
+ ok(1, msg);
+}
+
+function createArrayBufferContainingHelloWorld()
+{
+ var hello = "Hello, world!";
+ var array = new Uint8Array(hello.length);
+ for (var i = 0; i < hello.length; ++i)
+ array[i] = hello.charCodeAt(i);
+ return array.buffer;
+}
+
+function createEmptyArrayBuffer()
+{
+ return new ArrayBuffer(0);
+}
+
+function createArrayBufferContainingAllDistinctBytes()
+{
+ var array = new Uint8Array(256);
+ for (var i = 0; i < 256; ++i)
+ array[i] = i;
+ return array.buffer;
+}
+
+var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/websocket_hybi/file_binary-frames");
+ws.binaryType = "arraybuffer";
+is(ws.binaryType, "arraybuffer", "should be equal to 'arraybuffer'");
+
+var closeEvent;
+var receivedMessages = [];
+var expectedValues = [createArrayBufferContainingHelloWorld(), createEmptyArrayBuffer(), createArrayBufferContainingAllDistinctBytes()];
+
+ws.onmessage = function(event)
+{
+ receivedMessages.push(event.data);
+};
+
+ws.onclose = function(event)
+{
+ closeEvent = event;
+
+ is(receivedMessages.length, expectedValues.length, "lengths not equal");
+ for (var i = 0; i < expectedValues.length; ++i)
+ check(i);
+ SimpleTest.finish();
+};
+
+var responseType;
+
+function check(index)
+{
+ debug("Checking message #" + index + ".");
+ ok(receivedMessages[index] instanceof ArrayBuffer,
+ "Should receive an arraybuffer!");
+ checkArrayBuffer(index, receivedMessages[index], expectedValues[index]);
+}
+
+var actualArray;
+var expectedArray;
+
+function checkArrayBuffer(testIndex, actual, expected)
+{
+ actualArray = new Uint8Array(actual);
+ expectedArray = new Uint8Array(expected);
+ is(actualArray.length, expectedArray.length, "lengths not equal");
+ // Print only the first mismatched byte in order not to flood console.
+ for (var i = 0; i < expectedArray.length; ++i) {
+ if (actualArray[i] != expectedArray[i]) {
+ ok(false, "Value mismatch: actualArray[" + i + "] = " + actualArray[i] + ", expectedArray[" + i + "] = " + expectedArray[i]);
+ return;
+ }
+ }
+ ok(true, "Passed: Message #" + testIndex + ".");
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/websocket_hybi/test_receive-blob.html b/dom/base/test/websocket_hybi/test_receive-blob.html
new file mode 100644
index 000000000..cd8beceec
--- /dev/null
+++ b/dom/base/test/websocket_hybi/test_receive-blob.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="text/javascript" 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">
+
+function debug(msg) {
+ ok(true, msg);
+}
+
+function createArrayBufferContainingHelloWorld()
+{
+ var hello = "Hello, world!";
+ var array = new Uint8Array(hello.length);
+ for (var i = 0; i < hello.length; ++i)
+ array[i] = hello.charCodeAt(i);
+ return array.buffer;
+}
+
+function createEmptyArrayBuffer()
+{
+ return new ArrayBuffer(0);
+}
+
+function createArrayBufferContainingAllDistinctBytes()
+{
+ var array = new Uint8Array(256);
+ for (var i = 0; i < 256; ++i)
+ array[i] = i;
+ return array.buffer;
+}
+
+var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/websocket_hybi/file_binary-frames");
+is(ws.binaryType, "blob", "should be 'blob'");
+
+var closeEvent;
+var receivedMessages = [];
+var expectedValues = [createArrayBufferContainingHelloWorld(), createEmptyArrayBuffer(), createArrayBufferContainingAllDistinctBytes()];
+
+ws.onmessage = function(event)
+{
+ receivedMessages.push(event.data);
+};
+
+ws.onclose = function(event)
+{
+ closeEvent = event;
+
+ is(receivedMessages.length, expectedValues.length, "lengths not same");
+ check(0);
+};
+
+var responseType;
+
+function check(index)
+{
+ if (index == expectedValues.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ debug("Checking message #" + index + ".");
+ ok(receivedMessages[index] instanceof Blob,
+ "We should be receiving a Blob");
+ var reader = new FileReader();
+ reader.readAsArrayBuffer(receivedMessages[index]);
+ reader.onload = function(event)
+ {
+ checkArrayBuffer(index, reader.result, expectedValues[index]);
+ check(index + 1);
+ };
+ reader.onerror = function(event)
+ {
+ ok(false, "Failed to read blob: error code = " + reader.error.code);
+ check(index + 1);
+ };
+}
+
+var actualArray;
+var expectedArray;
+
+function checkArrayBuffer(testIndex, actual, expected)
+{
+ actualArray = new Uint8Array(actual);
+ expectedArray = new Uint8Array(expected);
+ is(actualArray.length, expectedArray.length, "lengths not same");
+ // Print only the first mismatched byte in order not to flood console.
+ for (var i = 0; i < expectedArray.length; ++i) {
+ if (actualArray[i] != expectedArray[i]) {
+ ok(false, "Value mismatch: actualArray[" + i + "] = " + actualArray[i] + ", expectedArray[" + i + "] = " + expectedArray[i]);
+ return;
+ }
+ }
+ ok(true, "Passed: Message #" + testIndex + ".");
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/websocket_hybi/test_send-arraybuffer.html b/dom/base/test/websocket_hybi/test_send-arraybuffer.html
new file mode 100644
index 000000000..4db0746fb
--- /dev/null
+++ b/dom/base/test/websocket_hybi/test_send-arraybuffer.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="text/javascript" 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">
+
+function debug(msg) {
+ ok(1, msg);
+}
+
+function startsWith(target, prefix)
+{
+ return target.indexOf(prefix) === 0;
+}
+
+function createArrayBufferContainingHelloWorld()
+{
+ var hello = "Hello, world!";
+ var array = new Uint8Array(hello.length);
+ for (var i = 0; i < hello.length; ++i)
+ array[i] = hello.charCodeAt(i);
+ return array.buffer;
+}
+
+function createEmptyArrayBuffer()
+{
+ return new ArrayBuffer(0);
+}
+
+function createArrayBufferContainingAllDistinctBytes()
+{
+ var array = new Uint8Array(256);
+ for (var i = 0; i < 256; ++i)
+ array[i] = i;
+ return array.buffer;
+}
+
+var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/websocket_hybi/file_check-binary-messages");
+var closeEvent;
+
+ws.onopen = function()
+{
+ ok(true, "onopen reached");
+ ws.send(createArrayBufferContainingHelloWorld());
+ ws.send(createEmptyArrayBuffer());
+ ws.send(createArrayBufferContainingAllDistinctBytes());
+};
+
+ws.onmessage = function(event)
+{
+ var message = event.data;
+ if (startsWith(message, "PASS"))
+ ok(true, message);
+ else
+ ok(false, message);
+};
+
+ws.onclose = function(event)
+{
+ ok(event.wasClean, "should have closed cleanly");
+ SimpleTest.finish();
+};
+
+ws.onerror = function(event)
+{
+ ok(false, "onerror should not have been called");
+};
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/websocket_hybi/test_send-blob.html b/dom/base/test/websocket_hybi/test_send-blob.html
new file mode 100644
index 000000000..291883cd7
--- /dev/null
+++ b/dom/base/test/websocket_hybi/test_send-blob.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="text/javascript" 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">
+
+function startsWith(target, prefix)
+{
+ return target.indexOf(prefix) === 0;
+}
+
+function distinctBytes()
+{
+ var array = new Array();
+ for (var i = 0; i < 256; ++i)
+ array[i] = i;
+ // Concatenates chars into a single binary string
+ return String.fromCharCode.apply(null, array);
+}
+
+var filesToCreate = [
+ {name: "hellofile", data: "Hello, world!"},
+ {name: "emptyfile"},
+ {name: "allchars", data: distinctBytes()},
+];
+
+SpecialPowers.createFiles(filesToCreate, function (files) {
+ var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/websocket_hybi/file_check-binary-messages");
+ var closeEvent;
+
+ ws.onopen = function()
+ {
+ ws.send(files[0]);
+ ws.send(files[1]);
+ ws.send(files[2]);
+ };
+
+ ws.onmessage = function(event)
+ {
+ var message = event.data;
+ if (startsWith(message, "PASS"))
+ ok(true, message);
+ else
+ ok(false, message);
+ };
+
+ ws.onclose = function(event)
+ {
+ ok(event.wasClean, "should have closed cleanly");
+ SimpleTest.finish();
+ };
+},
+function (msg) {
+ ok(false, "Failed to create files: " + msg);
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/websocket_tests.js b/dom/base/test/websocket_tests.js
new file mode 100644
index 000000000..e62896507
--- /dev/null
+++ b/dom/base/test/websocket_tests.js
@@ -0,0 +1,1244 @@
+// test1: client tries to connect to a http scheme location;
+function test1() {
+ return new Promise(function(resolve, reject) {
+ try {
+ var ws = CreateTestWS("http://mochi.test:8888/tests/dom/base/test/file_websocket");
+ ok(false, "test1 failed");
+ } catch (e) {
+ ok(true, "test1 failed");
+ }
+
+ resolve();
+ });
+}
+
+// test2: assure serialization of the connections;
+// this test expects that the serialization list to connect to the proxy
+// is empty.
+function test2() {
+ return new Promise(function(resolve, reject) {
+ var waitTest2Part1 = true;
+ var waitTest2Part2 = true;
+
+ var ws1 = CreateTestWS("ws://sub2.test2.example.com/tests/dom/base/test/file_websocket", "test-2.1");
+ var ws2 = CreateTestWS("ws://sub2.test2.example.com/tests/dom/base/test/file_websocket", "test-2.2");
+
+ var ws2CanConnect = false;
+
+ function maybeFinished() {
+ if (!waitTest2Part1 && !waitTest2Part2) {
+ resolve();
+ }
+ }
+
+ ws1.onopen = function() {
+ ok(true, "ws1 open in test 2");
+ ws2CanConnect = true;
+ ws1.close();
+ }
+
+ ws1.onclose = function(e) {
+ waitTest2Part1 = false;
+ maybeFinished();
+ }
+
+ ws2.onopen = function() {
+ ok(ws2CanConnect, "shouldn't connect yet in test-2!");
+ ws2.close();
+ }
+
+ ws2.onclose = function(e) {
+ waitTest2Part2 = false;
+ maybeFinished();
+ }
+ });
+}
+
+// test3: client tries to connect to an non-existent ws server;
+function test3() {
+ return new Promise(function(resolve, reject) {
+ var hasError = false;
+ var ws = CreateTestWS("ws://this.websocket.server.probably.does.not.exist");
+
+ ws.onopen = shouldNotOpen;
+
+ ws.onerror = function (e) {
+ hasError = true;
+ }
+
+ ws.onclose = function(e) {
+ shouldCloseNotCleanly(e);
+ ok(hasError, "rcvd onerror event");
+ is(e.code, 1006, "test-3 close code should be 1006 but is:" + e.code);
+ resolve();
+ }
+ });
+}
+
+// test4: client tries to connect using a relative url;
+function test4() {
+ return new Promise(function(resolve, reject) {
+ try {
+ var ws = CreateTestWS("file_websocket");
+ ok(false, "test-4 failed");
+ } catch (e) {
+ ok(true, "test-4 failed");
+ }
+
+ resolve();
+ });
+}
+
+// test5: client uses an invalid protocol value;
+function test5() {
+ return new Promise(function(resolve, reject) {
+ try {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "");
+ ok(false, "couldn't accept an empty string in the protocol parameter");
+ } catch (e) {
+ ok(true, "couldn't accept an empty string in the protocol parameter");
+ }
+
+ try {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "\n");
+ ok(false, "couldn't accept any not printable ASCII character in the protocol parameter");
+ } catch (e) {
+ ok(true, "couldn't accept any not printable ASCII character in the protocol parameter");
+ }
+
+ try {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test 5");
+ ok(false, "U+0020 not acceptable in protocol parameter");
+ } catch (e) {
+ ok(true, "U+0020 not acceptable in protocol parameter");
+ }
+
+ resolve();
+ });
+}
+
+// test6: counter and encoding check;
+function test6() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-6");
+ var counter = 1;
+
+ ws.onopen = function() {
+ ws.send(counter);
+ }
+
+ ws.onmessage = function(e) {
+ if (counter == 5) {
+ is(e.data, "ã‚ã„ã†ãˆãŠ", "test-6 counter 5 data ok");
+ ws.close();
+ } else {
+ is(parseInt(e.data), counter+1, "bad counter");
+ counter += 2;
+ ws.send(counter);
+ }
+ }
+
+ ws.onclose = function(e) {
+ shouldCloseCleanly(e);
+ resolve();
+ }
+ });
+}
+
+// test7: onmessage event origin property check
+function test7() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://sub2.test2.example.org/tests/dom/base/test/file_websocket", "test-7");
+ var gotmsg = false;
+
+ ws.onopen = function() {
+ ok(true, "test 7 open");
+ }
+
+ ws.onmessage = function(e) {
+ ok(true, "test 7 message");
+ is(e.origin, "ws://sub2.test2.example.org", "onmessage origin set to ws:// host");
+ gotmsg = true;
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ ok(gotmsg, "recvd message in test 7 before close");
+ shouldCloseCleanly(e);
+ resolve();
+ }
+ });
+}
+
+// test8: client calls close() and the server sends the close frame (with no
+// code or reason) in acknowledgement;
+function test8() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-8");
+
+ ws.onopen = function() {
+ is(ws.protocol, "test-8", "test-8 subprotocol selection");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ shouldCloseCleanly(e);
+ // We called close() with no close code: so pywebsocket will also send no
+ // close code, which translates to code 1005
+ is(e.code, 1005, "test-8 close code has wrong value:" + e.code);
+ is(e.reason, "", "test-8 close reason has wrong value:" + e.reason);
+ resolve();
+ }
+ });
+}
+
+// test9: client closes the connection before the ws connection is established;
+function test9() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://test2.example.org/tests/dom/base/test/file_websocket", "test-9");
+
+ ws._receivedErrorEvent = false;
+
+ ws.onopen = shouldNotOpen;
+
+ ws.onerror = function(e) {
+ ws._receivedErrorEvent = true;
+ }
+
+ ws.onclose = function(e) {
+ ok(ws._receivedErrorEvent, "Didn't received the error event in test 9.");
+ shouldCloseNotCleanly(e);
+ resolve();
+ }
+
+ ws.close();
+ });
+}
+
+// test10: client sends a message before the ws connection is established;
+function test10() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://sub1.test1.example.com/tests/dom/base/test/file_websocket", "test-10");
+
+ ws.onclose = function(e) {
+ shouldCloseCleanly(e);
+ resolve();
+ }
+
+ try {
+ ws.send("client data");
+ ok(false, "Couldn't send data before connecting!");
+ } catch (e) {
+ ok(true, "Couldn't send data before connecting!");
+ }
+
+ ws.onopen = function()
+ {
+ ok(true, "test 10 opened");
+ ws.close();
+ }
+ });
+}
+
+// test11: a simple hello echo;
+function test11() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-11");
+ is(ws.readyState, 0, "create bad readyState in test-11!");
+
+ ws.onopen = function() {
+ is(ws.readyState, 1, "open bad readyState in test-11!");
+ ws.send("client data");
+ }
+
+ ws.onmessage = function(e) {
+ is(e.data, "server data", "bad received message in test-11!");
+ ws.close(1000, "Have a nice day");
+
+ // this ok() is disabled due to a race condition - it state may have
+ // advanced through 2 (closing) and into 3 (closed) before it is evald
+ // ok(ws.readyState == 2, "onmessage bad readyState in test-11!");
+ }
+
+ ws.onclose = function(e) {
+ is(ws.readyState, 3, "onclose bad readyState in test-11!");
+ shouldCloseCleanly(e);
+ is(e.code, 1000, "test 11 got wrong close code: " + e.code);
+ is(e.reason, "Have a nice day", "test 11 got wrong close reason: " + e.reason);
+ resolve();
+ }
+ });
+}
+
+// test12: client sends a message containing unpaired surrogates
+function test12() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-12");
+
+ ws.onopen = function() {
+ try {
+ // send an unpaired surrogate
+ ws._gotMessage = false;
+ ws.send("a\ud800b");
+ ok(true, "ok to send an unpaired surrogate");
+ } catch (e) {
+ ok(false, "shouldn't fail any more when sending an unpaired surrogate!");
+ }
+ }
+
+ ws.onmessage = function(msg) {
+ is(msg.data, "SUCCESS", "Unpaired surrogate in UTF-16 not converted in test-12");
+ ws._gotMessage = true;
+ // Must support unpaired surrogates in close reason, too
+ ws.close(1000, "a\ud800b");
+ }
+
+ ws.onclose = function(e) {
+ is(ws.readyState, 3, "onclose bad readyState in test-12!");
+ ok(ws._gotMessage, "didn't receive message!");
+ shouldCloseCleanly(e);
+ is(e.code, 1000, "test 12 got wrong close code: " + e.code);
+ is(e.reason, "a\ufffdb", "test 11 didn't get replacement char in close reason: " + e.reason);
+ resolve();
+ }
+ });
+}
+
+// test13: server sends an invalid message;
+function test13() {
+ return new Promise(function(resolve, reject) {
+ // previous versions of this test counted the number of protocol errors
+ // returned, but the protocol stack typically closes down after reporting a
+ // protocol level error - trying to resync is too dangerous
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-13");
+ ws._timesCalledOnError = 0;
+
+ ws.onerror = function() {
+ ws._timesCalledOnError++;
+ }
+
+ ws.onclose = function(e) {
+ ok(ws._timesCalledOnError > 0, "no error events");
+ resolve();
+ }
+ });
+}
+
+// test14: server sends the close frame, it doesn't close the tcp connection
+// and it keeps sending normal ws messages;
+function test14() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-14");
+
+ ws.onmessage = function() {
+ ok(false, "shouldn't received message after the server sent the close frame");
+ }
+
+ ws.onclose = function(e) {
+ shouldCloseCleanly(e);
+ resolve();
+ };
+ });
+}
+
+// test15: server closes the tcp connection, but it doesn't send the close
+// frame;
+function test15() {
+ return new Promise(function(resolve, reject) {
+ /*
+ * DISABLED: see comments for test-15 case in file_websocket_wsh.py
+ */
+ resolve();
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-15");
+ ws.onclose = function(e) {
+ shouldCloseNotCleanly(e);
+ resolve();
+ }
+
+ // termination of the connection might cause an error event if it happens in OPEN
+ ws.onerror = function() {
+ }
+ });
+}
+
+// test16: client calls close() and tries to send a message;
+function test16() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-16");
+
+ ws.onopen = function() {
+ ws.close();
+ ok(!ws.send("client data"), "shouldn't send message after calling close()");
+ }
+
+ ws.onmessage = function() {
+ ok(false, "shouldn't send message after calling close()");
+ }
+
+ ws.onerror = function() {
+ }
+
+ ws.onclose = function() {
+ resolve();
+ }
+ });
+}
+
+// test17: see bug 572975 - all event listeners set
+function test17() {
+ return new Promise(function(resolve, reject) {
+ var status_test17 = "not started";
+
+ var test17func = function() {
+ var local_ws = new WebSocket("ws://sub1.test2.example.org/tests/dom/base/test/file_websocket", "test-17");
+ status_test17 = "started";
+
+ local_ws.onopen = function(e) {
+ status_test17 = "opened";
+ e.target.send("client data");
+ forcegc();
+ };
+
+ local_ws.onerror = function() {
+ ok(false, "onerror called on test " + current_test + "!");
+ };
+
+ local_ws.onmessage = function(e) {
+ ok(e.data == "server data", "Bad message in test-17");
+ status_test17 = "got message";
+ forcegc();
+ };
+
+ local_ws.onclose = function(e) {
+ ok(status_test17 == "got message", "Didn't got message in test-17!");
+ shouldCloseCleanly(e);
+ status_test17 = "closed";
+ forcegc();
+ resolve();
+ };
+
+ window._test17 = null;
+ forcegc();
+ }
+
+ window._test17 = test17func;
+ window._test17();
+ });
+}
+
+// The tests that expects that their websockets neither open nor close MUST
+// be in the end of the tests, i.e. HERE, in order to prevent blocking the other
+// tests.
+
+// test18: client tries to connect to an http resource;
+function test18() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket_http_resource.txt");
+ ws.onopen = shouldNotOpen;
+ ws.onerror = ignoreError;
+ ws.onclose = function(e)
+ {
+ shouldCloseNotCleanly(e);
+ resolve();
+ }
+ });
+}
+
+// test19: server closes the tcp connection before establishing the ws
+// connection;
+function test19() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-19");
+ ws.onopen = shouldNotOpen;
+ ws.onerror = ignoreError;
+ ws.onclose = function(e)
+ {
+ shouldCloseNotCleanly(e);
+ resolve();
+ }
+ });
+}
+
+// test20: see bug 572975 - only on error and onclose event listeners set
+function test20() {
+ return new Promise(function(resolve, reject) {
+ var test20func = function() {
+ var local_ws = new WebSocket("ws://sub1.test1.example.org/tests/dom/base/test/file_websocket", "test-20");
+
+ local_ws.onerror = function() {
+ ok(false, "onerror called on test " + current_test + "!");
+ }
+
+ local_ws.onclose = function(e) {
+ ok(true, "test 20 closed despite gc");
+ resolve();
+ }
+
+ local_ws = null;
+ window._test20 = null;
+ forcegc();
+ }
+
+ window._test20 = test20func;
+ window._test20();
+ });
+}
+
+// test21: see bug 572975 - same as test 17, but delete strong event listeners
+// when receiving the message event;
+function test21() {
+ return new Promise(function(resolve, reject) {
+ var test21func = function() {
+ var local_ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-21");
+ var received_message = false;
+
+ local_ws.onopen = function(e) {
+ e.target.send("client data");
+ forcegc();
+ e.target.onopen = null;
+ forcegc();
+ }
+
+ local_ws.onerror = function() {
+ ok(false, "onerror called on test " + current_test + "!");
+ }
+
+ local_ws.onmessage = function(e) {
+ is(e.data, "server data", "Bad message in test-21");
+ received_message = true;
+ forcegc();
+ e.target.onmessage = null;
+ forcegc();
+ }
+
+ local_ws.onclose = function(e) {
+ shouldCloseCleanly(e);
+ ok(received_message, "close transitioned through onmessage");
+ resolve();
+ }
+
+ local_ws = null;
+ window._test21 = null;
+ forcegc();
+ }
+
+ window._test21 = test21func;
+ window._test21();
+ });
+}
+
+// test22: server takes too long to establish the ws connection;
+function test22() {
+ return new Promise(function(resolve, reject) {
+ const pref_open = "network.websocket.timeout.open";
+ SpecialPowers.setIntPref(pref_open, 5);
+
+ var ws = CreateTestWS("ws://sub2.test2.example.org/tests/dom/base/test/file_websocket", "test-22");
+
+ ws.onopen = shouldNotOpen;
+ ws.onerror = ignoreError;
+
+ ws.onclose = function(e) {
+ shouldCloseNotCleanly(e);
+ resolve();
+ }
+
+ SpecialPowers.clearUserPref(pref_open);
+ });
+}
+
+// test23: should detect WebSocket on window object;
+function test23() {
+ return new Promise(function(resolve, reject) {
+ ok("WebSocket" in window, "WebSocket should be available on window object");
+ resolve();
+ });
+}
+
+// test24: server rejects sub-protocol string
+function test24() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-does-not-exist");
+
+ ws.onopen = shouldNotOpen;
+ ws.onclose = function(e) {
+ shouldCloseNotCleanly(e);
+ resolve();
+ }
+
+ ws.onerror = function() {
+ }
+ });
+}
+
+// test25: ctor with valid empty sub-protocol array
+function test25() {
+ return new Promise(function(resolve, reject) {
+ var prots=[];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ // This test errors because the server requires a sub-protocol, but
+ // the test just wants to ensure that the ctor doesn't generate an
+ // exception
+ ws.onerror = ignoreError;
+ ws.onopen = shouldNotOpen;
+
+ ws.onclose = function(e) {
+ is(ws.protocol, "", "test25 subprotocol selection");
+ ok(true, "test 25 protocol array close");
+ resolve();
+ }
+ });
+}
+
+// test26: ctor with invalid sub-protocol array containing 1 empty element
+function test26() {
+ return new Promise(function(resolve, reject) {
+ var prots=[""];
+
+ try {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+ ok(false, "testing empty element sub protocol array");
+ } catch (e) {
+ ok(true, "testing empty sub element protocol array");
+ }
+
+ resolve();
+ });
+}
+
+// test27: ctor with invalid sub-protocol array containing an empty element in
+// list
+function test27() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test27", ""];
+
+ try {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+ ok(false, "testing empty element mixed sub protocol array");
+ } catch (e) {
+ ok(true, "testing empty element mixed sub protocol array");
+ }
+
+ resolve();
+ });
+}
+
+// test28: ctor using valid 1 element sub-protocol array
+function test28() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test28"];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 28 protocol array open");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ is(ws.protocol, "test28", "test28 subprotocol selection");
+ ok(true, "test 28 protocol array close");
+ resolve();
+ }
+ });
+}
+
+// test29: ctor using all valid 5 element sub-protocol array
+function test29() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test29a", "test29b"];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 29 protocol array open");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 29 protocol array close");
+ resolve();
+ }
+ });
+}
+
+// test30: ctor using valid 1 element sub-protocol array with element server
+// will reject
+function test30() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-does-not-exist"];
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = shouldNotOpen;
+
+ ws.onclose = function(e) {
+ shouldCloseNotCleanly(e);
+ resolve();
+ }
+
+ ws.onerror = function() {
+ }
+ });
+}
+
+// test31: ctor using valid 2 element sub-protocol array with 1 element server
+// will reject and one server will accept
+function test31() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-does-not-exist", "test31"];
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 31 protocol array open");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ is(ws.protocol, "test31", "test31 subprotocol selection");
+ ok(true, "test 31 protocol array close");
+ resolve();
+ }
+ });
+}
+
+// test32: ctor using invalid sub-protocol array that contains duplicate items
+function test32() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test32","test32"];
+
+ try {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+ ok(false, "testing duplicated element sub protocol array");
+ } catch (e) {
+ ok(true, "testing duplicated sub element protocol array");
+ }
+
+ resolve();
+ });
+}
+
+// test33: test for sending/receiving custom close code (but no close reason)
+function test33() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test33"];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 33 open");
+ ws.close(3131); // pass code but not reason
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 33 close");
+ shouldCloseCleanly(e);
+ is(e.code, 3131, "test 33 got wrong close code: " + e.code);
+ is(e.reason, "", "test 33 got wrong close reason: " + e.reason);
+ resolve();
+ }
+ });
+}
+
+// test34: test for receiving custom close code and reason
+function test34() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-34"];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 34 open");
+ ws.close();
+ }
+
+ ws.onclose = function(e)
+ {
+ ok(true, "test 34 close");
+ ok(e.wasClean, "test 34 closed cleanly");
+ is(e.code, 1001, "test 34 custom server code");
+ is(e.reason, "going away now", "test 34 custom server reason");
+ resolve();
+ }
+ });
+}
+
+// test35: test for sending custom close code and reason
+function test35() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-35a");
+
+ ws.onopen = function(e) {
+ ok(true, "test 35a open");
+ ws.close(3500, "my code");
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 35a close");
+ ok(e.wasClean, "test 35a closed cleanly");
+ var wsb = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-35b");
+
+ wsb.onopen = function(e) {
+ ok(true, "test 35b open");
+ wsb.close();
+ }
+
+ wsb.onclose = function(e) {
+ ok(true, "test 35b close");
+ ok(e.wasClean, "test 35b closed cleanly");
+ is(e.code, 3501, "test 35 custom server code");
+ is(e.reason, "my code", "test 35 custom server reason");
+ resolve();
+ }
+ }
+ });
+}
+
+// test36: negative test for sending out of range close code
+function test36() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-36"];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 36 open");
+
+ try {
+ ws.close(13200);
+ ok(false, "testing custom close code out of range");
+ } catch (e) {
+ ok(true, "testing custom close code out of range");
+ ws.close(3200);
+ }
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 36 close");
+ ok(e.wasClean, "test 36 closed cleanly");
+ resolve();
+ }
+ });
+}
+
+// test37: negative test for too long of a close reason
+function test37() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-37"];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 37 open");
+
+ try {
+ ws.close(3100,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
+ ok(false, "testing custom close reason out of range");
+ } catch (e) {
+ ok(true, "testing custom close reason out of range");
+ ws.close(3100,"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012");
+ }
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 37 close");
+ ok(e.wasClean, "test 37 closed cleanly");
+
+ var wsb = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-37b");
+
+ wsb.onopen = function(e) {
+ // now test that a rejected close code and reason dont persist
+ ok(true, "test 37b open");
+ try {
+ wsb.close(3101,"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123");
+ ok(false, "testing custom close reason out of range 37b");
+ } catch (e) {
+ ok(true, "testing custom close reason out of range 37b");
+ wsb.close();
+ }
+ }
+
+ wsb.onclose = function(e) {
+ ok(true, "test 37b close");
+ ok(e.wasClean, "test 37b closed cleanly");
+
+ var wsc = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-37c");
+
+ wsc.onopen = function(e) {
+ ok(true, "test 37c open");
+ wsc.close();
+ }
+
+ wsc.onclose = function(e) {
+ isnot(e.code, 3101, "test 37c custom server code not present");
+ is(e.reason, "", "test 37c custom server reason not present");
+ resolve();
+ }
+ }
+ }
+ });
+}
+
+// test38: ensure extensions attribute is defined
+function test38() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-38"];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 38 open");
+ isnot(ws.extensions, undefined, "extensions attribute defined");
+ // is(ws.extensions, "deflate-stream", "extensions attribute deflate-stream");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 38 close");
+ resolve();
+ }
+ });
+}
+
+// test39: a basic wss:// connectivity test
+function test39() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-39"];
+
+ var ws = CreateTestWS("wss://example.com/tests/dom/base/test/file_websocket", prots);
+ status_test39 = "started";
+
+ ws.onopen = function(e) {
+ status_test39 = "opened";
+ ok(true, "test 39 open");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 39 close");
+ is(status_test39, "opened", "test 39 did open");
+ resolve();
+ }
+ });
+}
+
+// test40: negative test for wss:// with no cert
+function test40() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-40"];
+
+ var ws = CreateTestWS("wss://nocert.example.com/tests/dom/base/test/file_websocket", prots);
+
+ status_test40 = "started";
+ ws.onerror = ignoreError;
+
+ ws.onopen = function(e) {
+ status_test40 = "opened";
+ ok(false, "test 40 open");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 40 close");
+ is(status_test40, "started", "test 40 did not open");
+ resolve();
+ }
+ });
+}
+
+// test41: HSTS
+function test41() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://example.com/tests/dom/base/test/file_websocket", "test-41a", 1);
+
+ ws.onopen = function(e) {
+ ok(true, "test 41a open");
+ is(ws.url, "ws://example.com/tests/dom/base/test/file_websocket",
+ "test 41a initial ws should not be redirected");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 41a close");
+
+ // establish a hsts policy for example.com
+ var wsb = CreateTestWS("wss://example.com/tests/dom/base/test/file_websocket", "test-41b", 1);
+
+ wsb.onopen = function(e) {
+ ok(true, "test 41b open");
+ wsb.close();
+ }
+
+ wsb.onclose = function(e) {
+ ok(true, "test 41b close");
+
+ // try ws:// again, it should be done over wss:// now due to hsts
+ var wsc = CreateTestWS("ws://example.com/tests/dom/base/test/file_websocket", "test-41c");
+
+ wsc.onopen = function(e) {
+ ok(true, "test 41c open");
+ is(wsc.url, "wss://example.com/tests/dom/base/test/file_websocket",
+ "test 41c ws should be redirected by hsts to wss");
+ wsc.close();
+ }
+
+ wsc.onclose = function(e) {
+ ok(true, "test 41c close");
+
+ // clean up the STS state
+ const Ci = SpecialPowers.Ci;
+ var loadContext = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+ var flags = 0;
+ if (loadContext.usePrivateBrowsing)
+ flags |= Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
+ SpecialPowers.cleanUpSTSData("http://example.com", flags);
+ resolve();
+ }
+ }
+ }
+ });
+}
+
+// test42: non-char utf-8 sequences
+function test42() {
+ return new Promise(function(resolve, reject) {
+ // test some utf-8 non-characters. They should be allowed in the
+ // websockets context. Test via round trip echo.
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-42");
+ var data = ["U+FFFE \ufffe",
+ "U+FFFF \uffff",
+ "U+10FFFF \udbff\udfff"];
+ var index = 0;
+
+ ws.onopen = function() {
+ ws.send(data[0]);
+ ws.send(data[1]);
+ ws.send(data[2]);
+ }
+
+ ws.onmessage = function(e) {
+ is(e.data, data[index], "bad received message in test-42! index="+index);
+ index++;
+ if (index == 3) {
+ ws.close();
+ }
+ }
+
+ ws.onclose = function(e) {
+ resolve();
+ }
+ });
+}
+
+// test43: Test setting binaryType attribute
+function test43() {
+ return new Promise(function(resolve, reject) {
+ var prots=["test-43"];
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", prots);
+
+ ws.onopen = function(e) {
+ ok(true, "test 43 open");
+ // Test binaryType setting
+ ws.binaryType = "arraybuffer";
+ ws.binaryType = "blob";
+ ws.binaryType = ""; // illegal
+ is(ws.binaryType, "blob");
+ ws.binaryType = "ArrayBuffer"; // illegal
+ is(ws.binaryType, "blob");
+ ws.binaryType = "Blob"; // illegal
+ is(ws.binaryType, "blob");
+ ws.binaryType = "mcfoofluu"; // illegal
+ is(ws.binaryType, "blob");
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "test 43 close");
+ resolve();
+ }
+ });
+}
+
+// test44: Test sending/receving binary ArrayBuffer
+function test44() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-44");
+ is(ws.readyState, 0, "bad readyState in test-44!");
+ ws.binaryType = "arraybuffer";
+
+ ws.onopen = function() {
+ is(ws.readyState, 1, "open bad readyState in test-44!");
+ var buf = new ArrayBuffer(3);
+ // create byte view
+ var view = new Uint8Array(buf);
+ view[0] = 5;
+ view[1] = 0; // null byte
+ view[2] = 7;
+ ws.send(buf);
+ }
+
+ ws.onmessage = function(e) {
+ ok(e.data instanceof ArrayBuffer, "Should receive an arraybuffer!");
+ var view = new Uint8Array(e.data);
+ ok(view.length == 2 && view[0] == 0 && view[1] ==4, "testing Reply arraybuffer" );
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ is(ws.readyState, 3, "onclose bad readyState in test-44!");
+ shouldCloseCleanly(e);
+ resolve();
+ }
+ });
+}
+
+// test45: Test sending/receving binary Blob
+function test45() {
+ return new Promise(function(resolve, reject) {
+ function test45Real(blobFile) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-45");
+ is(ws.readyState, 0, "bad readyState in test-45!");
+ // ws.binaryType = "blob"; // Don't need to specify: blob is the default
+
+ ws.onopen = function() {
+ is(ws.readyState, 1, "open bad readyState in test-45!");
+ ws.send(blobFile);
+ }
+
+ var test45blob;
+
+ ws.onmessage = function(e) {
+ test45blob = e.data;
+ ok(test45blob instanceof Blob, "We should be receiving a Blob");
+
+ ws.close();
+ }
+
+ ws.onclose = function(e) {
+ is(ws.readyState, 3, "onclose bad readyState in test-45!");
+ shouldCloseCleanly(e);
+
+ // check blob contents
+ var reader = new FileReader();
+ reader.onload = function(event) {
+ is(reader.result, "flob", "response should be 'flob': got '"
+ + reader.result + "'");
+ }
+
+ reader.onerror = function(event) {
+ testFailed("Failed to read blob: error code = " + reader.error.code);
+ }
+
+ reader.onloadend = function(event) {
+ resolve();
+ }
+
+ reader.readAsBinaryString(test45blob);
+ }
+ }
+
+ SpecialPowers.createFiles([{name: "testBlobFile", data: "flob"}],
+ function(files) {
+ test45Real(files[0]);
+ },
+ function(msg) {
+ testFailed("Failed to create file for test45: " + msg);
+ resolve();
+ });
+ });
+}
+
+// test46: Test that we don't dispatch incoming msgs once in CLOSING state
+function test46() {
+ return new Promise(function(resolve, reject) {
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-46");
+ is(ws.readyState, 0, "create bad readyState in test-46!");
+
+ ws.onopen = function() {
+ is(ws.readyState, 1, "open bad readyState in test-46!");
+ ws.close()
+ is(ws.readyState, 2, "close must set readyState to 2 in test-46!");
+ }
+
+ ws.onmessage = function(e) {
+ ok(false, "received message after calling close in test-46!");
+ }
+
+ ws.onclose = function(e) {
+ is(ws.readyState, 3, "onclose bad readyState in test-46!");
+ shouldCloseCleanly(e);
+ resolve();
+ }
+ });
+}
+
+// test47: Make sure onerror/onclose aren't called during close()
+function test47() {
+ return new Promise(function(resolve, reject) {
+ var hasError = false;
+ var ws = CreateTestWS("ws://another.websocket.server.that.probably.does.not.exist");
+
+ ws.onopen = shouldNotOpen;
+
+ ws.onerror = function (e) {
+ is(ws.readyState, 3, "test-47: readyState should be CLOSED(3) in onerror: got "
+ + ws.readyState);
+ ok(!ws._withinClose, "onerror() called during close()!");
+ hasError = true;
+ }
+
+ ws.onclose = function(e) {
+ shouldCloseNotCleanly(e);
+ ok(hasError, "test-47: should have called onerror before onclose");
+ is(ws.readyState, 3, "test-47: readyState should be CLOSED(3) in onclose: got "
+ + ws.readyState);
+ ok(!ws._withinClose, "onclose() called during close()!");
+ is(e.code, 1006, "test-47 close code should be 1006 but is:" + e.code);
+ resolve();
+ }
+
+ // Call close before we're connected: throws error
+ // Make sure we call onerror/onclose asynchronously
+ ws._withinClose = 1;
+ ws.close(3333, "Closed before we were open: error");
+ ws._withinClose = 0;
+ is(ws.readyState, 2, "test-47: readyState should be CLOSING(2) after close(): got "
+ + ws.readyState);
+ });
+}
+
+// test48: see bug 1227136 - client calls close() from onopen() and waits until
+// WebSocketChannel::mSocketIn is nulled out on socket thread.
+function test48() {
+ return new Promise(function(resolve, reject) {
+ const pref_close = "network.websocket.timeout.close";
+ SpecialPowers.setIntPref(pref_close, 1);
+
+ var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-48");
+
+ ws.onopen = function() {
+ ws.close();
+
+ var date = new Date();
+ var curDate = null;
+ do {
+ curDate = new Date();
+ } while(curDate-date < 1500);
+
+ }
+
+ ws.onclose = function(e) {
+ ok(true, "ws close in test 48");
+ resolve();
+ }
+
+ SpecialPowers.clearUserPref(pref_close);
+ });
+}
diff --git a/dom/base/test/wholeTexty-helper.xml b/dom/base/test/wholeTexty-helper.xml
new file mode 100644
index 000000000..779a54c8b
--- /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 000000000..968ec209a
--- /dev/null
+++ b/dom/base/test/worker_postMessages.js
@@ -0,0 +1,33 @@
+function test_workers() {
+ onmessage = function(e) {
+ postMessage(e.data, e.ports);
+ }
+}
+
+function test_broadcastChannel() {
+ var bc = new BroadcastChannel('postMessagesTest_inWorkers');
+ bc.onmessage = function(e) {
+ postMessage(e.data);
+ }
+}
+
+function test_messagePort(port) {
+ port.onmessage = function(e) {
+ postMessage(e.data, e.ports);
+ }
+}
+
+onmessage = function(e) {
+ if (e.data == 'workers') {
+ test_workers();
+ postMessage('ok');
+ } else if (e.data == 'broadcastChannel') {
+ test_broadcastChannel();
+ 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 000000000..b93f02f80
--- /dev/null
+++ b/dom/base/usecounters.py
@@ -0,0 +1,71 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 buildconfig
+import collections
+import re
+import StringIO
+import sys
+
+def read_conf(conf_filename):
+ # Can't read/write from a single StringIO, so make a new one for reading.
+ stream = open(conf_filename, 'rU')
+
+ 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'property ([A-Za-z0-9]+)$', line)
+ if m:
+ property_name = m.group(1)
+ yield { 'type': 'property',
+ 'property_name': property_name }
+ continue
+ raise ValueError('error parsing %s at line %d' % (conf_filename, line_num))
+
+ return parse_counters(stream)
+
+def generate_histograms(filename):
+ # The mapping for use counters to telemetry histograms depends on the
+ # ordering of items in the dictionary.
+ 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):
+ append_counter('USE_COUNTER2_%s_DOCUMENT' % name, 'Whether a document %s' % desc)
+ append_counter('USE_COUNTER2_%s_PAGE' % name, 'Whether a page %s' % 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'] == 'property':
+ prop = counter['property_name']
+ append_counters('PROPERTY_%s' % prop.replace('-', '_').upper(), "used the '%s' property" % prop)
+
+ return items